From 4f18541cb696e9942a22b0fe4e7629af32966206 Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Mon, 12 Jan 2026 22:50:05 -0800 Subject: [PATCH] preparing v1.74.0 --- .github/workflows/release.yml | 28 +- CHANGELOG.md | 2 + Cargo.toml | 2 +- data/rules/cursor.yml | 32 +++ data/rules/datadog.yml | 1 + data/rules/definednetworking.yml | 37 +++ data/rules/facebook.yml | 28 +- data/rules/filezilla.yml | 66 +++++ data/rules/github.yml | 54 +--- data/rules/harness.yml | 37 +++ data/rules/intra42.yml | 63 +++++ data/rules/klingai.yml | 59 ++++ data/rules/lark.yml | 141 ++++++++++ data/rules/mergify.yml | 39 +++ data/rules/microsoft_teams.yml | 36 +-- data/rules/naver.yml | 458 +++++++++++++++++++++++++++++++ data/rules/openrouter.yml | 14 +- data/rules/plaid.yml | 264 ++++++++++++++++++ data/rules/resend.yml | 43 +++ data/rules/retellai.yml | 38 +++ data/rules/ringcentral.yml | 115 ++++++++ data/rules/scalingo.yml | 1 + src/cli/global.rs | 2 +- src/update.rs | 2 +- 24 files changed, 1465 insertions(+), 97 deletions(-) create mode 100644 data/rules/cursor.yml create mode 100644 data/rules/definednetworking.yml create mode 100644 data/rules/filezilla.yml create mode 100644 data/rules/harness.yml create mode 100644 data/rules/intra42.yml create mode 100644 data/rules/klingai.yml create mode 100644 data/rules/lark.yml create mode 100644 data/rules/mergify.yml create mode 100644 data/rules/naver.yml create mode 100644 data/rules/plaid.yml create mode 100644 data/rules/resend.yml create mode 100644 data/rules/retellai.yml create mode 100644 data/rules/ringcentral.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6746936..e918d13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,14 @@ on: push: branches: - main + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: "Tag to publish (leave blank to use Cargo.toml version)" + required: false + type: string env: VCPKG_ROOT: C:\vcpkg VCPKG_DOWNLOADS: C:\vcpkg\downloads @@ -288,11 +296,20 @@ jobs: contents: write steps: - uses: actions/checkout@v4 - - name: Read version from Cargo.toml + - name: Determine tag id: version + shell: bash run: | - VERSION=$(grep -m1 '^version\s*=' Cargo.toml | cut -d '"' -f2) - echo "version=$VERSION" >> "$GITHUB_OUTPUT" + set -euo pipefail + if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then + TAG="${{ github.event.release.tag_name }}" + elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && -n "${{ github.event.inputs.tag }}" ]]; then + TAG="${{ github.event.inputs.tag }}" + else + VERSION=$(grep -m1 '^version\s*=' Cargo.toml | cut -d '"' -f2) + TAG="v${VERSION}" + fi + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - uses: actions/download-artifact@v4 with: path: target/release/kingfisher-* @@ -312,8 +329,9 @@ jobs: - name: Create release & upload assets uses: ncipollo/release-action@v1 with: - tag: v${{ steps.version.outputs.version }} - name: "Kingfisher v${{ steps.version.outputs.version }}" + tag: ${{ steps.version.outputs.tag }} + name: "Kingfisher ${{ steps.version.outputs.tag }}" bodyFile: .latest_changelog.md # ← only the most-recent entry + allowUpdates: true generateReleaseNotes: false artifacts: target/release/** diff --git a/CHANGELOG.md b/CHANGELOG.md index faa3f72..60f0b4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog All notable changes to this project will be documented in this file. +## [v1.74.0] + ## [v1.73.0] - Will now prefer git history findings when identical secrets appear in both current files and git history (dedup only). diff --git a/Cargo.toml b/Cargo.toml index f4c1cd1..78f87e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ publish = false [package] name = "kingfisher" -version = "1.73.0" +version = "1.74.0" description = "MongoDB's blazingly fast and accurate secret scanning and validation tool" edition.workspace = true rust-version.workspace = true diff --git a/data/rules/cursor.yml b/data/rules/cursor.yml new file mode 100644 index 0000000..2dacb35 --- /dev/null +++ b/data/rules/cursor.yml @@ -0,0 +1,32 @@ +rules: + - name: Cursor Integrations (User) API Key + id: kingfisher.cursor.1 + pattern: | + (?xi) + \b + ( + key_ + [0-9a-f]{64} + ) + \b + min_entropy: 3.8 + confidence: medium + examples: + - key_8c5a7657fc397e114def1b51dd520410ad50ece61e30b64261ff369ab275ef29 + - key_86aed0092d14dd6aae4e8ad107d52f760c5efa4bc3753730ca6983babc2b1072 + - key_d9ba91e29d81f1bf2085b55b06f5ac43885749005cff565bee7f406ca2e2f3f9 + references: + - https://cursor.com/docs/cloud-agent/api/endpoints + validation: + type: Http + content: + request: + method: GET + url: https://api.cursor.com/v0/me + headers: + Accept: application/json + Authorization: 'Basic {{ TOKEN | b64enc }}' + response_matcher: + - report_response: true + - type: WordMatch + words: ['"userEmail"'] diff --git a/data/rules/datadog.yml b/data/rules/datadog.yml index c2dd478..eb83d04 100644 --- a/data/rules/datadog.yml +++ b/data/rules/datadog.yml @@ -45,6 +45,7 @@ rules: ( [A-Z0-9]{32} ) + \b pattern_requirements: min_digits: 3 min_entropy: 3.3 diff --git a/data/rules/definednetworking.yml b/data/rules/definednetworking.yml new file mode 100644 index 0000000..5f333c7 --- /dev/null +++ b/data/rules/definednetworking.yml @@ -0,0 +1,37 @@ +rules: + - name: Defined Networking API Token + id: kingfisher.definednetworking.1 + pattern: | + (?x) + \b + ( + dnkey- + [A-Z0-9]{26} + - + [A-Z0-9]{52} + ) + \b + min_entropy: 3.6 + confidence: medium + examples: + - 'defined_api_token="dnkey-AHBDSNIG5ATR4LPUX4XTEVXEP4-ACW2JQ45HAWA2XA6FIJNSNBRY2Q4WMSCNNIFSL6VRZQYFZKI2VHA"' + references: + - https://docs.defined.net/api/defined-networking-api/ + - https://docs.defined.net/api/networks-list/ + - https://docs.defined.net/guides/rotating-api-keys/ + validation: + type: Http + content: + request: + method: GET + url: https://api.defined.net/v1/networks + headers: + Accept: application/json + Authorization: "Bearer {{ TOKEN }}" + response_matcher: + - report_response: true + # 200 = valid + authorized + # 403 = valid token but missing required scope (still proves the token is real) + - type: StatusMatch + status: [200, 403] + diff --git a/data/rules/facebook.yml b/data/rules/facebook.yml index 841cda9..7fca27a 100644 --- a/data/rules/facebook.yml +++ b/data/rules/facebook.yml @@ -2,13 +2,13 @@ rules: - name: Facebook App ID id: kingfisher.facebook.1 pattern: | - (?xi) - \b + (?xi) + \b (?:facebook|fb) - (?:.|[\n\r]){0,8}? - (?:APP|APPLICATION) - (?:.|[\n\r]){0,16}? - \b + (?:.|[\n\r]){0,32}? + (?:APP|APPLICATION|CLIENT) + (?:.|[\n\r]){0,32}? + \b ( \d{15} ) @@ -27,13 +27,17 @@ rules: id: kingfisher.facebook.2 pattern: | (?xi) - \b (?: facebook | fb ) - .? - (?: api | app | application | client | consumer | customer | secret | key ) - .? - (?: key | oauth | sec | secret )? + \b + (?: facebook | fb ) + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|AUTH) + (?:.|[\n\r]){0,48}? .{0,2} \s{0,20} .{0,2} \s{0,20} .{0,2} - \b ([a-z0-9]{32}) \b + \b + ( + [a-z0-9]{32} + ) + \b examples: - ' # config.facebook.key = "34cebc81c056a21bc66e212f947d73ec"' - " var fbApiKey = '0278fc1adf6dc1d82a156f306ce2c5cc';" diff --git a/data/rules/filezilla.yml b/data/rules/filezilla.yml new file mode 100644 index 0000000..45beb3c --- /dev/null +++ b/data/rules/filezilla.yml @@ -0,0 +1,66 @@ +rules: + - name: FileZilla base64 encoded password + id: kingfisher.filezilla.1 + pattern: | + (?xis) + ]*> + (?:.|[\n\r]){0,5000}? + <(?:RecentServers|Servers)\b[^>]*> + (?:.|[\n\r]){0,5000}? + ]*> + (?:.|[\n\r]){0,3000}? + ]*\bencoding\s*=\s*"(?:base64|radix64)"[^>]*> + \s* + ( + [A-Z0-9+/]{8,}={0,2} + ) + \s* + + min_entropy: 3.2 + confidence: medium + pattern_requirements: + ignore_if_contains: + - "ZXhhbXBsZQ==" # "example" (base64) + - "cGFzc3dvcmQ=" # "password" (base64) + - "Y2hhbmdlbWU=" # "changeme" (base64) + examples: + - 'VGhpc0lzQVRlc3RQYXNzd29yZA==' + - 'NjllNWU5ZWMwZDU0MmU5Y2QwOTY4MWM5YzZhMDdkYWVmNjg3OWE3MDMzM2Q4MWJmCg==' + references: + - https://forum.filezilla-project.org/viewtopic.php?style=246&t=38820 + - https://forum.filezilla-project.org/viewtopic.php?p=133138 + - https://forum.filezilla-project.org/viewtopic.php?t=24758 + + - name: FileZilla stored password (Pass plaintext) + id: kingfisher.filezilla.2 + pattern: | + (?xis) + ]*> + (?:.|[\n\r]){0,5000}? + <(?:RecentServers|Servers)\b[^>]*> + (?:.|[\n\r]){0,5000}? + ]*> + (?:.|[\n\r]){0,3000}? + ]*\bencoding\s*=)[^>]*> + \s* + ( + [^<\r\n]{4,128} + ) + \s* + + min_entropy: 2.8 + confidence: medium + pattern_requirements: + ignore_if_contains: + - example + - Example + - password + - Password + - changeme + - ChangeMe + examples: + - "ExamplePas123" + - "superS3cret!" + references: + - https://stackoverflow.com/questions/29790136/filezilla-plain-text-password + - https://forum.filezilla-project.org/viewtopic.php?t=24758 diff --git a/data/rules/github.yml b/data/rules/github.yml index 9c04297..72ec078 100644 --- a/data/rules/github.yml +++ b/data/rules/github.yml @@ -198,56 +198,4 @@ rules: words: - '"login"' - '"id"' - - name: GitHub Client ID - id: kingfisher.github.7 - pattern: | - (?xi) - (?:github) - .? - (?: 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]{20}) \b - visible: false - examples: - - | - GITHUB_CLIENT_ID=ac58d6da7d7a84c039b7 - GITHUB_SECRET=37d02377a3e9d849e18704c3ec883f9c5787d857 - - name: GitHub Secret Key - id: kingfisher.github.8 - pattern: | - (?xi) - github - (?:.|[\n\r]){0,32}? - (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) - (?:.|[\n\r]){0,32}? - \b - ( - [a-z0-9]{40} - ) - \b - depends_on_rule: - - rule_id: "kingfisher.github.5" - variable: GITHUB_CLIENT_ID - validation: - type: Http - content: - request: - method: POST - url: "https://github.com/login/oauth/access_token" - headers: - Accept: "application/json" - Content-Type: "application/json" - body: '{"client_id":"{{GITHUB_CLIENT_ID}}","client_secret":"{{TOKEN}}","code":"invalid_code"}' - response_matcher: - - report_response: true - - type: StatusMatch - status: [200] - - type: WordMatch - words: - - '"error":"bad_verification_code"' - examples: - - | - GITHUB_CLIENT_ID=ac58d6da7d7a84c039b7 - GITHUB_SECRET=37d02377a3e9d849e18704c3ec883f9c5787d857 + diff --git a/data/rules/harness.yml b/data/rules/harness.yml new file mode 100644 index 0000000..aeb81d0 --- /dev/null +++ b/data/rules/harness.yml @@ -0,0 +1,37 @@ +rules: + - name: Harness Personal Access Token (PAT) + id: kingfisher.harness.pat.1 + pattern: | + (?xi) + \b + ( + pat\. + [A-Z0-9]{22} + \. + [0-9a-f]{24} + \. + [A-Z0-9]{20} + ) + \b + min_entropy: 3.4 + confidence: medium + examples: + - 'HARNESS_TOKEN="pat.AbCdEfGhIjKlMnOpQrStUv.0123abcd4567ef890123abcd.ZyXwVuTsRqPoNmLkJiHg"' + references: + - https://developer.harness.io/docs/platform/automation/api/api-quickstart/ + - https://apidocs.harness.io/ + validation: + type: Http + content: + request: + method: GET + url: https://app.harness.io/ng/api/apikey/aggregate + headers: + Accept: application/json + x-api-key: "{{ TOKEN }}" + response_matcher: + # Valid token + authorized OR valid token but missing params/perms + - type: StatusMatch + status: [200, 400, 403] + negative: true + - type: JsonValid diff --git a/data/rules/intra42.yml b/data/rules/intra42.yml new file mode 100644 index 0000000..7e0a1fd --- /dev/null +++ b/data/rules/intra42.yml @@ -0,0 +1,63 @@ +rules: + - name: Intra42 Client ID + id: kingfisher.intra42.1 + visible: false + pattern: | + (?xi) + \b + ( + u-s4t2(?:ud|af)-[a-f0-9]{64} + ) + \b + min_entropy: 3.0 + confidence: medium + examples: + - 'INTRA42_CLIENT_ID="u-s4t2ud-33ad3d923534cae0bf765b20ac23831a4e35937298f21062a72db03e99de65b7"' + references: + - https://api.intra.42.fr/apidoc/guides/getting_started + + - name: Intra42 Client Secret (s-s4t2ud/af) + id: kingfisher.intra42.2 + pattern: | + (?xi) + \b + ( + s-s4t2(?:ud|af)-[a-f0-9]{64} + ) + \b + min_entropy: 3.6 + confidence: medium + pattern_requirements: + min_digits: 3 + min_lowercase: 2 + examples: + - 'INTRA42_CLIENT_SECRET="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"' + references: + - https://api.intra.42.fr/apidoc/guides/getting_started + - https://api.intra.42.fr/apidoc/guides/web_application_flow + - https://api.intra.42.fr/apidoc/guides/specification + + depends_on_rule: + - rule_id: kingfisher.intra42.1 + variable: CLIENT_ID + + validation: + type: Http + content: + request: + method: POST + url: https://api.intra.42.fr/oauth/token + headers: + Accept: application/json + Content-Type: application/x-www-form-urlencoded + body: > + grant_type=client_credentials&client_id={{ CLIENT_ID | url_encode }}&client_secret={{ TOKEN | url_encode }} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + match_all_words: false + words: + - '"access_token"' diff --git a/data/rules/klingai.yml b/data/rules/klingai.yml new file mode 100644 index 0000000..9b10aab --- /dev/null +++ b/data/rules/klingai.yml @@ -0,0 +1,59 @@ +rules: + - name: Kling AI Secret Key + id: kingfisher.klingai.1 + pattern: | + (?xi) + \b + kling + (?:.|[\n\r]){0,120}? + \b + (?:access[\s_-]*key|accesskeyid|ak) + (?:.|[\n\r]){0,64}? + \b + (?P + [A-Za-z0-9]{32} + ) + \b + (?:.|[\n\r]){0,120}? + \b + (secret[\s_-]*key|accesskeysecret|sk) + \b + (?:.|[\n\r]){0,64}? + \b + (?P + [A-Za-z0-9]{32} + ) + \b + min_entropy: 2.0 + confidence: medium + examples: + - 'KLING_SECRET_KEY="0123456789abcdef0123456789abcdef"' + references: + - https://docs.qingque.cn/d/home/eZQDkhg4h2Qg8SEVSUTBdzYeY + - https://community.n8n.io/t/authorization-kling-api/112647 + validation: + type: Http + content: + request: + method: GET + + # SINGLE LINE URL (no folded block) so you don't accidentally end up with %20 + url: 'https://api-singapore.klingai.com/account/costs?start_time={{ "" | unix_timestamp | minus: 3600 | times: 1000 }}&end_time={{ "" | unix_timestamp | times: 1000 }}' + + headers: + Content-Type: application/json + Accept: application/json + + # SINGLE LINE Authorization header (no YAML "|" block) so it won't be dropped. + # JWT matches the Python example: HS256 header + {iss,exp,nbf} payload signed with SK. + + Authorization: '{%- assign header = "HS256" | jwt_header -%}{%- assign now = "" | unix_timestamp -%}{%- assign exp = now | plus: 1800 -%}{%- assign nbf = now | minus: 5 -%}{%- assign payload_json = ''{"iss":"'' | append: AKID | append: ''","exp":'' | append: exp | append: '',"nbf":'' | append: nbf | append: ''}'' -%}{%- assign payload = payload_json | b64url_enc -%}{%- assign signing_input = header | append: "." | append: payload -%}{%- assign sig_b64 = signing_input | hmac_sha256: SECRET -%}{%- assign sig = sig_b64 | replace: "+", "-" | replace: "/", "_" | replace: "=", "" -%}Bearer {{ header }}.{{ payload }}.{{ sig }}' + + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: StatusMatch + status: [401, 403, 500] + negative: true + - type: JsonValid \ No newline at end of file diff --git a/data/rules/lark.yml b/data/rules/lark.yml new file mode 100644 index 0000000..7c0a25a --- /dev/null +++ b/data/rules/lark.yml @@ -0,0 +1,141 @@ +rules: + - name: LarkSuite Tenant Access Token + id: kingfisher.lark.1 + pattern: | + (?xi) + (?:lark|larksuite) + (?:.|[\n\r]){0,64}? + (?:tenant|access[_ -]?token)? + (?:.|[\n\r]){0,32}? + \b + ( + t-[A-Z0-9_.]{14,50} + ) + \b + min_entropy: 3.2 + confidence: medium + examples: + - larksuite_tenant_access_token="t-AbCdEfGhIjKlMnOpQrStUvWxYz_1234" + references: + - https://open.larksuite.com/document/home/introduction-to-scope-and-authorization/access-credentials + - https://open.larksuite.com/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/chat/list + - https://open.larksuite.com/document/faq/trouble-shooting/how-to-fix-the-99991672-error + - https://open.larksuite.com/document/ukTMukTMukTM/ugjM14COyUjL4ITN + validation: + type: Http + content: + request: + method: GET + url: https://open.larksuite.com/open-apis/im/v1/chats?page_size=1 + headers: + Accept: application/json + Authorization: "Bearer {{ TOKEN }}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 400] + - type: JsonValid + # Verified if API says success (code=0) OR "No permission" (code=99991672), + # which still indicates the token is recognized/valid but missing scopes. + - type: WordMatch + match_all_words: false + words: + - '"code":0' + - '"code": 0' + - '"code":99991672' + - '"code": 99991672' + + - name: LarkSuite User Access Token + id: kingfisher.lark.2 + pattern: | + (?xi) + (?:lark|larksuite) + (?:.|[\n\r]){0,64}? + (?:user|access[_ -]?token|oauth)? + (?:.|[\n\r]){0,32}? + \b + ( + u-[A-Z0-9_.]{14,50} + ) + \b + min_entropy: 3.2 + confidence: medium + examples: + - larksuite_user_access_token="u-ZyXwVuTsRqPoNmLkJiHgFeDcBa_5678" + references: + - https://open.larksuite.com/document/home/introduction-to-scope-and-authorization/access-credentials + - https://open.larksuite.com/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/chat/list + - https://open.larksuite.com/document/faq/trouble-shooting/how-to-fix-the-99991672-error + - https://open.larksuite.com/document/ukTMukTMukTM/ugjM14COyUjL4ITN + validation: + type: Http + content: + request: + method: GET + url: https://open.larksuite.com/open-apis/im/v1/chats?page_size=1 + headers: + Accept: application/json + Authorization: "Bearer {{ TOKEN }}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 400] + - type: JsonValid + - type: WordMatch + match_all_words: false + words: + - '"code":0' + - '"code": 0' + - '"code":99991672' + - '"code": 99991672' + + - name: LarkSuite App Access Token + id: kingfisher.lark.3 + pattern: | + (?xi) + (?:lark|larksuite) + (?:.|[\n\r]){0,64}? + (?:app|access[_ -]?token)? + (?:.|[\n\r]){0,32}? + \b + ( + a-[A-Z0-9_.]{14,50} + ) + \b + min_entropy: 3.2 + confidence: medium + examples: + - larksuite_app_access_token="a-QwBsTyUiOpBsDfGhJnLxYcVbN_9012" + references: + - https://open.larksuite.com/document/home/introduction-to-scope-and-authorization/access-credentials + - https://open.larksuite.com/document/server-docs/getting-started/api-access-token/auth-v3/tenant_access_token + - https://open.larksuite.com/document/server-docs/getting-started/api-access-token/auth-v3/app_access_token + validation: + type: Http + content: + request: + method: POST + url: https://open.larksuite.com/open-apis/auth/v3/tenant_access_token + headers: + Content-Type: application/json + Accept: application/json + # This endpoint expects app_access_token + tenant_key for store apps. + # We intentionally send a random (almost-certainly invalid) tenant_key. + # If the app_access_token is invalid, platforms commonly return code 99991664 ("invalid app token"). + body: | + { + "app_access_token": "{{ TOKEN }}", + "tenant_key": "{{ '' | random_string: 16 }}" + } + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 400] + - type: JsonValid + # Fail verification if provider explicitly says the app token is invalid. + - type: WordMatch + negative: true + match_all_words: false + words: + - '"code":99991664' + - '"code": 99991664' diff --git a/data/rules/mergify.yml b/data/rules/mergify.yml new file mode 100644 index 0000000..85673d0 --- /dev/null +++ b/data/rules/mergify.yml @@ -0,0 +1,39 @@ +rules: + - name: Mergify Application API Key + id: kingfisher.mergify.1 + pattern: | + (?x) + \b + ( + mergify_application_key_ + [A-Za-z0-9_-]{40,200} + ) + \b + min_entropy: 3.2 + confidence: high + examples: + - mergify_application_key_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4Y5z6_-AbCdEfGhIj + - mergify_application_key_ZxYwVuTsRqPoNmLkJiHgFeDcBa9876543210_-__aBcDeFgHiJkLmNoPqRsTuVwXyZ + references: + - https://docs.mergify.com/api/ + - https://docs.mergify.com/api-usage/ + validation: + type: Http + content: + request: + method: GET + url: https://api.mergify.com/v1/application + headers: + Accept: application/json + Authorization: "Bearer {{ TOKEN }}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: HeaderMatch + header: content-type + expected: ["application/json"] + - type: JsonValid + - type: WordMatch + words: ['"id"', '"name"', '"scope"'] + match_all_words: true diff --git a/data/rules/microsoft_teams.yml b/data/rules/microsoft_teams.yml index 1f7ac49..98c9068 100644 --- a/data/rules/microsoft_teams.yml +++ b/data/rules/microsoft_teams.yml @@ -5,29 +5,29 @@ rules: (?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} + .*\.office\.com/webhook/ + [0-9a-z]{8}- + [0-9a-z]{4}- + [0-9a-z]{4}- + [0-9a-z]{4}- + [0-9a-z]{12} @ - [0-9a-f]{8}- - [0-9a-f]{4}- - [0-9a-f]{4}- - [0-9a-f]{4}- - [0-9a-f]{12} + [0-9a-z]{8}- + [0-9a-z]{4}- + [0-9a-z]{4}- + [0-9a-z]{4}- + [0-9a-z]{12} /IncomingWebhook/ - [0-9a-f]{32} + [0-9a-z]{32} / - [0-9a-f]{8}- - [0-9a-f]{4}- - [0-9a-f]{4}- - [0-9a-f]{4}- - [0-9a-f]{12} + [0-9a-z]{8}- + [0-9a-z]{4}- + [0-9a-z]{4}- + [0-9a-z]{4}- + [0-9a-z]{12} ) pattern_requirements: - min_digits: 2 + min_digits: 8 min_entropy: 3.3 confidence: medium examples: diff --git a/data/rules/naver.yml b/data/rules/naver.yml new file mode 100644 index 0000000..e3f1b33 --- /dev/null +++ b/data/rules/naver.yml @@ -0,0 +1,458 @@ +rules: + # -------------------------- + # GOV — long‑lived credentials + # -------------------------- + - name: navercloud_gov_access_key + id: kingfisher.navercloud.gov_access_key + pattern: | + (?xi) + \b + (?:gov-ntruss\.com|gov-ncloud\.com|api-gov\.ncloud-docs\.com|guide-gov\.ncloud-docs\.com) + (?:.|[\n\r]){0,250}? + (?:x-ncp-iam-access-key|NCLOUD_ACCESS_KEY(?:_ID)?|NCP_ACCESS_KEY(?:_ID)?|access[_-]?key(?:_id)?|accessKey(?:Id)?) + (?:.|[\n\r]){0,40}? + ( + [A-Z0-9]{20} + ) + \b + pattern_requirements: + min_digits: 2 + min_uppercase: 2 + ignore_if_contains: + - secure token service + - temporary credential + - temporary credentials + min_entropy: 3.0 + confidence: medium + examples: + - 'https://ncloud.apigw.gov-ntruss.com/server/v2/getRegionList x-ncp-iam-access-key: D78BB444D6D3C84CA38D' + references: + - https://api-gov.ncloud-docs.com/docs/common-ncpapi-ncpapi + - https://api-gov.ncloud-docs.com/docs/compute-server + - https://api-gov.ncloud-docs.com/docs/compute-server-common-getregionlist + validation: + type: Http + content: + request: + method: GET + url: https://ncloud.apigw.gov-ntruss.com/server/v2/getRegionList + headers: + x-ncp-apigw-timestamp: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%}{{ ts }} + x-ncp-iam-access-key: "{{ TOKEN }}" + x-ncp-apigw-signature-v2: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%} + {%- assign uri = "/server/v2/getRegionList" -%} + {%- assign msg = "GET " | append: uri | append: "\n" | append: ts | append: "\n" | append: TOKEN -%} + {{ msg | hmac_sha256: SECRET }} + response_matcher: + - type: StatusMatch + status: [200] + - type: XmlValid + - type: WordMatch + match_all_words: true + words: + - "0" + - "success" + depends_on_rule: + - rule_id: kingfisher.navercloud.gov_access_key_secret + variable: SECRET + + - name: navercloud_gov_access_key_secret + id: kingfisher.navercloud.gov_access_key_secret + pattern: | + (?xi) + \b + (?:gov-ntruss\.com|gov-ncloud\.com|api-gov\.ncloud-docs\.com|guide-gov\.ncloud-docs\.com) + (?:.|[\n\r]){0,250}? + (?:NCLOUD_SECRET_KEY|NCP_SECRET_KEY|secret[_-]?key|secretKey|secret_access_key|SecretAccessKey) + (?:.|[\n\r]){0,40}? + ( + [A-Z0-9/+=]{40} + ) + \b + pattern_requirements: + min_digits: 2 + min_uppercase: 1 + min_lowercase: 1 + ignore_if_contains: + - secure token service + - temporary credential + - temporary credentials + min_entropy: 3.5 + confidence: medium + examples: + - 'gov-ntruss.com secretKey="b0qP0cQ9ZfS0lq8Gm9QyGqgT2aX9bVd1eKp3uX1/"' + references: + - https://api-gov.ncloud-docs.com/docs/common-ncpapi-ncpapi + - https://api-gov.ncloud-docs.com/docs/compute-server-common-getregionlist + validation: + type: Http + content: + request: + method: GET + url: https://ncloud.apigw.gov-ntruss.com/server/v2/getRegionList + headers: + x-ncp-apigw-timestamp: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%}{{ ts }} + x-ncp-iam-access-key: "{{ AKID }}" + x-ncp-apigw-signature-v2: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%} + {%- assign uri = "/server/v2/getRegionList" -%} + {%- assign msg = "GET " | append: uri | append: "\n" | append: ts | append: "\n" | append: AKID -%} + {{ msg | hmac_sha256: TOKEN }} + response_matcher: + - type: StatusMatch + status: [200] + - type: XmlValid + - type: WordMatch + match_all_words: true + words: + - "0" + - "success" + depends_on_rule: + - rule_id: kingfisher.navercloud.gov_access_key + variable: AKID + + # -------------------------- + # GOV — STS (temporary credentials) + # -------------------------- + - name: navercloud_gov_sts + id: kingfisher.navercloud.gov_sts + pattern: | + (?xi) + \b + (?:gov-ntruss\.com|gov-ncloud\.com|api-gov\.ncloud-docs\.com|guide-gov\.ncloud-docs\.com) + (?:.|[\n\r]){0,250}? + (?:secure\s*token\s*service|\bsts\b|temporary\s*credentials?) + (?:.|[\n\r]){0,120}? + (?:x-ncp-iam-access-key|access[_-]?key(?:_id)?|accessKey(?:Id)?|AccessKeyId|NCLOUD_ACCESS_KEY(?:_ID)?) + (?:.|[\n\r]){0,40}? + ( + [A-Z0-9]{20} + ) + \b + pattern_requirements: + min_digits: 2 + min_uppercase: 2 + min_entropy: 3.0 + confidence: medium + examples: + - 'Secure Token Service (STS) AccessKeyId: D78BB444D6D3C84CA38D gov-ntruss.com' + references: + - https://api-gov.ncloud-docs.com/docs/management-sts + - https://api-gov.ncloud-docs.com/docs/common-ncpapi-ncpapi + - https://api-gov.ncloud-docs.com/docs/compute-server-common-getregionlist + validation: + type: Http + content: + request: + method: GET + url: https://ncloud.apigw.gov-ntruss.com/server/v2/getRegionList + headers: + x-ncp-apigw-timestamp: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%}{{ ts }} + x-ncp-iam-access-key: "{{ TOKEN }}" + x-ncp-apigw-signature-v2: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%} + {%- assign uri = "/server/v2/getRegionList" -%} + {%- assign msg = "GET " | append: uri | append: "\n" | append: ts | append: "\n" | append: TOKEN -%} + {{ msg | hmac_sha256: SECRET }} + response_matcher: + - type: StatusMatch + status: [200] + - type: XmlValid + - type: WordMatch + match_all_words: true + words: + - "0" + - "success" + depends_on_rule: + - rule_id: kingfisher.navercloud.gov_sts_secret + variable: SECRET + + - name: navercloud_gov_sts_secret + id: kingfisher.navercloud.gov_sts_secret + pattern: | + (?xi) + \b + (?:gov-ntruss\.com|gov-ncloud\.com|api-gov\.ncloud-docs\.com|guide-gov\.ncloud-docs\.com) + (?:.|[\n\r]){0,250}? + (?:secure\s*token\s*service|\bsts\b|temporary\s*credentials?) + (?:.|[\n\r]){0,120}? + (?:secret[_-]?key|secretKey|SecretAccessKey|NCLOUD_SECRET_KEY) + (?:.|[\n\r]){0,40}? + ( + [A-Z0-9/+=]{40} + ) + \b + pattern_requirements: + min_digits: 2 + min_uppercase: 1 + min_lowercase: 1 + min_entropy: 3.5 + confidence: medium + examples: + - 'STS SecretAccessKey: b0qP0cQ9ZfS0lq8Gm9QyGqgT2aX9bVd1eKp3uX1/ gov-ncloud.com' + references: + - https://api-gov.ncloud-docs.com/docs/management-sts + - https://api-gov.ncloud-docs.com/docs/common-ncpapi-ncpapi + - https://api-gov.ncloud-docs.com/docs/compute-server-common-getregionlist + validation: + type: Http + content: + request: + method: GET + url: https://ncloud.apigw.gov-ntruss.com/server/v2/getRegionList + headers: + x-ncp-apigw-timestamp: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%}{{ ts }} + x-ncp-iam-access-key: "{{ AKID }}" + x-ncp-apigw-signature-v2: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%} + {%- assign uri = "/server/v2/getRegionList" -%} + {%- assign msg = "GET " | append: uri | append: "\n" | append: ts | append: "\n" | append: AKID -%} + {{ msg | hmac_sha256: TOKEN }} + response_matcher: + - type: StatusMatch + status: [200] + - type: XmlValid + - type: WordMatch + match_all_words: true + words: + - "0" + - "success" + depends_on_rule: + - rule_id: kingfisher.navercloud.gov_sts + variable: AKID + + # -------------------------- + # PUB — long‑lived credentials + # -------------------------- + - name: navercloud_pub_access_key + id: kingfisher.navercloud.pub_access_key + pattern: | + (?xi) + \b + (?:\.apigw\.ntruss\.com|api\.ncloud-docs\.com|guide\.ncloud-docs\.com|www\.ncloud\.com|x-ncp-iam-access-key|NCLOUD_ACCESS_KEY(?:_ID)?|NCP_ACCESS_KEY(?:_ID)?) + (?:.|[\n\r]){0,200}? + (?:x-ncp-iam-access-key|access[_-]?key(?:_id)?|accessKey(?:Id)?|AccessKeyId|NCLOUD_ACCESS_KEY(?:_ID)?|NCP_ACCESS_KEY(?:_ID)?) + (?:.|[\n\r]){0,40}? + ( + [A-Z0-9]{20} + ) + \b + (?:.|[\n\r]){0,80}? + pattern_requirements: + min_digits: 2 + min_uppercase: 2 + ignore_if_contains: + - gov-ntruss + - gov-ncloud + - api-gov.ncloud-docs.com + min_entropy: 3.0 + confidence: medium + examples: + - 'https://ncloud.apigw.ntruss.com/server/v2/getRegionList x-ncp-iam-access-key: D78BB444D6D3C84CA38D' + references: + - https://api-gov.ncloud-docs.com/docs/common-ncpapi-ncpapi + validation: + type: Http + content: + request: + method: GET + url: https://ncloud.apigw.ntruss.com/server/v2/getRegionList + headers: + x-ncp-apigw-timestamp: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%}{{ ts }} + x-ncp-iam-access-key: "{{ TOKEN }}" + x-ncp-apigw-signature-v2: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%} + {%- assign uri = "/server/v2/getRegionList" -%} + {%- assign msg = "GET " | append: uri | append: "\n" | append: ts | append: "\n" | append: TOKEN -%} + {{ msg | hmac_sha256: SECRET }} + response_matcher: + - type: StatusMatch + status: [200] + - type: XmlValid + - type: WordMatch + match_all_words: true + words: + - "0" + - "success" + depends_on_rule: + - rule_id: kingfisher.navercloud.pub_access_key_secret + variable: SECRET + + - name: navercloud_pub_access_key_secret + id: kingfisher.navercloud.pub_access_key_secret + pattern: | + (?xi) + \b + (?:\.apigw\.ntruss\.com|api\.ncloud-docs\.com|guide\.ncloud-docs\.com|www\.ncloud\.com|NCLOUD_SECRET_KEY|NCP_SECRET_KEY|secret[_-]?key|secretKey) + (?:.|[\n\r]){0,200}? + (?:NCLOUD_SECRET_KEY|NCP_SECRET_KEY|secret[_-]?key|secretKey|secret_access_key|SecretAccessKey) + (?:.|[\n\r]){0,40}? + ( + [A-Z0-9/+=]{40} + ) + \b + pattern_requirements: + min_digits: 2 + min_uppercase: 1 + min_lowercase: 1 + ignore_if_contains: + - gov-ntruss + - gov-ncloud + - api-gov.ncloud-docs.com + min_entropy: 3.5 + confidence: medium + examples: + - 'secretKey="b0qP0cQ9ZfS0lq8Gm9QyGqgT2aX9bVd1eKp3uX1/" https://sens.apigw.ntruss.com' + references: + - https://api-gov.ncloud-docs.com/docs/common-ncpapi-ncpapi + validation: + type: Http + content: + request: + method: GET + url: https://ncloud.apigw.ntruss.com/server/v2/getRegionList + headers: + x-ncp-apigw-timestamp: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%}{{ ts }} + x-ncp-iam-access-key: "{{ AKID }}" + x-ncp-apigw-signature-v2: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%} + {%- assign uri = "/server/v2/getRegionList" -%} + {%- assign msg = "GET " | append: uri | append: "\n" | append: ts | append: "\n" | append: AKID -%} + {{ msg | hmac_sha256: TOKEN }} + response_matcher: + - type: StatusMatch + status: [200] + - type: XmlValid + - type: WordMatch + match_all_words: true + words: + - "0" + - "success" + depends_on_rule: + - rule_id: kingfisher.navercloud.pub_access_key + variable: AKID + + # -------------------------- + # PUB — STS (temporary credentials) + # -------------------------- + - name: navercloud_pub_sts + id: kingfisher.navercloud.pub_sts + pattern: | + (?xi) + \b + (?:\.apigw\.ntruss\.com|api\.ncloud-docs\.com|guide\.ncloud-docs\.com|www\.ncloud\.com|ncloud\.apigw\.ntruss\.com) + (?:.|[\n\r]){0,250}? + (?:secure\s*token\s*service|\bsts\b|temporary\s*credentials?) + (?:.|[\n\r]){0,120}? + (?:x-ncp-iam-access-key|access[_-]?key(?:_id)?|accessKey(?:Id)?|AccessKeyId|NCLOUD_ACCESS_KEY(?:_ID)?) + (?:.|[\n\r]){0,40}? + ( + [A-Z0-9]{20} + ) + \b + pattern_requirements: + min_digits: 2 + min_uppercase: 2 + ignore_if_contains: + - gov-ntruss + - gov-ncloud + - api-gov.ncloud-docs.com + min_entropy: 3.0 + confidence: medium + examples: + - 'Secure Token Service (STS) AccessKeyId: D78BB444D6D3C84CA38D https://api.ncloud-docs.com' + references: + - https://api.ncloud-docs.com/docs/en/management-sts + - https://api-gov.ncloud-docs.com/docs/common-ncpapi-ncpapi + validation: + type: Http + content: + request: + method: GET + url: https://ncloud.apigw.ntruss.com/server/v2/getRegionList + headers: + x-ncp-apigw-timestamp: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%}{{ ts }} + x-ncp-iam-access-key: "{{ TOKEN }}" + x-ncp-apigw-signature-v2: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%} + {%- assign uri = "/server/v2/getRegionList" -%} + {%- assign msg = "GET " | append: uri | append: "\n" | append: ts | append: "\n" | append: TOKEN -%} + {{ msg | hmac_sha256: SECRET }} + response_matcher: + - type: StatusMatch + status: [200] + - type: XmlValid + - type: WordMatch + match_all_words: true + words: + - "0" + - "success" + depends_on_rule: + - rule_id: kingfisher.navercloud.pub_sts_secret + variable: SECRET + + - name: navercloud_pub_sts_secret + id: kingfisher.navercloud.pub_sts_secret + pattern: | + (?xi) + \b + (?:\.apigw\.ntruss\.com|api\.ncloud-docs\.com|guide\.ncloud-docs\.com|www\.ncloud\.com|ncloud\.apigw\.ntruss\.com) + (?:.|[\n\r]){0,250}? + (?:secure\s*token\s*service|\bsts\b|temporary\s*credentials?) + (?:.|[\n\r]){0,120}? + (?:secret[_-]?key|secretKey|SecretAccessKey|NCLOUD_SECRET_KEY) + (?:.|[\n\r]){0,40}? + ( + [A-Z0-9/+=]{40} + ) + \b + pattern_requirements: + min_digits: 2 + min_uppercase: 1 + min_lowercase: 1 + ignore_if_contains: + - gov-ntruss + - gov-ncloud + - api-gov.ncloud-docs.com + min_entropy: 3.5 + confidence: medium + examples: + - 'STS SecretAccessKey: b0qP0cQ9ZfS0lq8Gm9QyGqgT2aX9bVd1eKp3uX1/ https://guide.ncloud-docs.com' + references: + - https://api.ncloud-docs.com/docs/en/management-sts + - https://api-gov.ncloud-docs.com/docs/common-ncpapi-ncpapi + validation: + type: Http + content: + request: + method: GET + url: https://ncloud.apigw.ntruss.com/server/v2/getRegionList + headers: + x-ncp-apigw-timestamp: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%}{{ ts }} + x-ncp-iam-access-key: "{{ AKID }}" + x-ncp-apigw-signature-v2: > + {%- assign ts = "" | unix_timestamp | times: 1000 -%} + {%- assign uri = "/server/v2/getRegionList" -%} + {%- assign msg = "GET " | append: uri | append: "\n" | append: ts | append: "\n" | append: AKID -%} + {{ msg | hmac_sha256: TOKEN }} + response_matcher: + - type: StatusMatch + status: [200] + - type: XmlValid + - type: WordMatch + match_all_words: true + words: + - "0" + - "success" + depends_on_rule: + - rule_id: kingfisher.navercloud.pub_sts + variable: AKID diff --git a/data/rules/openrouter.yml b/data/rules/openrouter.yml index ff7a469..a75653a 100644 --- a/data/rules/openrouter.yml +++ b/data/rules/openrouter.yml @@ -10,8 +10,8 @@ rules: \b pattern_requirements: min_digits: 4 - min_entropy: 4.0 - confidence: high + min_entropy: 3.5 + confidence: medium examples: - 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 - 'Authorization: Bearer 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' @@ -23,14 +23,16 @@ rules: content: request: method: GET - url: https://openrouter.ai/api/v1/credits + url: https://openrouter.ai/api/v1/key headers: Authorization: "Bearer {{ TOKEN }}" - Accept: application/json + Accept: "application/json" response_matcher: - report_response: true - type: StatusMatch status: [200] - - type: JsonValid - type: WordMatch - words: ['"data"', '"total_credits"', '"total_usage"'] + words: + - '"data"' + - '"label"' + match_all_words: true \ No newline at end of file diff --git a/data/rules/plaid.yml b/data/rules/plaid.yml new file mode 100644 index 0000000..f163566 --- /dev/null +++ b/data/rules/plaid.yml @@ -0,0 +1,264 @@ +rules: + - name: Plaid Client ID (helper) + id: kingfisher.plaid.1 + visible: false + pattern: | + (?xi) + \b + (?:plaid[\w-]{0,32})? + (?:client[_-]?id|plaid[_-]?client[_-]?id) + \b + (?:\s*[:=]\s*|["']\s*:\s*["']|=\s*["']) + \s* + \b + ( + [a-z0-9]{24} + ) + \b + min_entropy: 2.8 + confidence: medium + examples: + - 'plaid_client_id="sd479fjropblyr5b4m2dutha"' + references: + - https://plaid.com/docs/api/institutions/ + + # ------------------------- + - name: Plaid Secret (Production) + id: kingfisher.plaid.2 + pattern: | + (?xi) + \b + plaid + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|ACCESS_KEY) + (?:.|[\n\r]){0,32}? + \b + ( + [a-z0-9]{30} + ) + \b + min_entropy: 3.2 + confidence: medium + pattern_requirements: + min_digits: 2 + min_lowercase: 6 + ignore_if_contains: + - changeme + - example + - test + examples: + - 'plaid_secret_key="wuxd6sw7ma4lv10xremyhz7ulf9owc"' + references: + - https://plaid.com/docs/api/institutions/ + - https://plaid.com/docs/quickstart/glossary/ + - https://plaid.com/docs/errors/invalid-input/ + depends_on_rule: + - rule_id: kingfisher.plaid.1 + variable: CLIENT_ID + validation: + type: Http + content: + request: + method: POST + url: https://production.plaid.com/institutions/get + headers: + Accept: application/json + Content-Type: application/json + body: | + { + "client_id": "{{ CLIENT_ID | json_escape }}", + "secret": "{{ TOKEN | json_escape }}", + "count": 1, + "offset": 0, + "country_codes": ["US"] + } + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + # If keys are invalid, Plaid returns INVALID_API_KEYS (HTTP 400). + - type: WordMatch + negative: true + words: + - '"INVALID_API_KEYS"' + + - name: Plaid Secret (Sandbox) + id: kingfisher.plaid.3 + pattern: | + (?xi) + \b + plaid + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|ACCESS_KEY) + (?:.|[\n\r]){0,32}? + \b + ( + [a-z0-9]{30} + ) + \b + min_entropy: 3.2 + confidence: medium + pattern_requirements: + min_digits: 2 + min_lowercase: 6 + ignore_if_contains: + - changeme + - example + - test + examples: + - 'PLAID_SECRET="wuxd6sw7ma4lv10xremyhz7ulf9owc"' + references: + - https://plaid.com/docs/api/institutions/ + - https://plaid.com/docs/quickstart/glossary/ + - https://plaid.com/docs/errors/invalid-input/ + depends_on_rule: + - rule_id: kingfisher.plaid.1 + variable: CLIENT_ID + validation: + type: Http + content: + request: + method: POST + url: https://sandbox.plaid.com/institutions/get + headers: + Accept: application/json + Content-Type: application/json + body: | + { + "client_id": "{{ CLIENT_ID | json_escape }}", + "secret": "{{ TOKEN | json_escape }}", + "count": 1, + "offset": 0, + "country_codes": ["US"] + } + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + # If keys are invalid, Plaid returns INVALID_API_KEYS (HTTP 400). + - type: WordMatch + negative: true + words: + - '"INVALID_API_KEYS"' + + # ------------------------- + # Plaid access tokens (env-specific) + # Validate using /accounts/get (requires client_id + secret + access_token) + # ------------------------- + - name: Plaid Access Token (Production) + id: kingfisher.plaid.4 + pattern: | + (?xi) + \b + ( + access-production- + [0-9a-f]{8}- + [0-9a-f]{4}- + [0-9a-f]{4}- + [0-9a-f]{4}- + [0-9a-f]{12} + ) + \b + min_entropy: 3.4 + confidence: medium + examples: + - 'plaid_api_token="access-production-822f6ce0-ee1a-221e-3cd8-f3ce5094b3e2"' + references: + - https://plaid.com/docs/quickstart/glossary/ + - https://plaid.com/docs/api/accounts/ + - https://plaid.com/docs/errors/invalid-input/ + depends_on_rule: + - rule_id: kingfisher.plaid.1 + variable: CLIENT_ID + - rule_id: kingfisher.plaid.2 + variable: PLAID_SECRET + validation: + type: Http + content: + request: + method: POST + url: https://production.plaid.com/accounts/get + headers: + Accept: application/json + Content-Type: application/json + body: | + { + "client_id": "{{ CLIENT_ID | json_escape }}", + "secret": "{{ PLAID_SECRET | json_escape }}", + "access_token": "{{ TOKEN | json_escape }}" + } + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + match_all_words: true + words: + - '"accounts"' + - type: WordMatch + negative: true + match_all_words: false + words: + - '"INVALID_ACCESS_TOKEN"' + - '"INVALID_API_KEYS"' + + - name: Plaid Access Token (Sandbox) + id: kkingfisher.plaid.5 + pattern: | + (?xi) + \b + ( + access-sandbox- + [0-9a-f]{8}- + [0-9a-f]{4}- + [0-9a-f]{4}- + [0-9a-f]{4}- + [0-9a-f]{12} + ) + \b + min_entropy: 3.4 + confidence: medium + examples: + - 'PLAID_ACCESS_TOKEN="access-sandbox-822f6ce0-ee1a-221e-3cd8-f3ce5094b3e2"' + references: + - https://plaid.com/docs/quickstart/glossary/ + - https://plaid.com/docs/api/accounts/ + - https://plaid.com/docs/errors/invalid-input/ + depends_on_rule: + - rule_id: kingfisher.plaid.1 + variable: CLIENT_ID + - rule_id: kingfisher.plaid.3 + variable: PLAID_SECRET + validation: + type: Http + content: + request: + method: POST + url: https://sandbox.plaid.com/accounts/get + headers: + Accept: application/json + Content-Type: application/json + body: | + { + "client_id": "{{ CLIENT_ID | json_escape }}", + "secret": "{{ PLAID_SECRET | json_escape }}", + "access_token": "{{ TOKEN | json_escape }}" + } + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + match_all_words: true + words: + - '"accounts"' + - type: WordMatch + negative: true + match_all_words: false + words: + - '"INVALID_ACCESS_TOKEN"' + - '"INVALID_API_KEYS"' \ No newline at end of file diff --git a/data/rules/resend.yml b/data/rules/resend.yml new file mode 100644 index 0000000..a889842 --- /dev/null +++ b/data/rules/resend.yml @@ -0,0 +1,43 @@ +rules: + - name: Resend API Key + id: kingfisher.resend.api_key.1 + pattern: | + (?x) + \b + ( + re_ + [A-Za-z0-9]{8} + _ + [A-Za-z0-9]{24} + ) + \b + min_entropy: 3.2 + confidence: high + categories: + - api + - secret + examples: + - 'RESEND_API_KEY="re_EbtXGAbq_2E1LZ8WYqYsrrDfjEHf6DxEK"' + - "Authorization: Bearer re_jZmz3GSH_MqwC1vjBjZpQH88W4dLsTPpu" + references: + - https://resend.com/docs/api-reference/introduction + - https://resend.com/docs/api-reference/domains/list-domains + - https://resend.com/docs/api-reference/errors + - https://resend.com/docs/knowledge-base/how-to-handle-api-keys + validation: + type: Http + content: + request: + method: GET + url: https://api.resend.com/domains + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: "application/json" + response_matcher: + - report_response: true + # Valid keys: + # - 200 => full_access works for /domains + # - 401 => restricted_api_key (sending-only), still a real key + - type: StatusMatch + status: [200, 401] + - type: JsonValid diff --git a/data/rules/retellai.yml b/data/rules/retellai.yml new file mode 100644 index 0000000..6c5561d --- /dev/null +++ b/data/rules/retellai.yml @@ -0,0 +1,38 @@ +rules: + - name: Retell AI API Key + id: kingfisher.retellai.api_key.1 + pattern: | + (?xi) + \b + ( + key_[0-9a-f]{28} + ) + \b + min_entropy: 3.0 + confidence: high + categories: + - api + - secret + references: + - https://docs.retellai.com/accounts/api-keys-overview + - https://docs.retellai.com/api-references/get-concurrency + + validation: + type: Http + content: + request: + method: GET + url: https://api.retellai.com/get-concurrency + headers: + Accept: application/json + Authorization: "Bearer {{ TOKEN }}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: + - '"current_concurrency"' + - '"concurrency_limit"' + match_all_words: true diff --git a/data/rules/ringcentral.yml b/data/rules/ringcentral.yml new file mode 100644 index 0000000..cc96dc0 --- /dev/null +++ b/data/rules/ringcentral.yml @@ -0,0 +1,115 @@ +rules: + # Helper: Client ID / App Key (NOT secret) + - name: RingCentral Client ID (helper) + id: kingfisher.ringcentral.client_id.1 + visible: false + pattern: | + (?xi) + (?:ringcentral|rcsdk) + (?:.|[\n\r]){0,32}? + (?: + client[_-]?id + | app[_-]?key + | appKey + | RINGCENTRAL_CLIENT[_-]?ID + | RC_CLIENT[_-]?ID + ) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9A-Za-z_-]{22} + ) + \b + min_entropy: 2.6 + confidence: medium + examples: + - "ringcentral_client_id = 'AbCDefGHiJKlMNopQRsTuv'" + - '"appKey: \"AbCDefGHiJKlMNopQRsTuv\""' + references: + - https://developers.ringcentral.com/guide/getting-started/register-app + - https://ringcentral.github.io/tutorial/ + + # Main: Client Secret / App Secret (secret) + - name: RingCentral Client Secret + id: kingfisher.ringcentral.client_secret.1 + pattern: | + (?xi) + (?:ringcentral|rcsdk) + (?:.|[\n\r]){0,32}? + (?: + client[_-]?secret + | app[_-]?secret + | appSecret + | RINGCENTRAL_CLIENT[_-]?SECRET + | RC_CLIENT[_-]?SECRET + ) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9A-Za-z_-]{22,64} + ) + \b + min_entropy: 3.2 + confidence: high + examples: + - "ringcentral_client_secret = 'aBcDeFgHiJkLmNoPqRsTuVwXyZ012345'" + - '"appSecret": "yourAppSecretGoesHere_123"' + pattern_requirements: + min_digits: 1 + min_lowercase: 1 + min_uppercase: 1 + ignore_if_contains: + - yourAppSecret + - YOUR_APP_SECRET + - changeme + - example + - test + references: + - https://developers.ringcentral.com/api-reference/OAuth-and-OIDC/getToken + - https://developers.ringcentral.com/guide/basics/errors + - https://ringcentral.github.io/tutorial/ + + depends_on_rule: + - rule_id: kingfisher.ringcentral.client_id.1 + variable: CLIENT_ID + + validation: + type: Http + content: + request: + method: POST + url: https://platform.devtest.ringcentral.com/restapi/oauth/token + headers: + Accept: application/json + Content-Type: application/x-www-form-urlencoded + Authorization: > + Basic {{ CLIENT_ID | append: ":" | append: TOKEN | b64enc }} + # Intentionally use a grant that's typically not enabled. If we get "unsupported grant type" + # (or “grant type not allowed / unauthorized for grant type”), that still proves the client + # credentials were accepted. + body: grant_type=client_credentials + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 400, 401, 403] + - type: JsonValid + + # VERIFIED signals (any of these indicate the client auth succeeded) + - type: WordMatch + match_all_words: false + words: + - "OAU-250" # Unsupported grant type + - "OAU-112" # client unauthorized for required grant type + - "OAU-125" # grant type not allowed for application + - '"access_token"' + + # NOT VERIFIED signals (bad client id/secret or malformed auth header) + - type: WordMatch + negative: true + match_all_words: false + words: + - "OAU-120" # Wrong Application ID + - "OAU-121" # Wrong Application + - "OAU-146" # Invalid client credentials + - "OAU-123" # Invalid Authorization header + - '"invalid_client"' diff --git a/data/rules/scalingo.yml b/data/rules/scalingo.yml index 15351c4..c297526 100644 --- a/data/rules/scalingo.yml +++ b/data/rules/scalingo.yml @@ -7,6 +7,7 @@ rules: ( tk-us-[\w-]{48} ) + \b pattern_requirements: min_digits: 2 min_entropy: 3.0 diff --git a/src/cli/global.rs b/src/cli/global.rs index 4e4f5ab..bdd52e2 100644 --- a/src/cli/global.rs +++ b/src/cli/global.rs @@ -72,7 +72,7 @@ pub enum Command { View(ViewArgs), /// Update the Kingfisher binary - #[command(name = "self-update", alias = "update")] + #[command(name = "update", alias = "self-update")] SelfUpdate, } diff --git a/src/update.rs b/src/update.rs index 62c0043..c525dd4 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,5 +1,5 @@ // This module checks GitHub for a newer Kingfisher release and (optionally) -// self-updates. Our release assets use short, user-friendly names such as +// s. Our release assets use short, user-friendly names such as // `kingfisher-linux-arm64.tgz`, `kingfisher-darwin-x64.tgz`, etc. Those names // do **not** match the full Rust target triple that the `self_update` crate // expects (e.g. `aarch64-unknown-linux-musl`). We therefore map the compile-