diff --git a/.github/workflows/release-provenance.yml b/.github/workflows/release-provenance.yml
deleted file mode 100644
index c3adb37..0000000
--- a/.github/workflows/release-provenance.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: SLSA Provenance
-
-on:
- release:
- types: [published]
-
-permissions: {}
-
-jobs:
- # Compute SHA256 hashes of all release assets
- hash:
- name: Compute artifact hashes
- runs-on: ubuntu-24.04
- permissions:
- contents: read
- outputs:
- hashes: ${{ steps.hash.outputs.hashes }}
- steps:
- - name: Download release assets
- env:
- GH_TOKEN: ${{ github.token }}
- TAG_NAME: ${{ github.event.release.tag_name }}
- run: |
- set -euo pipefail
- mkdir -p assets
- gh release download "${TAG_NAME}" \
- --repo "${{ github.repository }}" \
- --dir assets
-
- - name: Compute SHA256 hashes
- id: hash
- run: |
- set -euo pipefail
- cd assets
- # Base64-encode the SHA256 hashes for SLSA provenance
- echo "hashes=$(sha256sum -- * | base64 -w0)" >> "$GITHUB_OUTPUT"
-
- # Generate SLSA provenance for the release artifacts
- provenance:
- name: Generate SLSA provenance
- needs: [hash]
- permissions:
- actions: read
- id-token: write
- contents: write
- uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0
- with:
- base64-subjects: "${{ needs.hash.outputs.hashes }}"
- upload-assets: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8ea626f..f58df1d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -408,6 +408,46 @@ jobs:
with:
subject-path: 'target/release/*'
+ # ──────────────── SLSA Provenance ────────────────
+ hash:
+ name: Compute artifact hashes
+ needs: [release]
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+ outputs:
+ hashes: ${{ steps.hash.outputs.hashes }}
+ steps:
+ - name: Download release assets
+ env:
+ GH_TOKEN: ${{ github.token }}
+ TAG_NAME: ${{ needs.release.outputs.tag }}
+ run: |
+ set -euo pipefail
+ mkdir -p assets
+ gh release download "${TAG_NAME}" \
+ --repo "${{ github.repository }}" \
+ --dir assets
+
+ - name: Compute SHA256 hashes
+ id: hash
+ run: |
+ set -euo pipefail
+ cd assets
+ echo "hashes=$(sha256sum -- * | base64 -w0)" >> "$GITHUB_OUTPUT"
+
+ provenance:
+ name: Generate SLSA provenance
+ needs: [hash]
+ permissions:
+ actions: read
+ id-token: write
+ contents: write
+ uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0
+ with:
+ base64-subjects: "${{ needs.hash.outputs.hashes }}"
+ upload-assets: true
+
# ──────────────── Publish Docker image ────────────────
publish-docker:
needs: [release]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6954865..97b5ed7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,8 +3,10 @@
All notable changes to this project will be documented in this file.
## [v1.92.0]
-- Added new built-in rules for Etsy, Flutterwave, Freemius, JFrog, Kraken, KuCoin, and Trello, plus recent Betterleaks-derived additions including Octopus Deploy, OpenShift, Private AI, SettleMint, Sidekiq, and Polymarket.
+- Added new built-in rules for Etsy, Flutterwave, Freemius, JFrog, Kraken, KuCoin, Trello, Octopus Deploy, OpenShift, Private AI, SettleMint, Sidekiq, and Polymarket.
- Added live HTTP validation for Etsy, JFrog, Octopus Deploy, OpenShift, and Private AI where provider documentation supported reliable token-only checks.
+- Added detection + validation rules for Anthropic Admin, Azure Speech, Azure Translator, Databento, DataStax Astra, DevCycle, Fullstory, GC Notify, and Stytch; built-in runtime rule count is now 601 with `--confidence=low`.
+- Added Heroku token revocation support for both legacy UUID-format tokens and `HRKU-` platform tokens via the OAuth authorizations API.
## [v1.91.0]
- Added SSRF protection for credential validation: outbound HTTP requests now block connections to loopback, private, link-local, and other non-public IP addresses. HTTP redirect targets are DNS-resolved and validated against the same SSRF rules. Use `--allow-internal-ips` to opt out when scanning internal infrastructure.
diff --git a/README.md b/README.md
index d5cb266..e208ae9 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](https://opensource.org/licenses/Apache-2.0)
-[](https://github.com/mongodb/kingfisher)
+[](https://github.com/mongodb/kingfisher)
[](https://github.com/mongodb/kingfisher/pkgs/container/kingfisher)
@@ -302,6 +302,40 @@ Kingfisher supports multiple installation methods:
**For complete installation instructions and pre-commit hook setup, see [docs/INSTALLATION.md](docs/INSTALLATION.md).**
+## Verifying Releases
+
+Every Kingfisher release includes [SLSA v3](https://slsa.dev) provenance and GitHub build attestations so you can verify that artifacts were built by our CI pipeline and haven't been tampered with.
+
+### SLSA provenance
+
+Each GitHub release includes a `multiple.intoto.jsonl` provenance file. Verify any release artifact with [`slsa-verifier`](https://github.com/slsa-framework/slsa-verifier):
+
+```bash
+# Install slsa-verifier
+go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
+
+# Download the artifact and provenance from the release
+gh release download v1.91.0 --repo mongodb/kingfisher \
+ --pattern 'kingfisher-linux-x64.tgz' \
+ --pattern 'multiple.intoto.jsonl'
+
+# Verify
+slsa-verifier verify-artifact kingfisher-linux-x64.tgz \
+ --provenance-path multiple.intoto.jsonl \
+ --source-uri github.com/mongodb/kingfisher
+```
+
+### GitHub attestations
+
+Release artifacts also have GitHub build attestations, verifiable with the GitHub CLI:
+
+```bash
+gh release download v1.91.0 --repo mongodb/kingfisher \
+ --pattern 'kingfisher-linux-x64.tgz'
+
+gh attestation verify kingfisher-linux-x64.tgz --repo mongodb/kingfisher
+```
+
# Detection Rules
Kingfisher ships with [hundreds of rules](crates/kingfisher-rules/data/rules/) that cover everything from classic cloud keys to the latest AI SaaS tokens. Below is an overview:
diff --git a/crates/kingfisher-rules/data/rules/AGENTS.md b/crates/kingfisher-rules/data/rules/AGENTS.md
index 80a9522..80ecbaf 100644
--- a/crates/kingfisher-rules/data/rules/AGENTS.md
+++ b/crates/kingfisher-rules/data/rules/AGENTS.md
@@ -30,8 +30,27 @@ Strongly recommended fields:
## Pattern Quality Rules
- Prefer specific anchors/prefixes and provider context over broad generic regex.
+- When the token format is generic or common-looking (for example bare 32-hex keys), prefer contextual patterns of the form: provider keyword -> short flexible gap -> key/secret label -> short flexible gap -> token. A good default is:
+ - `\b`
+ - provider identifier (for example `amplitude`, `azure`, `speech`, `translator`)
+ - `(?:.|[\n\r]){0,N}?`
+ - common credential labels such as `(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|AUTHORIZATION|API)`
+ - `(?:.|[\n\r]){0,M}?`
+ - the token capture wrapped in a single unnamed capture group
+- Do not add surrounding context when the token is already strongly self-identifying by prefix or structure (for example `sk-ant-api...`, `AstraCS:...`, `dvc_client_...`, `secret-test-...`). In those cases, prefer the tighter self-identifying regex.
- Use `pattern_requirements` to enforce quality constraints (`min_digits`, `min_uppercase`, `min_lowercase`, `min_special_chars`, `ignore_if_contains`, `checksum`).
-- Use checksum validation in `pattern_requirements.checksum` when token formats support it.
+- Use checksum validation in `pattern_requirements.checksum` when token formats support it. This is preferred when the provider token format includes a documented or reverse-engineered check segment, because it can sharply reduce false positives without adding brittle surrounding context.
+- For checksum-based rules, prefer named captures for the main token body and checksum suffix/prefix, then compute the expected checksum in Liquid. A typical pattern is:
+ - `(
+ prefix_(?P
...)(?P...)
+ )`
+ - with:
+ - `actual.template: "{{ checksum }}"`
+ - `actual.requires_capture: checksum`
+ - `expected: "{{ body | | }}"`
+ - `skip_if_missing: true`
+- Example: GitHub PATs use a CRC32-derived base62 checksum. The rule in `github.yml` captures `body` and `checksum`, then compares `{{ checksum }}` against `{{ body | crc32 | base62: 6 }}`.
+- Prefer checksum validation over extra loose context whenever the token structure itself supports it. If the checksum is only present on some token generations, keep `skip_if_missing: true` so older examples continue to load safely.
- Use `visible: false` for helper/non-secret captures used only by dependent rules.
- Use `depends_on_rule` for multi-part credential validation (for example ID + secret).
diff --git a/crates/kingfisher-rules/data/rules/anthropic.yml b/crates/kingfisher-rules/data/rules/anthropic.yml
index 3aed2b1..1a2784a 100644
--- a/crates/kingfisher-rules/data/rules/anthropic.yml
+++ b/crates/kingfisher-rules/data/rules/anthropic.yml
@@ -47,4 +47,38 @@ rules:
words:
- '"type":"message"'
- 'credit balance is too low'
- url: https://api.anthropic.com/v1/messages
\ No newline at end of file
+ url: https://api.anthropic.com/v1/messages
+
+ - name: Anthropic Admin API Key
+ id: kingfisher.anthropic.2
+ pattern: |
+ (?xi)
+ (
+ sk-ant-admin(?:\d{2,4})?-[A-Za-z0-9_-]{40,}
+ )
+ pattern_requirements:
+ min_digits: 2
+ min_uppercase: 1
+ min_lowercase: 1
+ min_entropy: 3.5
+ confidence: medium
+ examples:
+ - sk-ant-admin03-4mB9zY2Qx8LmN7pR5sT1uV6wX0aBcDeFgHiJkLmNoPqRsTuVwXyZ1234
+ references:
+ - https://docs.anthropic.com/en/api/administration-api
+ - https://docs.anthropic.com/en/api/admin-api/organization/get-me
+ validation:
+ type: Http
+ content:
+ request:
+ method: GET
+ url: https://api.anthropic.com/v1/organizations/me
+ headers:
+ x-api-key: '{{ TOKEN }}'
+ anthropic-version: "2023-06-01"
+ content-type: application/json
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
diff --git a/crates/kingfisher-rules/data/rules/azurespeech.yml b/crates/kingfisher-rules/data/rules/azurespeech.yml
new file mode 100644
index 0000000..4a661cf
--- /dev/null
+++ b/crates/kingfisher-rules/data/rules/azurespeech.yml
@@ -0,0 +1,84 @@
+rules:
+ - name: Azure Speech Region
+ id: kingfisher.azurespeech.1
+ visible: false
+ pattern: |
+ (?xi)
+ \b
+ (?:
+ SPEECH_REGION
+ |
+ AZURE_SPEECH_REGION
+ |
+ speech[_-]?region
+ |
+ azure[_-]?speech[_-]?region
+ )
+ \b
+ (?:.|[\n\r]){0,16}?
+ [=:]
+ \s*["']?
+ (
+ [a-z0-9-]{4,32}
+ )
+ ["']?
+ min_entropy: 1.5
+ confidence: medium
+ examples:
+ - SPEECH_REGION=eastus
+ - azure_speech_region="westus2"
+ references:
+ - https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech
+
+ - name: Azure Speech API Key
+ id: kingfisher.azurespeech.2
+ pattern: |
+ (?xi)
+ \b
+ (?:
+ speech
+ |
+ azure[_-]?speech
+ )
+ (?:.|[\n\r]){0,24}?
+ (?:
+ key
+ |
+ api[_-]?key
+ |
+ subscription[_-]?key
+ |
+ secret
+ )
+ (?:.|[\n\r]){0,16}?
+ (
+ [a-f0-9]{32}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_lowercase: 2
+ min_entropy: 3.5
+ confidence: medium
+ examples:
+ - AZURE_SPEECH_KEY=1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
+ - speech_subscription_key="abcdef0123456789abcdef0123456789"
+ references:
+ - https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech
+ depends_on_rule:
+ - rule_id: kingfisher.azurespeech.1
+ variable: AZURE_SPEECH_REGION
+ validation:
+ type: Http
+ content:
+ request:
+ method: POST
+ url: https://{{ AZURE_SPEECH_REGION }}.api.cognitive.microsoft.com/sts/v1.0/issueToken
+ headers:
+ Ocp-Apim-Subscription-Key: "{{ TOKEN }}"
+ Content-Type: application/x-www-form-urlencoded
+ Content-Length: "0"
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
diff --git a/crates/kingfisher-rules/data/rules/azuretranslator.yml b/crates/kingfisher-rules/data/rules/azuretranslator.yml
new file mode 100644
index 0000000..7a9ccf3
--- /dev/null
+++ b/crates/kingfisher-rules/data/rules/azuretranslator.yml
@@ -0,0 +1,97 @@
+rules:
+ - name: Azure Translator Region
+ id: kingfisher.azuretranslator.1
+ visible: false
+ pattern: |
+ (?xi)
+ \b
+ (?:
+ TRANSLATOR_REGION
+ |
+ AZURE_TRANSLATOR_REGION
+ |
+ translator[_-]?region
+ |
+ translation[_-]?region
+ |
+ Ocp-Apim-Subscription-Region
+ )
+ \b
+ (?:.|[\n\r]){0,16}?
+ [=:]
+ \s*["']?
+ (
+ [a-z0-9-]{4,32}
+ )
+ ["']?
+ min_entropy: 1.5
+ confidence: medium
+ examples:
+ - TRANSLATOR_REGION=eastus
+ - azure_translator_region="westeurope"
+ references:
+ - https://learn.microsoft.com/en-us/azure/ai-services/translator/text-translation/reference/authentication
+ - https://learn.microsoft.com/en-us/azure/ai-services/translator/reference/v3-0-reference
+
+ - name: Azure Translator API Key
+ id: kingfisher.azuretranslator.2
+ pattern: |
+ (?xi)
+ \b
+ (?:
+ translator
+ |
+ translation
+ |
+ azure[_-]?(?:translator|translation)
+ )
+ (?:.|[\n\r]){0,24}?
+ (?:
+ key
+ |
+ api[_-]?key
+ |
+ subscription[_-]?key
+ |
+ secret
+ )
+ (?:.|[\n\r]){0,16}?
+ (
+ [a-f0-9]{32}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_lowercase: 2
+ min_entropy: 3.5
+ confidence: medium
+ examples:
+ - AZURE_TRANSLATOR_KEY=1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
+ - translator_subscription_key="abcdef0123456789abcdef0123456789"
+ references:
+ - https://learn.microsoft.com/en-us/azure/ai-services/translator/text-translation/reference/authentication
+ - https://learn.microsoft.com/en-us/azure/ai-services/translator/reference/v3-0-reference
+ depends_on_rule:
+ - rule_id: kingfisher.azuretranslator.1
+ variable: AZURE_TRANSLATOR_REGION
+ validation:
+ type: Http
+ content:
+ request:
+ method: POST
+ url: https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=es
+ headers:
+ Ocp-Apim-Subscription-Key: "{{ TOKEN }}"
+ Ocp-Apim-Subscription-Region: "{{ AZURE_TRANSLATOR_REGION }}"
+ Content-Type: application/json
+ body: |
+ [
+ {
+ "Text": "hello"
+ }
+ ]
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
diff --git a/crates/kingfisher-rules/data/rules/databento.yml b/crates/kingfisher-rules/data/rules/databento.yml
new file mode 100644
index 0000000..2249cb0
--- /dev/null
+++ b/crates/kingfisher-rules/data/rules/databento.yml
@@ -0,0 +1,34 @@
+rules:
+ - name: Databento API Key
+ id: kingfisher.databento.1
+ pattern: |
+ (?x)
+ \b
+ (
+ db-[A-Za-z0-9]{29}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_lowercase: 2
+ min_entropy: 3.3
+ confidence: medium
+ examples:
+ - DATABENTO_API_KEY=db-abc123def456ghi789jkl012mno34
+ references:
+ - https://databento.com/docs/api-reference-historical
+ - https://databento.com/docs/portal/api-keys
+ validation:
+ type: Http
+ content:
+ request:
+ method: GET
+ url: https://hist.databento.com/v0/metadata.list_datasets
+ headers:
+ Authorization: "Basic {{ TOKEN | append: ':' | b64enc }}"
+ Accept: application/json
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
diff --git a/crates/kingfisher-rules/data/rules/datastax.yml b/crates/kingfisher-rules/data/rules/datastax.yml
new file mode 100644
index 0000000..62b4537
--- /dev/null
+++ b/crates/kingfisher-rules/data/rules/datastax.yml
@@ -0,0 +1,33 @@
+rules:
+ - name: DataStax Astra Application Token
+ id: kingfisher.datastax.1
+ pattern: |
+ (?x)
+ \b
+ (
+ AstraCS:[A-Za-z0-9]{20,}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_entropy: 4.0
+ confidence: medium
+ examples:
+ - ASTRA_DB_APPLICATION_TOKEN=AstraCS:Q29kZXhWYWxpZGF0aW9uVG9rZW5FeGFtcGxlMTIzNDU2Nzg5
+ references:
+ - https://docs.datastax.com/en/astra-db-serverless/administration/manage-application-tokens.html
+ - https://docs.datastax.com/en/astra-db-classic/api-reference/devops-api.html
+ validation:
+ type: Http
+ content:
+ request:
+ method: GET
+ url: https://api.astra.datastax.com/v2/databases
+ headers:
+ Authorization: Bearer {{ TOKEN }}
+ Accept: application/json
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
diff --git a/crates/kingfisher-rules/data/rules/devcycle.yml b/crates/kingfisher-rules/data/rules/devcycle.yml
new file mode 100644
index 0000000..0e71e76
--- /dev/null
+++ b/crates/kingfisher-rules/data/rules/devcycle.yml
@@ -0,0 +1,117 @@
+rules:
+ - name: DevCycle Client SDK Key
+ id: kingfisher.devcycle.1
+ pattern: |
+ (?x)
+ \b
+ (
+ dvc_client_[A-Za-z0-9]{8,32}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_lowercase: 2
+ min_entropy: 3.0
+ confidence: medium
+ examples:
+ - dvc_client_abc12345
+ - 'sdkKey: "dvc_client_abcdefg1234"'
+ references:
+ - https://docs.devcycle.com/cli-guides/environments/
+ - https://docs.devcycle.com/bucketing-api/
+ validation:
+ type: Http
+ content:
+ request:
+ method: POST
+ url: https://bucketing-api.devcycle.com/v1/variables
+ headers:
+ Authorization: Bearer {{ TOKEN }}
+ Content-Type: application/json
+ body: |
+ {
+ "user_id": "kingfisher-validation-user"
+ }
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
+
+ - name: DevCycle Mobile SDK Key
+ id: kingfisher.devcycle.2
+ pattern: |
+ (?x)
+ \b
+ (
+ dvc_mobile_[A-Za-z0-9]{8,32}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_lowercase: 2
+ min_entropy: 3.0
+ confidence: medium
+ examples:
+ - dvc_mobile_abc12345
+ - 'mobileKey: "dvc_mobile_abcdefg1234"'
+ references:
+ - https://docs.devcycle.com/cli-guides/environments/
+ - https://docs.devcycle.com/bucketing-api/
+ validation:
+ type: Http
+ content:
+ request:
+ method: POST
+ url: https://bucketing-api.devcycle.com/v1/variables
+ headers:
+ Authorization: Bearer {{ TOKEN }}
+ Content-Type: application/json
+ body: |
+ {
+ "user_id": "kingfisher-validation-user"
+ }
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
+
+ - name: DevCycle Server SDK Key
+ id: kingfisher.devcycle.3
+ pattern: |
+ (?x)
+ \b
+ (
+ dvc_server_[A-Za-z0-9]{8,32}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_lowercase: 2
+ min_entropy: 3.0
+ confidence: medium
+ examples:
+ - dvc_server_abc12345
+ - 'serverKey: "dvc_server_abcdefg1234"'
+ references:
+ - https://docs.devcycle.com/cli-guides/environments/
+ - https://docs.devcycle.com/bucketing-api/
+ validation:
+ type: Http
+ content:
+ request:
+ method: POST
+ url: https://bucketing-api.devcycle.com/v1/variables
+ headers:
+ Authorization: Bearer {{ TOKEN }}
+ Content-Type: application/json
+ body: |
+ {
+ "user_id": "kingfisher-validation-user"
+ }
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
diff --git a/crates/kingfisher-rules/data/rules/fullstory.yml b/crates/kingfisher-rules/data/rules/fullstory.yml
new file mode 100644
index 0000000..b9c4571
--- /dev/null
+++ b/crates/kingfisher-rules/data/rules/fullstory.yml
@@ -0,0 +1,41 @@
+rules:
+ - name: Fullstory API Key
+ id: kingfisher.fullstory.1
+ pattern: |
+ (?xi)
+ \b
+ (?:fullstory|fs_api_key|fullstory_api_key)
+ (?:.|[\n\r]){0,32}?
+ (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|AUTHORIZATION|API)
+ (?:.|[\n\r]){0,16}?
+ \b
+ (
+ (?:na1|eu1)\.[A-Za-z0-9]{20,}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_lowercase: 2
+ min_entropy: 3.3
+ confidence: medium
+ examples:
+ - FULLSTORY_API_KEY=na1.Abcd1234Efgh5678Ijkl9012Mnop3456
+ - 'fs_api_key: "eu1.Abcd1234Efgh5678Ijkl9012Mnop3456"'
+ references:
+ - https://developer.fullstory.com/server/v1/getting-started/
+ - https://developer.fullstory.com/server/authentication/
+ - https://developer.fullstory.com/server/v1/authentication/me/
+ validation:
+ type: Http
+ content:
+ request:
+ method: GET
+ url: https://api.fullstory.com/me
+ headers:
+ Authorization: Basic {{ TOKEN }}
+ Accept: application/json
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
diff --git a/crates/kingfisher-rules/data/rules/gcnotify.yml b/crates/kingfisher-rules/data/rules/gcnotify.yml
new file mode 100644
index 0000000..1d83850
--- /dev/null
+++ b/crates/kingfisher-rules/data/rules/gcnotify.yml
@@ -0,0 +1,41 @@
+rules:
+ - name: GC Notify API Key
+ id: kingfisher.gcnotify.1
+ pattern: |
+ (?xi)
+ \b
+ (
+ ApiKey-v1
+ \s+
+ gcntfy-[a-z0-9_]+
+ -
+ [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}
+ )
+ \b
+ pattern_requirements:
+ min_digits: 4
+ min_lowercase: 2
+ min_entropy: 3.5
+ confidence: medium
+ examples:
+ - 'Authorization: "ApiKey-v1 gcntfy-my_test_key-26785a09-ab16-4eb0-8407-a37497a57506-3d844edf-8d35-48ac-975b-e847b4f122b0"'
+ references:
+ - https://documentation.notification.canada.ca/en/start.html
+ - https://documentation.notification.canada.ca/en/status.html
+ - https://documentation.notification.canada.ca/en/keys.html
+ validation:
+ type: Http
+ content:
+ request:
+ method: GET
+ url: https://api.notification.canada.ca/v2/notifications
+ headers:
+ Authorization: "{{ TOKEN }}"
+ Accept: application/json
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: JsonValid
diff --git a/crates/kingfisher-rules/data/rules/heroku.yml b/crates/kingfisher-rules/data/rules/heroku.yml
index 96e94e4..d10ff87 100644
--- a/crates/kingfisher-rules/data/rules/heroku.yml
+++ b/crates/kingfisher-rules/data/rules/heroku.yml
@@ -20,6 +20,7 @@ rules:
- 'HEROKU_API_KEY: c55dbac4-e0e8-4a06-b892-75cac2387ce5'
references:
- https://devcenter.heroku.com/articles/authentication
+ - https://devcenter.heroku.com/articles/oauth
validation:
type: Http
content:
@@ -33,6 +34,35 @@ rules:
- report_response: true
- type: StatusMatch
status: [200]
+ revocation:
+ type: HttpMultiStep
+ content:
+ steps:
+ - name: lookup_authorization_id
+ request:
+ method: GET
+ url: https://api.heroku.com/oauth/authorizations
+ headers:
+ Accept: application/vnd.heroku+json; version=3
+ Authorization: Bearer {{ TOKEN }}
+ response_matcher:
+ - type: StatusMatch
+ status: [200]
+ extract:
+ AUTHORIZATION_ID:
+ type: Regex
+ pattern: '"id":"([^"]+)"(?:.|[\n\r]){0,2048}?"token":"{{ TOKEN }}"'
+ - name: revoke_authorization
+ request:
+ method: DELETE
+ url: https://api.heroku.com/oauth/authorizations/{{ AUTHORIZATION_ID }}
+ headers:
+ Accept: application/vnd.heroku+json; version=3
+ Authorization: Bearer {{ TOKEN }}
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
- name: Heroku API Key (Platform Key)
id: kingfisher.heroku.2
pattern: |
@@ -61,8 +91,38 @@ rules:
- '"id":'
- '"name":'
match_all_words: true
+ revocation:
+ type: HttpMultiStep
+ content:
+ steps:
+ - name: lookup_authorization_id
+ request:
+ method: GET
+ url: https://api.heroku.com/oauth/authorizations
+ headers:
+ Accept: application/vnd.heroku+json; version=3
+ Authorization: Bearer {{TOKEN}}
+ response_matcher:
+ - type: StatusMatch
+ status: [200]
+ extract:
+ AUTHORIZATION_ID:
+ type: Regex
+ pattern: '"id":"([^"]+)"(?:.|[\n\r]){0,2048}?"token":"{{ TOKEN }}"'
+ - name: revoke_authorization
+ request:
+ method: DELETE
+ url: https://api.heroku.com/oauth/authorizations/{{ AUTHORIZATION_ID }}
+ headers:
+ Accept: application/vnd.heroku+json; version=3
+ Authorization: "Bearer {{TOKEN}}"
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
references:
- https://devcenter.heroku.com/articles/platform-api-quickstart
+ - https://devcenter.heroku.com/articles/oauth
examples:
- "HRKU-AADVTUYvfjT4nhuJ07bEfAUq9GS3PkTdyWuNBiXYmYMg_____wgAf6OTnGyh"
- "HRKU-AABW9W1iH9NHEIlAABq9nZUq9GS3PkTdyWuNBiXYmYMg_____wV2XYIXxm5p"
diff --git a/crates/kingfisher-rules/data/rules/stytch.yml b/crates/kingfisher-rules/data/rules/stytch.yml
new file mode 100644
index 0000000..97c7a57
--- /dev/null
+++ b/crates/kingfisher-rules/data/rules/stytch.yml
@@ -0,0 +1,76 @@
+rules:
+ - name: Stytch Project ID
+ id: kingfisher.stytch.1
+ visible: false
+ pattern: |
+ (?xi)
+ \b
+ (
+ project-(?:test|live)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
+ )
+ \b
+ min_entropy: 2.0
+ confidence: medium
+ examples:
+ - project-test-8aed2e54-0266-4793-9b5e-0cc9c56064da
+ references:
+ - https://stytch.com/docs/api-reference/consumer/api/overview
+ - https://stytch.com/docs/api-reference/b2b/api/sessions/authenticate-jwt
+
+ - name: Stytch Project Secret
+ id: kingfisher.stytch.2
+ pattern: |
+ (?xi)
+ \b
+ (
+ secret-(?:test|live)-[A-Za-z0-9_-]{35}=?
+ )
+ \b
+ pattern_requirements:
+ min_digits: 2
+ min_uppercase: 1
+ min_lowercase: 1
+ min_entropy: 3.5
+ confidence: medium
+ examples:
+ - secret-test-IJ7zLTgXp8xoS7yXO2xavNxZTbYfvm-2nZM=
+ references:
+ - https://stytch.com/docs/api-reference/consumer/api/overview
+ - https://stytch.com/docs/api-reference/b2b/api/m2m/overview
+ - https://stytch.com/docs/api-reference/b2b/api/sessions/authenticate-jwt
+ depends_on_rule:
+ - rule_id: kingfisher.stytch.1
+ variable: STYTCH_PROJECT_ID
+ validation:
+ type: Http
+ content:
+ request:
+ method: POST
+ url: >
+ {%- if TOKEN contains "-live-" -%}
+ https://api.stytch.com/v1/m2m/clients/search
+ {%- else -%}
+ https://test.stytch.com/v1/m2m/clients/search
+ {%- endif -%}
+ headers:
+ Authorization: "Basic {{ STYTCH_PROJECT_ID | append: ':' | append: TOKEN | b64enc }}"
+ Content-Type: application/json
+ body: |
+ {
+ "query": {
+ "operator": "AND",
+ "operands": [
+ {
+ "filter_name": "status",
+ "filter_value": ["active"]
+ }
+ ]
+ }
+ }
+ response_matcher:
+ - report_response: true
+ - type: StatusMatch
+ status: [200]
+ - type: WordMatch
+ words:
+ - '"m2m_clients"'