Merge pull request #10 from mongodb/development

v1.13.0
This commit is contained in:
Mick Grove 2025-06-25 18:11:51 -07:00 committed by GitHub
commit 5cf64dca1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 1799 additions and 18 deletions

View file

@ -80,6 +80,8 @@ jobs:
- name: Build Darwin x64
run: make darwin-x64
- name: Run tests
run: make tests
- name: Move artifacts to dist
shell: bash
@ -109,6 +111,8 @@ jobs:
- name: Build Darwin arm64
run: make darwin-arm64
- name: Run tests
run: make tests
- name: Move artifacts to dist
shell: bash
@ -171,13 +175,12 @@ jobs:
name: kingfisher-windows-x64
path: dist/kingfisher-*windows-x64*.*
# ──────────────── Draft public release ────────────────
release:
name: Public GitHub Release
needs: [linux-x64, linux-arm64, windows, macos-x64, macos-arm64] # wait for all builds to finish
needs: [linux-x64, linux-arm64, windows, macos-x64, macos-arm64]
runs-on: ubuntu-latest
permissions:
contents: write # allow release upload
contents: write
steps:
- uses: actions/checkout@v4
- name: Read version from Cargo.toml
@ -189,11 +192,23 @@ jobs:
with:
path: target/release/kingfisher-*
merge-multiple: true
- name: Extract latest changelog section
run: |
awk '
BEGIN { grabbing = 0 }
/^## \[/ {
if (grabbing) exit; # already grabbed latest entry
grabbing = 1
}
grabbing { print }
' CHANGELOG.md > .latest_changelog.md
# ── create the release using just that snippet ─────────────────────
- name: Create release & upload assets
uses: ncipollo/release-action@v1
with:
tag: v${{ steps.version.outputs.version }}
name: "Kingfisher v${{ steps.version.outputs.version }}"
bodyFile: CHANGELOG.md # use existing changelog
generateReleaseNotes: false # turn off auto-notes
artifacts: target/release/**
bodyFile: .latest_changelog.md # ← only the most-recent entry
generateReleaseNotes: false
artifacts: target/release/**

View file

@ -2,6 +2,11 @@
All notable changes to this project will be documented in this file.
## [1.13.0]
- Added new rules for Planetscale, Postman, Openweather, opsgenie, pagerduty, pastebin, paypal, netlify, netrc, newrelic, ngrok, npm, nuget, mandrill, mapbox, microsoft teams, stripe, linkedin, mailchimp, mailgun, linear, line, huggingface, ibm cloud, intercom, ipstack, heroku, gradle, grafana
- Added `--rule-stats` command-line flag that will display rule performance statistics during a scan. Useful when creating or debugging rules
## [1.12.0]
- Added automatic update checks using GitHub releases.
- New `--self-update` flag installs updates when available

View file

@ -10,7 +10,7 @@ publish = false
[package]
name = "kingfisher"
version = "1.12.0"
version = "1.13.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View file

@ -122,6 +122,11 @@ cat /path/to/file.py | kingfisher scan -
kingfisher scan /path/to/repo --rule kingfisher.aws
```
### Display rule performance statistics
```bash
kingfisher scan /path/to/repo --rule-stats
```
---
## Scanning GitHub
@ -244,6 +249,10 @@ The document below details the four-field formula (rule SHA-1, origin label, st
See ([docs/FINGERPRINT.md](docs/FINGERPRINT.md))
## Rule Performance Profiling
Use `--rule-stats` to collect timing information for every rule. After scanning, the summary prints a **Rule Performance Stats** section showing how many matches each rule produced along with its slowest and average match times. Useful when creating rules or debugging rules.
## CLI Options
```bash
kingfisher scan --help

View file

@ -28,4 +28,4 @@ rules:
type: StatusMatch
- type: WordMatch
words:
- '"username":"kingfishermdb"'
- '"username"'

View file

@ -13,7 +13,7 @@ rules:
min_entropy: 3
confidence: medium
examples:
- azure devops pat = FBdFol081crwkIHWJH2yiqDDyrFjVSi7HWl22hN2hTYfsB8NlGDpJQQJ77BAACAAAAAAAAAAAAASAZDOBucT
- azure devops pat = FBdFol081crwkIHWJH2yiqDDyrFjVSi7HWl22hN2hTYfsB8NlGDpJQQJ77BAACAAAAAAAAAAAAASAZDOBucTj
references:
- https://learn.microsoft.com/en-us/rest/api/azure/devops/profile/profiles/get?view=azure-devops-rest-7.1&tabs=HTTP
- https://learn.microsoft.com/en-us/azure/devops/release-notes/2024/general/sprint-241-update

View file

@ -9,7 +9,7 @@ rules:
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
(
[0-9a-zA-Z]{52}
[0-9A-Z]{52}
)
\b
min_entropy: 3.3

View file

@ -10,9 +10,8 @@ rules:
(?:.|[\n\r]){0,16}?
\b
(
[A-Z0-9]{16}
(?:\.[A-Z0-9]{7}){2}
\.[A-Z0-9]{8}
[A-Z0-9]{20}
\.[A-Z0-9]{20}
)
\b
min_entropy: 3.3

View file

@ -12,7 +12,6 @@ rules:
confidence: medium
examples:
- fio-u-TaWoPIBovaGCbBkUtGPKWS0D3cu254VA33IFCCrtwl8J2Dtq2pMJ9MvNHmNoL2XX
- ffio-u-TaWoPIBovaGCbBkUtGPKWS0D3cu254VA33IFCCrtwl8J2Dtq2pMJ9MvNHmNoL2XX
references:
- https://developer.frame.io/api/reference/operation/getMe/
validation:

33
data/rules/gradle.yml Normal file
View file

@ -0,0 +1,33 @@
rules:
- name: Hardcoded Gradle Credentials
id: kingfisher.gradle.1
pattern: |
(?xi)
credentials \s* \{
(?:\s*//.*)*
\s* (?:username|password) \s ['"]([^'"]{1,60})['"]
(?:\s*//.*)*
\s* (?:username|password) \s ['"]([^'"]{1,60})['"]
min_entropy: 3.3
confidence: medium
examples:
- |
credentials {
username 'user'
password 'password'
}
- |
publishing {
repositories {
maven {
url "http://us01cmsysart01.example.com:8081/artifactory/Mobile-Libs-Internal"
credentials {
// your password here
username "SOME_USERNAME"
password "SOME_PASSWORD"
}
}
}
- "credentials {\n username 'user'\n password 'password'\n}"
- "credentials {\n username \"user\"\n password \"password\"\n}"

107
data/rules/grafana.yml Normal file
View file

@ -0,0 +1,107 @@
rules:
- name: Grafana API Token
id: kingfisher.grafana.1
pattern: |
(?xi)
\b
(
eyJrIjoi[a-z0-9]{60,100}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- 'Authorization: Bearer eyJrIjoiWHZiSWd5NzdCYUZnNUtibE8obUpESmE2bzJYNDRIc1UiLCJuIjoibXlrZXkiLCJpZCI7MX1'
- 'admin_client = GrafanaClient("eyJrIjoiY21sM1JRYjB6RnVYSTNLenRWQkFEaWN2bXI2V202U2IiLCJuIjoiYWRtaW5rZXkiLCJpZCI6MX0=", host=grafana_host, port=3000, protocol="http")'
references:
- https://grafana.com/docs/grafana/latest/developers/http_api/auth/
- name: Grafana Cloud API Token
id: kingfisher.grafana.2
pattern: |
(?xi)
\b
(
glc_
[a-z0-9+/]{40,150}
={0,2}
)
min_entropy: 3.3
confidence: medium
examples:
- ' "token": "glc_eyJrIjoiZjI0YzZkNGEwZDBmZmZjMmUzNTU3ODcxMmY0ZWZlNTQ1NTljMDFjOCIsIm6iOiJteXRva3VuIiwiaWQiOjF8"'
- 'grafana = glc_etLvNLoNMLt7MTczNNwNbN6Nm1ldGEtbW9paxRvcmlpZt14ZXN4NNwNatN6NLCxdKeH7KTUvWpNqCrHlMKE9EhLcZH7to'
references:
- https://grafana.com/docs/grafana-cloud/developer-resources/api-reference/cloud-api/#regions
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://grafana.com/api/stack-regions
- name: Grafana Service Account Token
id: kingfisher.grafana.3
pattern: |
(?x)
\b
(glsa_[a-zA-Z0-9]{32}_[a-fA-F0-9]{8})
\b
min_entropy: 3.3
confidence: medium
examples:
- |
curl -H "Authorization: Bearer glsa_HOruNAb7SOiCdshU7algkrq7FDsNSLAa_55e2f8be" -X GET '<grafana_url>/api/access-control/user/permissions' | jq
- |
// getData()
// {
// let url="http://localhost:4200/api/search"
// const headers = new HttpHeaders({
// 'Content-Type': 'application/json',
// 'Authorization': `Bearer glsa_Sof0HKi3agxrQP9qm5r2G98VacBNwV5P_9b638c45`
// })
// return this.http.get(url, {headers: headers});
// }
references:
- https://grafana.com/docs/grafana/latest/administration/service-accounts/
validation:
type: Http
content:
request:
method: GET
headers:
Authorization: Bearer {{ TOKEN }}
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: "{{ GRAFANADOMAIN }}/api/access-control/me"
depends_on_rule:
- rule_id: kingfisher.grafana.4
variable: GRAFANADOMAIN
- name: Grafana Domain
id: kingfisher.grafana.4
pattern: |
(?xi)
(?:https?://)?
(?:[A-Za-z0-9-]+\.)*
grafana\.[A-Za-z0-9.-]+
(?::\d{2,5})?
(?:[/?\#]\S*)?
min_entropy: 3.0
visible: false
confidence: medium
examples:
- https://grafana.example.com
- http://grafana.prod.eu-west.mycorp.internal:3000/login
- https://api.team1.grafana.services.cluster.local/health
- grafana.dev.foo-bar.co.uk

109
data/rules/hashes.yml Normal file
View file

@ -0,0 +1,109 @@
rules:
- name: Password Hash (md5crypt)
id: kingfisher.pwhash.1
pattern: '(\$1\$[./A-Za-z0-9]{8}\$[./A-Za-z0-9]{22})'
references:
- https://en.wikipedia.org/wiki/Crypt_(C)#MD5-based_scheme
- https://unix.stackexchange.com/a/511017
- https://hashcat.net/wiki/doku.php?id=example_hashes
- https://passwordvillage.org/salted.html#md5crypt
min_entropy: 3.3
confidence: medium
examples: # generated with `openssl passwd -1 -salt 'OKgLCmVl' 'a'`
- '$1$OKgLCmVl$d02jECa4DXn/oXX0R.MoQ/'
- '$1$28772684$iEwNOgGugqO9.bIz5sk8k/'
- name: Password Hash (bcrypt)
id: kingfisher.pwhash.2
# Format from Wikipedia:
# $2<a/b/x/y>$[cost]$[22 character salt][31 character hash]
pattern: '(\$2[abxy]\$\d+\$[./A-Za-z0-9]{53})'
references:
- https://en.wikipedia.org/wiki/Bcrypt
- https://hashcat.net/wiki/doku.php?id=example_hashes
min_entropy: 3.3
confidence: medium
examples:
- '$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW'
- '$2a$05$/VT2Xs2dMd8GJKfrXhjYP.DkTjOVrY12yDN7/6I8ZV0q/1lEohLru'
- '$2a$05$Uo385Fa0g86uUXHwZxB90.qMMdRFExaXePGka4WGFv.86I45AEjmO'
- '$2a$05$LhayLxezLhK1LhWvKxCyLOj0j1u.Kj0jZ0pEmm134uzrQlFvQJLF6'
- '$2y$12$atWJ1Nx6ep65tNx0YIJ4I.jzgI86znQbNRI3lF0qIt/XCYnEPxSc2'
- name: Password Hash (sha256crypt)
id: kingfisher.pwhash.3
pattern: |
(?x)
(
\$ 5
(?: \$ rounds=\d+ )?
\$ [./A-Za-z0-9]{8,16}
\$ [./A-Za-z0-9]{43}
)
references:
- https://en.wikipedia.org/wiki/Crypt_(C)#Key_derivation_functions_supported_by_crypt
- https://hashcat.net/wiki/doku.php?id=example_hashes
- https://passwordvillage.org/salted.html#sha256crypt
min_entropy: 3.3
confidence: medium
examples:
- '$5$rounds=5000$GX7BopJZJxPc/KEK$le16UF8I2Anb.rOrn22AUPWvzUETDGefUmAV8AZkGcD'
- '$5$9ks3nNEqv31FX.F$gdEoLFsCRsn/WRN3wxUnzfeZLoooVlzeF4WjLomTRFD'
- '$5$KAlz5SULZNybHwil$3UgmS1pmo2r5HG.tjbjzoVxISBh8IH81d.bJh4MCC19'
- name: Password Hash (sha512crypt)
id: kingfisher.pwhash.4
pattern: |
(?x)
(
\$ 6
(?: \$ rounds=\d+ )?
\$ [./A-Za-z0-9]{8,16}
\$ [./A-Za-z0-9]{86}
)
references:
- https://en.wikipedia.org/wiki/Crypt_(C)#Key_derivation_functions_supported_by_crypt
- https://hashcat.net/wiki/doku.php?id=example_hashes
- https://passwordvillage.org/salted.html#sha512crypt
min_entropy: 3.3
confidence: medium
examples:
- '$6$52450745$k5ka2p8bFuSmoVT1tzOyyuaREkkKBcCNqoDKzYiJL9RaE8yMnPgh2XzzF0NDrUhgrcLwg78xs1w5pJiypEdFX/'
- '$6$qoE2letU$wWPRl.PVczjzeMVgjiA8LLy2nOyZbf7Amj3qLIL978o18gbMySdKZ7uepq9tmMQXxyTIrS12Pln.2Q/6Xscao0'
- name: Password Hash (Cisco IOS PBKDF2 with SHA256)
id: kingfisher.pwhash.5
pattern: |
(?x)
(
\$ 8
\$ [./A-Za-z0-9]{8,16}
\$ [./A-Za-z0-9]{43}
)
references:
- https://en.wikipedia.org/wiki/Crypt_(C)#Key_derivation_functions_supported_by_crypt
- https://hashcat.net/wiki/doku.php?id=example_hashes
min_entropy: 3.3
confidence: medium
examples:
- '$8$TnGX/fE4KGHOVU$pEhnEvxrvaynpi8j4f.EMHr6M.FzU8xnZnBr/tJdFWk'
- '$8$mTj4RZG8N9ZDOk$elY/asfm8kD3iDmkBe3hD2r4xcA/0oWS5V3os.O91u.'
- name: Password Hash (Kerberos 5, etype 23, AS-REP)
id: kingfisher.krb5.asrep.23.1
pattern: |
(?x)
(
\$ krb5asrep
\$ 23
\$
(?: [^:]+ : )?
[0-9a-f]{32}
\$ [0-9a-f]{64,}
)
\b
references:
- https://hashcat.net/wiki/doku.php?id=example_hashes
min_entropy: 3.3
confidence: medium
examples: # Kerberos 5, etype 23, AS-REP
- '$krb5asrep$23$user@domain.com:3e156ada591263b8aab0965f5aebd837$007497cb51b6c8116d6407a782ea0e1c5402b17db7afa6b05a6d30ed164a9933c754d720e279c6c573679bd27128fe77e5fea1f72334c1193c8ff0b370fadc6368bf2d49bbfdba4c5dccab95e8c8ebfdc75f438a0797dbfb2f8a1a5f4c423f9bfc1fea483342a11bd56a216f4d5158ccc4b224b52894fadfba3957dfe4b6b8f5f9f9fe422811a314768673e0c924340b8ccb84775ce9defaa3baa0910b676ad0036d13032b0dd94e3b13903cc738a7b6d00b0b3c210d1f972a6c7cae9bd3c959acf7565be528fc179118f28c679f6deeee1456f0781eb8154e18e49cb27b64bf74cd7112a0ebae2102ac'
- '$krb5asrep$23$8cf8eb5287e28a4006c064892150c4fb$3e05ecc13548bec8e1eeb900dea5429cc6931bae9b8524490eb3a8801560871fe44355ed556202afbb39872e1bbb5c3c4f1b37dcd68fda89a23ebad917d4bbb0933edd94331598939e5d0c0c98c7e219a2e9dd6b877280d1bd7c51131413be577a167208bcc21e9fe7ae8f393278d740e72ca5c44c42d5cb0bf6bab0a36f1b88b7ddc4abbc6f152e652f6ba35c2955fb4132e11b7e566f3b422c3740f79847b77783d245a4e570b8a621b4ff6ff4815566446af70313ee78133707a76a4e4424783bd7c04920aa822a1a36b29f7e25cef186e6439fc46e42e23d6bd918969ef49b8388aef158e443b3a57dbde7ada631fbef7326f9046a9b'
- '$krb5asrep$23$c447eddaebf22ebf006a8fc6f986488c$eb3a17eb56287b474cecad5d4e0490d949977ba3f5015220bcd3080444d5601d67b76c5453b678e8527624e40c273bea4cfe4a7303e136b9bc3b9e63b6fb492ee4b4d2f830c5fa5a55466b57a678f708438f6712354a2deb851792b09270f4941966b82a2fd5ad8fa1fbd95a60b0f9bcd57774b3e55467a02ffcb3f1379104c24e468342f83df20b571e6f34f9a9842b43735d58d94514dcefa76719c0f5c7c3a3bfa770380924625aa0a3472d7c02d10dbb278fd946f7efcfe59a4d4cb7bdb9c5dbddc027611fe333d3ac940ec5b4ed43b55ab54b03cd2df0a9a2a7b5d235c226b259bd5ff8e0e49680351d4f0c4d13e258bc8d383cad6fc2711be0'
- '$krb5asrep$23$771adbc2397abddef676742924414f2b$2df6eb2d9c71820dc3fa2c098e071d920f0e412f5f12411632c5ee70e004da1be6f003b78661f8e4507e173552a52da751c45887c19bc1661ed334e0ccb4ef33975d4bd68b3d24746f281b4ca4fdf98fca0e50a8e845ad7d834e020c05b1495bc473b0295c6e9b94963cb912d3ff0f2f48c9075b0f52d9a31e5f4cc67c7af1d816b6ccfda0da5ccf35820a4d7d79073fa404726407ac840910357ef210fcf19ed81660106dfc3f4d9166a89d59d274f31619ddd9a1e2712c879a4e9c471965098842b44fae7ca6dd389d5d98b7fd7aca566ca399d072025e81cf0ef5075447687f80100307145fade7a8'
- '$krb5asrep$23$user@domain.com:3e156ada591263b8aab0965f5aebd837$007497cb51b6c8116d6407a782ea0e1c5402b17db7afa6b05a6d30ed164a9933c754d720e279c6c573679bd27128fe77e5fea1f72334c1193c8ff0b370fadc6368bf2d49bbfdba4c5dccab95e8c8ebfdc75f438a0797dbfb2f8a1a5f4c423f9bfc1fea483342a11bd56a216f4d5158ccc4b224b52894fadfba3957dfe4b6b8f5f9f9fe422811a314768673e0c924340b8ccb84775ce9defaa3baa0910b676ad0036d13032b0dd94e3b13903cc738a7b6d00b0b3c210d1f972a6c7cae9bd3c959acf7565be528fc179118f28c679f6deeee1456f0781eb8154e18e49cb27b64bf74cd7112a0ebae2102ac'

34
data/rules/heroku.yml Normal file
View file

@ -0,0 +1,34 @@
rules:
- name: Heroku API Key
id: kingfisher.heroku.1
pattern: |
(?xi)
heroku
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[0-9a-f]{8}-[0-9a-f]{4}-
[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
)
\b
min_entropy: 3.0
confidence: medium
examples:
- 'HEROKU_API_KEY: c55dbac4-e0e8-4a06-b892-75cac2387ce5'
references:
- https://devcenter.heroku.com/articles/authentication
validation:
type: Http
content:
request:
method: GET
headers:
Accept: application/vnd.heroku+json; version=3
Authorization: Bearer {{ TOKEN }}
url: https://api.heroku.com/apps
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]

View file

@ -0,0 +1,39 @@
rules:
- name: HuggingFace User Access Token
id: kingfisher.huggingface.1
pattern: |
(?xi)
\b
(?:
(
(?:api_org|hf)_
(?:[0-9A-Z]{17}){2}
)
)
\b
references:
- https://huggingface.co/docs/hub/security-tokens
min_entropy: 3.3
confidence: medium
examples:
- 'HF_TOKEN:"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"'
- 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
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
Content-Type: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- match_all_words: true
type: WordMatch
words:
- '"name":'
- '"id":'
url: https://huggingface.co/api/whoami-v2

35
data/rules/ibm.yml Normal file
View file

@ -0,0 +1,35 @@
rules:
- name: IBM Cloud User API Key
id: kingfisher.ibm.1
pattern: |
(?xi)
(?:ibm(?:cloud)?|bx)
(?:.|[\n\r]){0,32}?
\b
(
[0-9A-Z_-]{42,44}
)
min_entropy: 3.5
confidence: medium
examples:
- ibmcloud_apikey = abcdef0123_56789abcdef0123456789abcdef01234
- ibm_platform_key="f-_RrJDVnuVh07HNTcmnQx_b6CbcQsxmEarVm9P_RWtF"
references:
- https://cloud.ibm.com/docs/account?topic=account-userapikey
- https://cloud.ibm.com/apidocs/iam-identity-token-api
validation:
type: Http
content:
request:
method: GET
headers:
Authorization: Basic Yng6Yng= # public “bx:bx” client credentials
Accept: application/json
url: https://iam.cloud.ibm.com/v1/apikeys/details?apikey={{ TOKEN }}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]

35
data/rules/intercom.yml Normal file
View file

@ -0,0 +1,35 @@
rules:
- name: Intercom API Token
id: kingfisher.intercom.1
pattern: |
(?xi)
(?:intercom(?:_access)?|ic)
(?:.|[\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,16}?
(
[0-9A-Z+/]{59}=
)
min_entropy: 3.5
confidence: medium
examples:
- "intercom_access_token: dG9rOvI0NmJlMTA5XzQwM2NfNDVlM184MjQzXzkwMDnmOTE1NGIyONoxOjA="
- ic_token = "g1ZsclJXTjNfc1pBSzJDemE0eFVDU0U5c25CeDN4Vm9hQ2Zac0hXemZHNGVDPQ=="
references:
- https://developers.intercom.com/docs/build-an-integration/learn-more/rest-apis
validation:
type: Http
content:
request:
method: GET
headers:
Accept: application/json
Authorization: Bearer {{ TOKEN }}
url: https://api.intercom.io/me
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200

28
data/rules/ionic.yml Normal file
View file

@ -0,0 +1,28 @@
rules:
- name: Ionic API token
id: kingfisher.ionic.1
pattern: |
(?xi)
\b
(
ion_
[a-z0-9]{42}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- ion_VNR17uGgdxr9P2aOrCulvSLTFDqijIV2ImQsOUhDEI
validation:
type: Http
content:
request:
headers:
Authorization: 'Bearer {{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.ionic.io/v1/auth/status

33
data/rules/ipstack.yml Normal file
View file

@ -0,0 +1,33 @@
rules:
- name: IpStack API Key
id: kingfisher.ipstack.1
pattern: |
(?xi)
\b
ipstack
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
(?:[0-9a-f]{16}){2}
)
\b
min_entropy: 3.0
confidence: medium
examples:
- "ipstack_token=123e4567e89b12d3a456426614174000"
- "ipstack_key=abcdefabcdefabcdefabcdefabcdef12"
references:
- https://ipstack.com/documentation
validation:
type: Http
content:
request:
method: GET
url: http://api.ipstack.com/1.1.1.1?access_key={{ TOKEN }}
response_matcher:
- type: WordMatch
words:
- '"ip":"1.1.1.1"'
- '"continent_code"'

24
data/rules/jenkins.yml Normal file
View file

@ -0,0 +1,24 @@
rules:
- name: Jenkins Token or Crumb
id: kingfisher.jenkins.1
pattern: '(?i)jenkins.{0,10}(?:crumb)?.{0,10}\b([0-9a-f]{32,36})\b'
categories: [api, fuzzy, secret]
min_entropy: 3.3
confidence: medium
examples:
- |
jenkins_user = 'root'
# jenkins_passwd = '116365fd86d63bf507aba962606a5c8956' Pre token
jenkins_passwd = '11811f784531053132519844d047186074' # Dev Token
jenkins_url = 'http://10.1.188.121'
- |
export JENKINS_USER=justin-admin-edit-view
export JENKINS_TOKEN=11f4274ec59be12eace9a08b08ee13d54b
export JENKINS=jenkins-cicd.apps.sno.openshiftlabs.net
- |
sh "curl -X POST 'http://jenkins.lsfusion.luxsoft.by/job/${Paths.updateParentVersionsJob}/build' --user ${USERPASS} -H 'Jenkins-Crumb:440561953171ba44ace9740562d172bb'"
negative_examples:
- '1. ~~Does not play well with [Build Token Root Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Build+Token+Root+Plugin) URL formats.~~ (added with [this commit](https://github.com/morficus/Parameterized-Remote-Trigger-Plugin/commit/f687dbe75d1c4f39f7e14b68220890384d7c5674) )'
references:
- https://www.jenkins.io/blog/2018/07/02/new-api-token-system/
- https://www.jenkins.io/doc/book/security/csrf-protection/

54
data/rules/jira.yml Normal file
View file

@ -0,0 +1,54 @@
rules:
- name: Jira Domain
id: kingfisher.jira.1
pattern: |
(?x)
(?i)
(
[a-z][a-z0-9-]{5,24}\.atlassian\.net
)
\b
min_entropy: 3.5
visible: false
confidence: medium
examples:
- example-jira.atlassian.net
- jira.sprintUri= https://leakyday.atlassian.net/rest
- name: Jira Token
id: kingfisher.jira.2
pattern: |
(?x)
(?i)
\b
jira
(?:.|[\n\r]){0,8}?
(?:SECRET|PRIVATE|ACCESS|KEY|PASSWORD|TOKEN)
(?:.|[\n\r]){0,16}?
\b
(
[a-z0-9-]{24}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- 'Here is my Jira token: VDOheDe1sSCeGkuTARhkFDE2'
- public static final String JIRA_PASSWORD = "VDOheDe1sSCeGkuTARhkFDE2";
validation:
type: Http
content:
request:
headers:
Accept: application/json
Authorization: Basic {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://{{ DOMAIN }}/rest/api/3/dashboard
depends_on_rule:
- rule_id: kingfisher.jira.1
variable: DOMAIN

36
data/rules/line.yml Normal file
View file

@ -0,0 +1,36 @@
rules:
- name: Line Messaging API Token
id: kingfisher.line.1
pattern: |
(?x)
(?i)
\b
line
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
(?:[0-9A-Za-z+/]{57}){3}=?
)
min_entropy: 3.5
confidence: medium
examples:
- line_access_token = 13IRTqF+j0TfDtuJoIWKRBPhpDnqYUaaSlOilnoy0urLE+kbf5hN4HUf5pSPw20ruyO0BFFF1IDjnBojctp5emFw0hZ51WxB8c75qo48upJInfmqDQ1xrFd4yFKBwx4yRBHYXmI/FyrtcWKd0FBoBAdB04t81/1O/w1cDnyilFU=
- linemsg_token:"13IRTqF+j0TfDtuJoIWKRBPhpDnqYUaaSlOilnoy0urLE+kbf5hN4HUf5pSPw20ruyO0BFFF1IDjnBojctp5emFw0hZ51WxB8c75qo48upJInfmqDQ1xrFd4yFKBwx4yRBHYXmI/FyrtcWKd0FBoBAdB04t81/1O/w1cDnyilFU="
references:
- https://developers.line.biz/en/reference/messaging-api/#get-consumption
validation:
type: Http
content:
request:
headers:
Authorization: 'Bearer {{ TOKEN }}'
Content-Type: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.line.me/v2/bot/message/quota/consumption

38
data/rules/linear.yml Normal file
View file

@ -0,0 +1,38 @@
rules:
- name: Linear API Key
id: kingfisher.linear.1
pattern: |
(?x)
(?i)
\b
(
lin_api_
(?:[0-9A-Za-z]{8}){5}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- linear_api_key = lin_api_2thngjik222gkiihzivh242LU7zvkdvdgB14B41S
- lin_api_token:"lin_api_9A6bCDeF0Gh1Ij2Klm3No4PQr5St6Uv7Wx8YZaBc"
references:
- https://linear.app/developers/graphql
validation:
type: Http
content:
request:
method: POST
headers:
Authorization: '{{ TOKEN }}'
Content-Type: application/json
body: >
{
"query": "query { issues(first: 1) { nodes { id } } }"
}
url: https://api.linear.app/graphql
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"issues":', '"nodes":']

67
data/rules/linkedin.yml Normal file
View file

@ -0,0 +1,67 @@
rules:
- name: LinkedIn Client ID
id: kingfisher.linkedin.1
pattern: |
(?x)(?i)
linkedin
.?
(?: api | app | application | client | consumer | customer )?
.?
(?: id | identifier | key )
.{0,2} \s{0,20} .{0,2} \s{0,20} .{0,2}
\b ([a-z0-9]{12,14}) \b
references:
- https://docs.microsoft.com/en-us/linkedin/shared/api-guide/best-practices/secure-applications
min_entropy: 2.5
confidence: medium
examples:
- 'Email ID Last 5 Digits of your SSN LinkedIn ID Availability'
- |
LINKEDIN_KEY = "77yg7tx91p4lag"
LINKEDIN_SECRET = "zt7GeN6IH911xvRj"
validation:
type: Http
content:
request:
headers:
Authorization: "Bearer {{ TOKEN }}"
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.linkedin.com/v2/me
- name: LinkedIn Secret Key
id: kingfisher.linkedin.2
pattern: |
(?x)(?i)
linkedin
.?
(?: api | app | application | client | consumer | customer | secret | key )
.?
(?: key | oauth | sec | secret )?
.{0,2} \s{0,20} .{0,2} \s{0,20} .{0,2}
\b ([a-z0-9]{16}) \b
references:
- https://docs.microsoft.com/en-us/linkedin/shared/api-guide/best-practices/secure-applications
min_entropy: 3.3
confidence: medium
examples:
- |
LINKEDIN_KEY = "77yg7tx91p4lag"
LINKEDIN_SECRET = "zt7GeN6IH911xvRj"
validation:
type: Http
content:
request:
headers:
Authorization: "Bearer {{ TOKEN }}"
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.linkedin.com/v2/me

36
data/rules/mailchimp.yml Normal file
View file

@ -0,0 +1,36 @@
rules:
- name: Mailchimp API Key
id: kingfisher.mailchimp.1
pattern: |
(?xi)
mailchimp
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
(?:[0-9a-f]{8}){4}
-us\d{1,2}
)
min_entropy: 3.5
confidence: medium
examples:
- 'mailchimp_token = abcdef1234567890abcdef1234567890-us1'
- 'mailchimp_key = "0123456789abcdeffedcba9876543210-us20"'
validation:
type: Http
content:
request:
headers:
Authorization: 'Basic {{ "x:" | append: TOKEN | b64enc }}'
Accept: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: 'https://{{ TOKEN | split: "-" | last }}.api.mailchimp.com/3.0/ping'
references:
- https://mailchimp.com/developer/marketing/api/root/
- https://mailchimp.com/developer/guides/marketing-api-conventions/

63
data/rules/mailgun.yml Normal file
View file

@ -0,0 +1,63 @@
rules:
- name: MailGun Token
id: kingfisher.mailgun.1
pattern: |
(?x)
(?i)
\b
mailgun
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
(?:[0-9A-Za-z-]{24}){3}
)
min_entropy: 3.5
confidence: medium
examples:
- mailgun_api_key=abcdef123456abcdef123456abcdef123456abcdef123456abcdef123456abcdef123456
validation:
type: Http
content:
request:
headers:
Authorization: Basic {{ TOKEN | b64enc }}
Accept: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.mailgun.net/v3/address/validate?address=test@example.com
- name: MailGun Primary Key
id: kingfisher.mailgun.2
pattern: |
(?x)
(?i)
(?:mailgun|mg)
(?:.|[\n\r]){0,64}?
\b
(
key-(?:[0-9a-f]{8}){4}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- key-mailgun_token= key-ad13dfc23adf55fa404a91e76d96f472
validation:
type: Http
content:
request:
headers:
Authorization: 'Basic {{ "api:" | append: TOKEN | b64enc }}'
Accept: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.mailgun.net/v3/address/validate?address=test@example.com

38
data/rules/mandrill.yml Normal file
View file

@ -0,0 +1,38 @@
rules:
- name: Mandrill API Key
id: kingfisher.mandrill.1
pattern: |
(?x)
(?i)
\b
mandrill
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
(?:[0-9A-Za-z_-]{11}){2}
)
min_entropy: 3.5
confidence: medium
examples:
- mandrill_token = taqnVL1P5AJrM4oU4opSqQ
categories:
- api
- identifier
validation:
type: Http
content:
request:
method: POST
headers:
Content-Type: application/json
body: |
{ "key": "{{ TOKEN }}" }
url: https://mandrillapp.com/api/1.0/users/ping.json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"PONG!"']

73
data/rules/mapbox.yml Normal file
View file

@ -0,0 +1,73 @@
rules:
- name: Mapbox Public Access Token
id: kingfisher.mapbox.1
pattern: '(?i)(?s)mapbox.{0,30}(pk\.[a-z0-9\-+/=]{32,128}\.[a-z0-9\-+/=]{20,30})(?:[^a-z0-9\-+/=]|$)'
min_entropy: 3.3
confidence: medium
examples:
- |
mapboxApiKey:
'pk.eyJ1Ijoia3Jpc3R3IiwiYSI6ImNqbGg1N242NTFlczczdnBcf99iMjgzZ2sifQ.lUneM-o3NucXN189EYyXxQ'
references:
- https://docs.mapbox.com/api/accounts/tokens/#token-format
- https://docs.mapbox.com/help/getting-started/access-tokens/
- https://docs.mapbox.com/help/troubleshooting/how-to-use-mapbox-securely
validation:
type: Http
content:
request:
method: GET
url: https://api.mapbox.com/styles/v1/mapbox/streets-v11?access_token={{ TOKEN }}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid
- name: Mapbox Secret Access Token
id: kingfisher.mapbox.2
pattern: '(?i)(?s)mapbox.{0,30}(sk\.[a-z0-9\-+/=]{32,128}\.[a-z0-9\-+/=]{20,30})(?:[^a-z0-9\-+/=]|$)'
min_entropy: 3.3
confidence: medium
examples:
- " //mapboxgl.accessToken = 'sk.eyJ1Ijoic2hlbmdsaWgiLCJhIjCf99ttaWF5bDBsMGNlaDJubGZyMGUwZXNmaCJ9.eI8KXNm5zKZXOKh0c8u9vg';"
- 'export MAPBOX_SECRET_TOKEN=sk.eyJ1IjoiY2FwcGVsYWVyZSIsImEicf99c1BaTkZnIn0.P4lD1eHeSEx7AsBq1zbJ4g'
references:
- https://docs.mapbox.com/api/accounts/tokens/#token-format
- https://docs.mapbox.com/help/getting-started/access-tokens/
- https://docs.mapbox.com/help/troubleshooting/how-to-use-mapbox-securely
validation:
type: Http
content:
request:
method: GET
url: https://api.mapbox.com/styles/v1/mapbox/streets-v11?access_token={{ TOKEN }}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid
- name: Mapbox Temporary Access Token
id: kingfisher.mapbox.3
pattern: '(?i)(?s)mapbox.{0,30}(tk\.[a-z0-9\-+/=]{32,128}\.[a-z0-9\-+/=]{20,30})(?:[^a-z0-9\-+/=]|$)'
min_entropy: 3.3
confidence: medium
examples:
- " //mapboxgl.accessToken = 'tk.eyJ1Ijoic2hlbmdsaWgiLCJhIjCf99ttaWF5bDBsMGNlaDJubGZyMGUwZXNmaCJ9.eI8KXNm5zKZXOKh0c8u9vg';"
- 'export MAPBOX_TEMP_TOKEN=tk.eyJ1IjoiY2FwcGVsYWVyZSIsImEicf99c1BaTkZnIn0.P4lD1eHeSEx7AsBq1zbJ4g'
references:
- https://docs.mapbox.com/api/accounts/tokens/#token-format
- https://docs.mapbox.com/help/getting-started/access-tokens/
- https://docs.mapbox.com/help/troubleshooting/how-to-use-mapbox-securely
validation:
type: Http
content:
request:
method: GET
url: https://api.mapbox.com/styles/v1/mapbox/streets-v11?access_token={{ TOKEN }}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid

View file

@ -0,0 +1,52 @@
rules:
- name: Microsoft Teams Webhook
id: kingfisher.msteams.1
pattern: |
(?xi)
(
https://
outlook\.office\.com/webhook/
[0-9a-f]{8}-
[0-9a-f]{4}-
[0-9a-f]{4}-
[0-9a-f]{4}-
[0-9a-f]{12}
@
[0-9a-f]{8}-
[0-9a-f]{4}-
[0-9a-f]{4}-
[0-9a-f]{4}-
[0-9a-f]{12}
/IncomingWebhook/
[0-9a-f]{32}
/
[0-9a-f]{8}-
[0-9a-f]{4}-
[0-9a-f]{4}-
[0-9a-f]{4}-
[0-9a-f]{12}
)
min_entropy: 3.3
confidence: medium
examples:
- 'https://outlook.office.com/webhook/9da5da9c-4218-4c22-aed6-b5c8baebfff5@2f2b54b7-0141-4ba7-8fcd-ab7d17a60547/IncomingWebhook/1bf66ccbb8e745e791fa6e6de0cf465b/4361420b-8fde-48eb-b62a-0e34fec63f5c'
- 'https://outlook.office.com/webhook/fa4983ab-49ea-4c1b-9297-2658ea56164c@f784fbed-7fc7-4c7a-aae9-d2f387b67c5d/IncomingWebhook/4d2b3a16113d47b080b7a083b5a5e533/74f315eb-1dde-4731-b6b5-2524b77f2acd'
- 'https://outlook.office.com/webhook/555aa7fc-ea71-4fb7-ae9e-755caa4404ed@72f988bf-86f1-41af-91ab-2d7cd011db47/IncomingWebhook/16085df23e564bb9076842605ede3af2/51dab674-ad95-4f0a-8964-8bdefc25b6d9'
- 'https://outlook.office.com/webhook/2f92c502-7feb-4a6c-86f1-477271ae576f@990414fa-d0a3-42f5-b740-21d865a44a28/IncomingWebhook/54e43eb586f14aa9984d5c0bec3d5050/539ce6fa-e9aa-413f-a79b-fb7e8998fcac'
validation:
type: Http
content:
request:
method: POST
url: '{{ TOKEN }}'
headers:
Content-Type: application/json
body: '{"text":""}'
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 400
- type: WordMatch
words:
- 'Text is required'

View file

@ -0,0 +1,38 @@
rules:
- name: Microsoft Teams Webhook
id: kingfisher.microsoftteamswebhook.1
pattern: |
(?x)
https://[a-zA-Z0-9]+\.webhook\.office\.com/webhookb2
/
[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}
@
[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}
/
IncomingWebhook
/
[a-zA-Z0-9]{32}
/
[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}
min_entropy: 3.3
confidence: medium
examples:
- "https://contoso.webhook.office.com/webhookb2/12345678-abcd-1234-efgh-56789abcdef0@12345678-abcd-1234-efgh-56789abcdef0/IncomingWebhook/abcdefgh12345678abcdefgh12345678/12345678-abcd-1234-efgh-56789abcdef0"
validation:
type: Http
content:
request:
body: |
{'text':''}
headers:
Content-Type: application/json
method: POST
response_matcher:
- type: StatusMatch
status:
- 200
- report_response: true
type: WordMatch
words:
- "Text is required"
url: '{{ TOKEN }}'

62
data/rules/netlify.yml Normal file
View file

@ -0,0 +1,62 @@
rules:
- name: Netlify API Key
id: kingfisher.netlify.1
pattern: |
(?xi)
netlify
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
([a-f0-9]{60,64})
\b
min_entropy: 3.3
examples:
- netlify_token=3cdfad7b885a6daceff3fb820389115750b373763fb30b10ca0382648b55872d
- netlify_secret=7a9ef2c84d6b3e5f1c8a0b9d2e4f6a8c7b3d5e9f2a1c8b4d6e3f5a9c7b2d8e4
references:
- https://howtorotate.com/docs/tutorials/netlify/
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
url: https://api.netlify.com/api/v1/user
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- name: Netlify API Key
id: kingfisher.netlify.2
pattern: |
(?xi)
\b
netlify
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
([A-Za-z0-9_-]{43,45})
\b
min_entropy: 3.5
confidence: medium
examples:
- netlify_token=G5yT54abRasekrOpe7SaArsowiuHTeR45sfEhsH-K1L2
- netlify_key=H7xZ98cdWbsemqNpv8UaXtsnyjKgVeQ34rsDkpM-N5P6
references:
- https://howtorotate.com/docs/tutorials/netlify/
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
url: https://api.netlify.com/api/v1/user
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]

29
data/rules/netrc.yml Normal file
View file

@ -0,0 +1,29 @@
rules:
- name: netrc Credentials
id: kingfisher.netrc.1
pattern: |
(?x)
( machine \s+ [^\s]+ | default )
\s+
login \s+ ([^\s]+)
\s+
password \s+ ([^\s]+)
min_entropy: 3.3
confidence: medium
examples:
- 'machine api.github.com login ziggy^stardust password 012345abcdef'
- |
```
machine raw.github.com
login visionmedia
password pass123
```
- |
"""
machine api.wandb.ai
login user
password 7cc938e45e63e9014f88f811be240ba0395c02dd
"""
references:
- https://everything.curl.dev/usingcurl/netrc
- https://devcenter.heroku.com/articles/authentication#api-token-storage

33
data/rules/newrelic.yml Normal file
View file

@ -0,0 +1,33 @@
rules:
- name: New Relic Personal API Key
id: kingfisher.newrelic.1
pattern: |
(?xi)
\b
newrelic
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[A-Z0-9_.]{4}
-
[A-Z0-9_.]{42}
)
min_entropy: 3.3
confidence: medium
examples:
- newrelic_key = abcd-1234567890abcdef1234567890abcdef1234dd5678
- newrelic_token = 1234-abcdefghijklmnopqrstuvwxyzABCD2342EFGHIJKL
validation:
type: Http
content:
request:
method: GET
headers:
X-Api-Key: '{{ TOKEN }}'
url: https://api.newrelic.com/v2/servers.json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]

31
data/rules/ngrok.yml Normal file
View file

@ -0,0 +1,31 @@
rules:
- name: Ngrok API Key
id: kingfisher.ngrok.1
pattern: |
(?x)(?i)
ngrok
(?:.|[\\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(?:[a-z0-9]{25,30}_\d[a-z0-9]{20}|(?:cr_|ak_)[a-z0-9]{25,30})
\b
min_entropy: 4
examples:
- 'ngrok authtoken: 2Ot3hdNCKF3JRJDCioqNV2J0PPc_6th2CSUm9KsjfXdtRDvzT'
validation:
type: Http
content:
request:
method: GET
headers:
Authorization: Bearer {{ TOKEN }}
ngrok-version: 2
url: https://api.ngrok.com/endpoints
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words:
- '"endpoints":'

65
data/rules/npm.yml Normal file
View file

@ -0,0 +1,65 @@
rules:
- name: NPM Access Token (fine-grained)
id: kingfisher.npm.1
pattern: |
(?x)
\b
(
npm_[A-Za-z0-9]{36}
)
\b
references:
- https://docs.npmjs.com/about-access-tokens
- https://github.com/github/roadmap/issues/557
- https://github.blog/changelog/2022-12-06-limit-scope-of-npm-tokens-with-the-new-granular-access-tokens/
min_entropy: 3.3
confidence: medium
examples:
- 'npm_TCllNwh2WLQlMWVhybM1iQrsTj6rMQ0BOh6d'
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"name":']
url: https://registry.npmjs.org/-/npm/v1/user
- name: NPM Access Token (old format)
id: kingfisher.npm.2
pattern: |
(?xi)
\b
(?:_authToken|NPM_TOKEN)
(?:.|[\n\r]){0,16}?
(
[0-9A-F]{8}
(?:-[0-9A-F]{4}){3}
-[0-9A-F]{12}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- '"_authToken": "b98ec224-cdb2-4340-b7bd-9617fc719d1d"'
- '-export NPM_TOKEN="007e64c7-635d-4d54-8295-f364cd8e0e0f"'
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"name":']
url: https://registry.npmjs.org/-/npm/v1/user

68
data/rules/nuget.yml Normal file
View file

@ -0,0 +1,68 @@
rules:
- name: NuGet API Key
id: kingfisher.nuget.1
pattern: |
(?x)
\b
(
oy2[a-z0-9]{43}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- oy2er16dp0r068m6p36u4bvr9nmkescfm1pf9lek1bgn3n
- oy2gdbfofub9ecpohsfdndem7nr8sui1g8le3ptnljqhlu
validation:
type: Http
content:
request:
method: POST
url: https://www.nuget.org/api/v2/package/create-verification-key/Newtonsoft.Json
headers:
X-NuGet-ApiKey: '{{ TOKEN }}'
X-NuGet-Protocol-Version: '4.1.0'
Content-Length: '0'
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"Key":']
- name: NuGet API Key
id: kingfisher.nuget.2
pattern: |
(?xi)
\b
nuget
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9]{46}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- nuget key = af7e33adc0rq7ls6ijcfmb5rgf1gp2o0tqk6d8s5p21k6t
- nuget secret dfaru1u13sd58q0kd0edvs4276p2mb6o31eljkvjh8t30u
validation:
type: Http
content:
request:
method: POST
url: https://www.nuget.org/api/v2/package/create-verification-key/Newtonsoft.Json
headers:
X-NuGet-ApiKey: '{{ TOKEN }}'
X-NuGet-Protocol-Version: '4.1.0'
Content-Length: '0'
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"Key":']

View file

@ -0,0 +1,37 @@
rules:
- name: OpenWeather Map API Key
id: kingfisher.openweather.1
pattern: |
(?xi)
(?:pyowm|openweather|\bowm\b)
(?:.|[\n\r]){0,64}?
\b
(
(?:
[a-z0-9]{32}
)
\b
|APPID=
(?:
[a-z0-9]{32}
)
)
\b
min_entropy: 3.5
examples:
- pyowm = '3k144a5af729351d0fc58bdrj9a21mkr'
- owm = '3k144a5af729351d0fc58bdrj9a21mkr'
- openweatherapikey=cd2b1d12d01ae2deffecfebafcc3c31d
- apikey=openweather:cd2b1d12d01ae2deffecfebafcc3c31d
validation:
type: Http
content:
request:
method: GET
response_matcher:
- report_response: true
- match_all_status: true
status:
- 200
type: StatusMatch
url: https://api.openweathermap.org/geo/1.0/reverse?lat=0&lon=0&limit=1&appid={{ TOKEN }}

32
data/rules/opsgenie.yml Normal file
View file

@ -0,0 +1,32 @@
rules:
- name: OpsGenie API Key
id: kingfisher.opsgenie.1
pattern: |
(?x)
(?i)
\b
opsgenie
(?:.|[\\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
)
min_entropy: 3.5
examples:
- opsgenie_api_key = '12345678-9abc-def0-1234-56789abcdef0'
validation:
type: Http
content:
request:
headers:
Authorization: GenieKey {{ TOKEN }}
method: GET
url: https://api.opsgenie.com/v2/alerts
response_matcher:
- report_response: true
- type: WordMatch
words:
- "Could not authenticate"
negative: true

View file

@ -0,0 +1,36 @@
rules:
- name: PagerDuty API Key
id: kingfisher.pagerduty.1
pattern: |
(?xi)
\b
(?:pagerduty|pager[_-]duty|pd[-_\]=\)]|pd\.webhook?)
(?:.|[\n\r]){0,16}?
(
u\+[A-Z0-9_+-]{18} # new personal tokens
|
[A-Z0-9_-]{20} # legacy personal tokens
|
[A-F0-9]{32} # integration keys / routing keys
)
\b
min_entropy: 3.3
confidence: medium
examples:
- pagerduty_key = u+Lyhd2_N2MCy+ZoH-S5
- pd_key = u+3xVszZ-b4m+T6d23KA
validation:
type: Http
content:
request:
method: GET
url: https://api.pagerduty.com/abilities
headers:
Authorization: Token token={{ TOKEN }}
Accept: application/vnd.pagerduty+json;version=2
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"abilities":']

View file

@ -0,0 +1,74 @@
rules:
- name: particle.io Access Token
id: kingfisher.particleio.1
pattern: |
(?x)
https://api\.particle\.io/v1/[a-zA-Z0-9_\-\s/"\\?]*
(?:access_token=|Authorization:\s*Bearer\s*)
\b
([a-zA-Z0-9]{40})
\b
min_entropy: 3.3
confidence: medium
examples:
- |
curl https://api.particle.io/v1/devices \
-H "Authorization: Bearer 38bb7b318cc6898c80317decb34525844bc9db55"
- |
curl https://api.particle.io/v1/devices \
-d access_token=38bb7b318cc6898c80317decb34525844bc9db55
- 'curl https://api.particle.io/v1/devices -H "Authorization: Bearer 38bb7b318cc6898c80317decb34525844bc9db55"'
- 'curl https://api.particle.io/v1/devices -d access_token=38bb7b318cc6898c80317decb34525844bc9db55'
- 'curl "https://api.particle.io/v1/devices/events?access_token=38bb7b318cc6898c80317decb34525844bc9db55"'
- 'curl "https://api.particle.io/v1/access_tokens/current?access_token=38bb7b318cc6898c80317decb34525844bc9db55"'
references:
- https://docs.particle.io/reference/cloud-apis/api/
validation:
type: Http
content:
request:
method: GET
url: https://api.particle.io/v1/user?access_token={{ TOKEN }}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
match_all_words: true
words: ['"username":']
- name: particle.io Access Token
id: kingfisher.particleio.2
pattern: |
(?x)
(?:access_token=|Authorization:\s*Bearer\s*)
\b
([a-zA-Z0-9]{40})
\b
[\s"\\]*https://api\.particle\.io/v1
min_entropy: 3.3
confidence: medium
examples:
- |
curl -H "Authorization: Bearer 38bb7b318cc6898c80317decb34525844bc9db55" \
https://api.particle.io/v1/devices
- |
curl -d access_token=38bb7b318cc6898c80317decb34525844bc9db55 \
https://api.particle.io/v1/devices
- 'curl -H "Authorization: Bearer 38bb7b318cc6898c80317decb34525844bc9db55" https://api.particle.io/v1/devices'
- 'curl -d access_token=38bb7b318cc6898c80317decb34525844bc9db55 https://api.particle.io/v1/devices'
references:
- https://docs.particle.io/reference/cloud-apis/api/
validation:
type: Http
content:
request:
method: GET
url: https://api.particle.io/v1/user?access_token={{ TOKEN }}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
match_all_words: true
words: ['"username":']

37
data/rules/pastebin.yml Normal file
View file

@ -0,0 +1,37 @@
rules:
- name: Pastebin API Key
id: kingfisher.pastebin.1
pattern: |
(?xi)
\b
pastebin
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-zA-Z0-9_]{32}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- pastebin_key=zwD26NeyMCvBsR9nxfaybLHD7TcLh22O
- pastebin_api_token=zwD26NeyMCvBsR9n_faybLHD7TcLh22O
validation:
type: Http
content:
request:
method: POST
url: https://pastebin.com/api/api_login.php
headers:
Content-Type: application/x-www-form-urlencoded
body: |
api_dev_key={{ TOKEN }}&api_user_name=dummy&api_user_password=dummy
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['invalid api_dev_key']
negative: true

56
data/rules/paypal.yml Normal file
View file

@ -0,0 +1,56 @@
rules:
- name: PayPal OAuth Client ID
id: kingfisher.paypal.1
pattern: |
(?xi)
paypal
(?:.|[\n\r]){0,8}?
(?:CLIENT|ID|USER)
(?:.|[\n\r]){0,16}?
\b
(
A[A-Z0-9_-]{79,99}
)
\b
min_entropy: 3.5
visible: false
examples:
- paypal_client_id=AZJ6y8Dpr1TYbqAIdhkPzyhjXoY6m8GplL7C3zZ3lPrkTIdhkPzyhjXo_Dx3
- name: PayPal OAuth Secret
id: kingfisher.paypal.2
pattern: |
(?xi)
paypal
(?:.|[\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[A-Z0-9_.-]{80,120}
)
\b
min_entropy: 3.5
examples:
- paypal_secret=EDe5J6y8Dpr1TYbqAIdhkPzyhjXoY6m8GplL7C3zZ3lPrkT1XlV6hYPSeJL5b1T1
validation:
type: Http
content:
request:
method: POST
url: https://api-m.paypal.com/v1/oauth2/token
headers:
Accept: application/json
Accept-Language: en_US
Content-Type: application/x-www-form-urlencoded
Authorization: |
Basic {{ CLIENTID | append: ':' | append: TOKEN | b64enc }}
body: grant_type=client_credentials
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
depends_on_rule:
- rule_id: kingfisher.paypal.1
variable: CLIENTID

View file

@ -0,0 +1,55 @@
rules:
- name: PlanetScale API Token
id: kingfisher.planetscale.1
pattern: |
(?x)
(?i)
\b
(
pscale_tkn_[a-z0-9-_]{43}
)
\b
min_entropy: 4
examples:
- pscale_tkn_abcdefghijklmnopqrstuvwxyZ1234567890_ABCDEF
validation:
type: Http
content:
request:
headers:
Accept: application/json
Authorization: '{{ USERNAME | append: ":" | append: TOKEN }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- '"id":'
- '"username":'
url: https://api.planetscale.com/v1/user
depends_on_rule:
- rule_id: kingfisher.planetscale.2
variable: USERNAME
- name: PlanetScale Username
id: kingfisher.planetscale.2
pattern: |
(?x)
(?i)
(?:pscale|planetscale)
(?:.|[\n\r]){0,16}?
(?:USER|ID|NAME)
(?:.|[\n\r]){0,16}?
\b
(
[a-z0-9]{12}
)
\b
min_entropy: 3.5
visible: false
examples:
- pscale_user = abcdefghijkl
- 'planetscale_id: hgtmrnzlv1t7'

37
data/rules/postman.yml Normal file
View file

@ -0,0 +1,37 @@
rules:
- name: Postman API Key
id: kingfisher.postman.1
pattern: |
(?x)
\b
(
PMAK-[A-Z0-9]{24}-[A-Z0-9]{34}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- PMAK-5dd543842789bd0036bf98c1-a5a9b8f1dfda8fbf18a4664ebe558b04ed
- PMAK-642a58a823faa300316566d1-6715a3a826ce5d5d62be8539d6ac357146
- PMAK-642a9b9084d6110029e75d7d-09efdcb872587f6f67696f02929647d9c6
- "// ('x-api-key', 'PMAK-629c73facbc064567cbf6970-f56e8b4cd0bb14d00962f17afc158dc2a2')"
references:
- https://learning.postman.com/docs/developer/intro-api/
- https://learning.postman.com/docs/developer/postman-api/authentication/
- https://learning.postman.com/docs/administration/managing-your-team/managing-api-keys/
validation:
type: Http
content:
request:
headers:
x-api-key: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200
- type: WordMatch
words:
- '"user":'
url: https://api.getpostman.com/me

56
data/rules/stripe.yml Normal file
View file

@ -0,0 +1,56 @@
rules:
- name: Stripe Publishable Key
id: kingfisher.stripe.1
pattern: |
(?xi)
(?:stripe|strp)
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
(
pk_live_
(?:[0-9A-Za-z]{6}){4,30}
)
min_entropy: 3.3
confidence: medium
categories: [api, key]
examples:
- stripe_pub_key = pk_live_HQS0j4H75XpthOW87eY1sXa2BYz3Ab
- name: Stripe Secret / Restricted Key
id: kingfisher.stripe.2
pattern: |
(?ix)
(?:^|[\s"'=])
(?:stripe|strp)
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
(
(?:
sk|rk
)_live_
(?:[0-9A-Za-z]{8}){3,25}
)
min_entropy: 3.3
confidence: medium
examples:
- stripe_secret_key = sk_live_f01c79xuuug7yodgzj5ws0h1x2kyvho3
- "strp_sec_key: rk_live_4haG9YwGkL2hXqTj5pSzo8FzB3uCwE7n"
validation:
type: Http
content:
request:
method: GET
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
url: https://api.stripe.com/v1/account
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
match_all_words: true
words: ['"object":"account"']

View file

@ -12,8 +12,8 @@ rules:
min_entropy: 3.0
confidence: medium
examples:
- tskey-secret-12345678-abcd
- tskey-api-abcdefg-123456789
- tskey-secret-12345678-abcdefghijkl
- tskey-api-abcdefg-1234567890123
references:
- https://tailscale.com/kb/1215/oauth-clients
validation:

View file

@ -25,6 +25,7 @@ use smallvec::SmallVec;
use tracing::debug;
use xxhash_rust::xxh3::xxh3_64;
use crate::rule_profiling::RuleTimer;
use crate::{
blob::{Blob, BlobId, BlobIdMap},
entropy::calculate_shannon_entropy,
@ -386,6 +387,8 @@ impl<'a> Matcher<'a> {
origin,
None,
redact,
&filename,
self.profiler.as_ref(),
);
}
// If tree-sitter produced base64-decoded matches, try them against all rules
@ -407,6 +410,8 @@ impl<'a> Matcher<'a> {
origin,
Some(ts_match.clone()),
redact,
&filename,
self.profiler.as_ref(),
);
}
}
@ -456,7 +461,21 @@ fn filter_match<'b>(
_origin: &OriginSet,
ts_match: Option<String>,
redact: bool,
filename: &str,
profiler: Option<&Arc<ConcurrentRuleProfiler>>,
) {
let mut timer = profiler.map(|p| {
RuleTimer::new(
p,
rule.id(),
rule.name(),
&rule.syntax.pattern,
filename,
)
});
let initial_len = matches.len();
// Use Cow to avoid unnecessary copying when ts_match is None
let byte_slice: Cow<[u8]> = match ts_match {
Some(ts_match_value) => Cow::Owned(ts_match_value.into_bytes()),
@ -515,6 +534,10 @@ fn filter_match<'b>(
});
previous_matches.push((rule_id, matching_input_offset_span));
}
if let Some(t) = timer.take() {
let new_count = (matches.len() - initial_len) as u64;
t.end(new_count > 0, new_count, 0);
}
}
fn get_language_and_queries(lang: &str) -> Option<(Language, FxHashMap<String, String>)> {
match lang.to_lowercase().as_str() {

View file

@ -75,7 +75,7 @@ pub async fn run_async_scan(
progress_enabled,
rules_db,
enable_profiling,
shared_profiler,
Arc::clone(&shared_profiler),
&matcher_stats,
)?;
@ -117,7 +117,15 @@ pub async fn run_async_scan(
// // Call cmd_report here
crate::reporter::run(global_args, Arc::clone(&datastore), args)
.context("Failed to run report command")?;
print_scan_summary(start_time, &datastore, global_args, args, rules_db, &matcher_stats);
print_scan_summary(
start_time,
&datastore,
global_args,
args,
rules_db,
&matcher_stats,
if enable_profiling { Some(shared_profiler.as_ref()) } else { None },
);
Ok(())
}

View file

@ -17,6 +17,7 @@ use crate::{
},
findings_store,
matcher::MatcherStats,
rule_profiling::ConcurrentRuleProfiler,
rules_database::RulesDatabase,
};
@ -42,6 +43,7 @@ pub fn print_scan_summary(
// inputs: &FilesystemEnumeratorResult,
rules_db: &RulesDatabase,
matcher_stats: &Mutex<MatcherStats>,
profiler: Option<&ConcurrentRuleProfiler>,
) {
// let duration = start_time.elapsed();
let ds = datastore.lock().unwrap();
@ -152,6 +154,47 @@ pub fn print_scan_summary(
humantime::format_duration(duration)
);
}
if args.rule_stats {
if let Some(prof) = profiler {
let stats = prof.generate_report();
if !stats.is_empty() {
// Calculate dynamic column widths
let name_w = stats.iter().map(|s| s.rule_name.len()).max().unwrap_or(4);
let id_w = stats.iter().map(|s| s.rule_id.len()).max().unwrap_or(2);
// Header
safe_println!("\n{:-^1$}", " Rule Performance Stats ", name_w + id_w + 47);
safe_println!(
"{: <name_w$} {: <id_w$} {: >8} {: >15} {: >15}",
"Rule",
"ID",
"Matches",
"Slowest",
"Average",
name_w = name_w,
id_w = id_w
);
safe_println!("{:-<width$}", "", width = name_w + id_w + 49);
// Rows
for rs in stats {
safe_println!(
"{: <name_w$} {: <id_w$} {: >8} {: >15?} {: >15?}",
rs.rule_name,
rs.rule_id,
rs.total_matches,
rs.slowest_match_time,
rs.average_match_time,
name_w = name_w,
id_w = id_w
);
}
}
}
}
debug!("\nAll Rules with Matches:");
debug!("=======================");
let max_rule_length = sorted_findings.iter().map(|(rule, _)| rule.len()).max().unwrap_or(0);