Compare commits

..

18 commits

Author SHA1 Message Date
4d5ce57a12 feat(gitea): add --clone-url-base flag for clone URL rewriting
When scanning a self-hosted Gitea/Forgejo instance, the API may be
reachable at a different hostname than the git clone endpoint (e.g.,
internal API vs. public clone URL behind a reverse proxy). The
--clone-url-base flag rewrites the scheme, host, and port of clone
URLs returned by the API, preserving the path.

Example:
  kingfisher scan gitea \
    --api-url https://forge.internal.example.com/api/v1/ \
    --clone-url-base https://forge.internal.example.com/ \
    --user eblume

This avoids routing clone traffic through an external proxy when the
API and git endpoints share the same internal host but the instance's
ROOT_URL points to the public endpoint.

Includes unit tests for the URL rewriting function and an integration
test using wiremock to verify the full enumeration path.
2026-03-29 22:00:33 -07:00
Mick Grove
165768b5ca
Merge pull request #303 from mongodb/development
v1.92.0
2026-03-29 20:34:43 -07:00
Mick Grove
ba30b1788f fixed github actions 2026-03-29 18:24:18 -07:00
Mick Grove
9c448eec60 fixed github actions 2026-03-29 17:36:40 -07:00
Mick Grove
49d980acb0 fixed github actions 2026-03-29 17:29:33 -07:00
Mick Grove
8da61ab553 fixed github actions 2026-03-29 17:22:23 -07:00
Mick Grove
fc542afa99 fixed github actions 2026-03-29 17:08:58 -07:00
Mick Grove
92b23b1f82 fixed github actions 2026-03-29 16:43:45 -07:00
Mick Grove
01f3a12106 fixed github actions 2026-03-29 15:34:08 -07:00
Mick Grove
2b99c6a354 fixed github actions 2026-03-29 12:48:55 -07:00
Mick Grove
ac2198e3bd fixed github actions 2026-03-29 12:32:14 -07:00
Mick Grove
482a60bb9d fixed github actions 2026-03-29 10:41:54 -07:00
Mick Grove
3d7629cf8b added more rules 2026-03-29 08:55:09 -07:00
Mick Grove
b1c2953d25 added more rules 2026-03-29 08:54:40 -07:00
Mick Grove
3442e92405 added more rules 2026-03-29 08:53:18 -07:00
Mick Grove
b9da8e2829 added more rules 2026-03-29 08:19:34 -07:00
Mick Grove
a28250be19 added more rules 2026-03-29 00:03:58 -07:00
Mick Grove
2265d2b1f0 added rules 2026-03-28 22:39:31 -07:00
39 changed files with 2205 additions and 56 deletions

View file

@ -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

View file

@ -408,6 +408,57 @@ 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 \
--pattern '*.tgz' \
--pattern '*.deb' \
--pattern '*.rpm' \
--pattern '*.zip'
- name: Compute SHA256 hashes
id: hash
run: |
set -euo pipefail
cd assets
shopt -s nullglob
files=( * )
if [ ${#files[@]} -eq 0 ]; then
echo "Error: no release assets found to hash in $(pwd)" >&2
exit 1
fi
hashes=$(sha256sum -- "${files[@]}" | base64 -w0)
echo "hashes=${hashes}" >> "$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]

View file

@ -2,6 +2,19 @@
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, 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.
- Added `hmac_sha256_b64key` Liquid filter for HMAC-SHA256 signing with base64-encoded keys (decodes key to raw bytes before signing), enabling correct Azure Notification Hub SAS validation.
- Integrated SLSA v3 provenance generation into the release workflow; hash computation now scopes to build artifacts only for idempotent re-runs.
- Removed Zapier webhook live validation (GET to a catch hook triggers the Zap).
- Hardened Heroku revocation regex to prevent crossing JSON object boundaries when extracting authorization IDs.
- Fixed Zendesk subdomain regex to reject trailing hyphens; renamed `ZENDESK_SUBDOMAIN` to `ZENDESK_HOST` for clarity.
- Fixed Stytch and Polymarket trailing `\b` boundaries that prevented matching base64-padded secrets ending with `=`.
- Tightened Kubernetes API Server URL pattern to require kube-specific identifiers, preventing bootstrap tokens from binding to unrelated `server:` entries.
## [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.
- Consolidated JWT SSRF checks to use the shared `is_ssrf_safe_ip` function, covering additional reserved ranges (CGNAT, documentation, benchmarking, IPv6 unique-local).

View file

@ -48,7 +48,7 @@ http = "1.4"
[package]
name = "kingfisher"
version = "1.91.0"
version = "1.92.0"
description = "MongoDB's blazingly fast and accurate secret scanning and validation tool"
edition.workspace = true
rust-version.workspace = true

76
NOTICE
View file

@ -47,10 +47,16 @@ Certain detection rules:
* crates/kingfisher-rules/data/rules/instagram.yml
* crates/kingfisher-rules/data/rules/iterable.yml
* crates/kingfisher-rules/data/rules/lokalise.yml
* crates/kingfisher-rules/data/rules/azure-notification-hub.yml
* crates/kingfisher-rules/data/rules/firebase.yml
* crates/kingfisher-rules/data/rules/helpscout.yml
* crates/kingfisher-rules/data/rules/kubernetes.yml
* crates/kingfisher-rules/data/rules/pendo.yml
* crates/kingfisher-rules/data/rules/razorpay.yml
* crates/kingfisher-rules/data/rules/spotify.yml
* crates/kingfisher-rules/data/rules/wakatime.yml
* crates/kingfisher-rules/data/rules/zapier.yml
* crates/kingfisher-rules/data/rules/zendesk.yml
are derived in part from Titus (https://github.com/praetorian-inc/titus),
which is licensed under the Apache License, Version 2.0.
@ -72,3 +78,73 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--------------------------------------------------------------------
Certain detection rules:
* crates/kingfisher-rules/data/rules/octopusdeploy.yml
* crates/kingfisher-rules/data/rules/openshift.yml
* crates/kingfisher-rules/data/rules/polymarket.yml
* crates/kingfisher-rules/data/rules/privateai.yml
* crates/kingfisher-rules/data/rules/settlemint.yml
* crates/kingfisher-rules/data/rules/sidekiq.yml
are derived in part from Betterleaks
(https://github.com/betterleaks/betterleaks), which is licensed under the MIT
License.
Betterleaks
Copyright (c) 2026 Zachary Rice
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------
Certain detection rules:
* crates/kingfisher-rules/data/rules/etsy.yml
* crates/kingfisher-rules/data/rules/flutterwave.yml
* crates/kingfisher-rules/data/rules/freemius.yml
* crates/kingfisher-rules/data/rules/jfrog.yml
* crates/kingfisher-rules/data/rules/kraken.yml
* crates/kingfisher-rules/data/rules/kucoin.yml
* crates/kingfisher-rules/data/rules/trello.yml
are derived in part from Gitleaks (https://github.com/gitleaks/gitleaks),
which is licensed under the MIT License.
Gitleaks
Copyright (c) 2019 Zachary Rice
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -4,7 +4,7 @@
<img src="docs/kingfisher_logo.png" alt="Kingfisher Logo" width="126" height="173" style="vertical-align: right;" />
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Detection Rules](https://img.shields.io/badge/Detection%20Rules-548-2ea043.svg)](https://github.com/mongodb/kingfisher)<br>
[![Detection Rules](https://img.shields.io/badge/Detection%20Rules-601-2ea043.svg)](https://github.com/mongodb/kingfisher)<br>
[![ghcr downloads](https://ghcr-badge.elias.eu.org/shield/mongodb/kingfisher/kingfisher)](https://github.com/mongodb/kingfisher/pkgs/container/kingfisher)<br>
@ -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 <version> --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 <version> --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:

View file

@ -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<body>...)(?P<checksum>...)
)`
- with:
- `actual.template: "{{ checksum }}"`
- `actual.requires_capture: checksum`
- `expected: "{{ body | <checksum-filter> | <encoding/filter chain> }}"`
- `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).

View file

@ -47,4 +47,38 @@ rules:
words:
- '"type":"message"'
- 'credit balance is too low'
url: https://api.anthropic.com/v1/messages
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

View file

@ -0,0 +1,156 @@
rules:
- name: Azure Notification Hub Namespace Host
id: kingfisher.azure.notificationhub.1
pattern: |
(?xi)
\b
(?:
endpoint
\s*=\s*
sb://
|
notification
(?:.|[\n\r]){0,48}?
https://
)
(
[a-z0-9]
[a-z0-9-]{1,62}
\.servicebus\.windows\.net
)
(?:/|;|\b)
min_entropy: 2.0
confidence: medium
visible: false
examples:
- Endpoint=sb://acme-push.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=VGhpcytpcythK3Rlc3Qra2V5K3ZhbHVlLzEyMzQ1Njc4OTA=
- 'notificationHubEndpoint: "https://mobile-prod.servicebus.windows.net"'
references:
- https://learn.microsoft.com/en-us/rest/api/notificationhubs/use-rest-api-backend
- name: Azure Notification Hub Name
id: kingfisher.azure.notificationhub.2
pattern: |
(?xi)
\b
(?:
notification
(?:hub)?
(?:name|path)
|
hub
(?:name|path)
)
\s*[:=]\s*
["']?
(
[A-Za-z0-9]
[A-Za-z0-9._-]{6,63}
)
["']?
\b
min_entropy: 2.0
confidence: medium
visible: false
examples:
- NotificationHubPath=my-mobile-hub
- 'notificationHubName: "android-prod"'
references:
- https://learn.microsoft.com/en-us/azure/notification-hubs/create-notification-hub-portal
- name: Azure Notification Hub SAS Key Name
id: kingfisher.azure.notificationhub.3
pattern: |
(?xi)
\b
["']?
SharedAccessKeyName
["']?
\s*[:=]\s*
["']?
(
[A-Za-z]
[A-Za-z0-9_-]{6,63}
)
["']?
\b
min_entropy: 2.0
confidence: medium
visible: false
examples:
- SharedAccessKeyName=DefaultListenSharedAccessSignature
- '"SharedAccessKeyName": "DefaultFullSharedAccessSignature"'
references:
- https://learn.microsoft.com/en-us/azure/notification-hubs/notification-hubs-push-notification-security
- name: Azure Notification Hub Access Key
id: kingfisher.azure.notificationhub.4
pattern: |
(?xi)
(?:
(?:notification\s*hub|Endpoint\s*=\s*sb://[a-z0-9-]{2,63}\.servicebus\.windows\.net/?)
(?:.|[\n\r]){0,160}?
SharedAccessKey
|
\b
(?:hubAccessKey|notificationHub(?:Access)?Key)
\b
)
\s*[:=]\s*
["']?
(
[A-Za-z0-9+/]{32,88}={0,2}
)
["']?
(?:[^A-Za-z0-9+/=]|$)
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 1
ignore_if_contains:
- example
- sample
- document
- placeholder
min_entropy: 3.7
confidence: medium
examples:
- Endpoint=sb://acme-push.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=Q29udG9zb1Rlc3RLZXkrMTIzNDU2Nzg5MC9BQkNERUZHSEk=
- |
const config = {
notificationHubName: "android-prod",
hubAccessKey: "U2FmZUtleVZhbHVlKzEyMzQ1Njc4OTBBQkNERUYrLz09"
};
references:
- https://learn.microsoft.com/en-us/azure/notification-hubs/notification-hubs-push-notification-security
- https://learn.microsoft.com/en-us/rest/api/notificationhubs/use-rest-api-backend
depends_on_rule:
- rule_id: kingfisher.azure.notificationhub.1
variable: NH_HOST
- rule_id: kingfisher.azure.notificationhub.2
variable: NH_HUB
- rule_id: kingfisher.azure.notificationhub.3
variable: NH_KEY_NAME
validation:
type: Http
content:
request:
method: GET
url: 'https://{{ NH_HOST }}/{{ NH_HUB }}/registrations/?api-version=2015-01'
headers:
Accept: application/atom+xml
Authorization: |
{%- assign uri = "https://" | append: NH_HOST | append: "/" | append: NH_HUB | append: "/registrations/?api-version=2015-01" -%}
{%- assign se = "" | unix_timestamp | plus: 300 -%}
{%- assign nl = "" | newline -%}
{%- assign to_sign = uri | url_encode | append: nl | append: se -%}
{%- capture auth -%}SharedAccessSignature sr={{ uri | url_encode }}&sig={{ to_sign | hmac_sha256_b64key: TOKEN | url_encode }}&se={{ se }}&skn={{ NH_KEY_NAME | url_encode }}{%- endcapture -%}
{{ auth | strip_newlines }}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: XmlValid
- type: WordMatch
words:
- "<feed"

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,51 @@
rules:
- name: Etsy Open API Key
id: kingfisher.etsy.1
pattern: |
(?xi)
\b
(?:
etsy
(?:.|[\n\r]){0,32}?
(?:
api[_-]?key |
keystring |
x-api-key
)
|
x-api-key
)
(?:.|[\n\r]){0,12}?
(
[a-z0-9]{24}:[A-Za-z0-9]{10,64}
)
\b
pattern_requirements:
min_digits: 4
min_lowercase: 8
ignore_if_contains:
- your_api_key
- your_key_here
- placeholder
min_entropy: 3.4
confidence: medium
examples:
- 'x-api-key: 1aa2bb33c44d55eeeeee6fff:a1b2c3d4e5'
- ETSY_API_KEY=1aa2bb33c44d55eeeeee6fff:a1b2c3d4e5
references:
- https://developers.etsy.com/documentation/tutorials/quickstart/
validation:
type: Http
content:
request:
method: GET
url: https://api.etsy.com/v3/application/openapi-ping
headers:
x-api-key: '{{ TOKEN | split: ":" | first }}'
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid

View file

@ -0,0 +1,58 @@
rules:
- name: Firebase Cloud Messaging Server Key
id: kingfisher.firebase.1
pattern: |
(?x)
\b
(
AAAA[A-Za-z0-9_-]{7}
:
APA91b[A-Za-z0-9_-]{120,180}
)
(?:[^A-Za-z0-9_-]|$)
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 1
ignore_if_contains:
- example
- sample
- placeholder
- your_key_here
min_entropy: 4.0
confidence: medium
examples:
- FCM_SERVER_KEY=AAAAA1b2CdE:APA91bAbCdEfGhIjKlMnOpQrStUvWxYz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-AaBbCcDdEeFfGgHhIiJj
- 'firebase_server_key: "AAAAQ1w2ErT:APA91bZaYxWvUtSrQpOnMlKjIhGfEdCbA9876543210ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-MmNnOoPpQqRrSsTtUuVv"'
references:
- https://firebase.google.com/docs/cloud-messaging/migrate-v1
# FCM legacy server keys no longer have a safe token-only validation path.
# HTTP v1 requires OAuth2 access tokens from service accounts rather than the legacy key itself.
- name: Firebase Cloud Messaging Device Token
id: kingfisher.firebase.2
pattern: |
(?x)
\b
(
[A-Za-z0-9_-]{22}
:
APA91b[A-Za-z0-9_-]{120,180}
)
(?:[^A-Za-z0-9_-]|$)
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 1
ignore_if_contains:
- example
- sample
- placeholder
min_entropy: 4.0
confidence: medium
examples:
- FCM_DEVICE_TOKEN=AbCdEfGhIjKlMnOpQrStUv:APA91bZaYxWvUtSrQpOnMlKjIhGfEdCbA9876543210ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-AaBbCcDdEeFfGgHhIiJj
- 'registrationToken: "AbCdEfGhIjKlMnOpQrStUv:APA91bZaYxWvUtSrQpOnMlKjIhGfEdCbA9876543210ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-AaBbCcDdEeFfGgHhIiJj"'
references:
- https://firebase.google.com/docs/cloud-messaging/manage-tokens
# Registration tokens cant be safely live-validated using only the token value.

View file

@ -0,0 +1,43 @@
rules:
- name: Flutterwave Public Key
id: kingfisher.flutterwave.1
pattern: |
(?x)
\b
(
FLWPUBK(?:_TEST)?-[a-f0-9]{32}-X
)
\b
pattern_requirements:
min_digits: 4
min_lowercase: 4
min_entropy: 3.1
confidence: medium
examples:
- FLW_PUBLIC_KEY=FLWPUBK_TEST-32193bba8dab84e3d9c4525c85ea7a12-X
- data-PBFPubKey="FLWPUBK_TEST-589490616a6297324231c5e89b58f3f6-X"
references:
- https://developer.flutterwave.com/docs/authentication
- https://developer.flutterwave.com/v2.0/docs/api-keys
- name: Flutterwave Secret Key
id: kingfisher.flutterwave.2
pattern: |
(?x)
\b
(
FLWSECK(?:_TEST)?-[a-f0-9]{32}-X
)
\b
pattern_requirements:
min_digits: 4
min_lowercase: 8
min_entropy: 3.3
confidence: medium
examples:
- FLW_SECRET_KEY=FLWSECK_TEST-a514d8f1abd080db1502a144f22954dc-X
- 'Authorization: Bearer FLWSECK_TEST-5b1f0a33de9c41748c2a7e9b51d3c6af-X'
- seckey=FLWSECK-e6db11d1f8a6208de8cb2f94e293450e-X
references:
- https://developer.flutterwave.com/docs/authentication
- https://developer.flutterwave.com/v2.0/reference/api-request-and-response-standards

View file

@ -0,0 +1,29 @@
rules:
- name: Freemius Secret Key
id: kingfisher.freemius.1
pattern: |
(?xi)
["']secret_key["']
\s*=>\s*
["']
(
sk_[^"' \t\r\n]{29}
)
["']
pattern_requirements:
min_digits: 2
min_lowercase: 6
min_special_chars: 2
ignore_if_contains:
- xxxxxxxxx
- placeholder
min_entropy: 3.6
confidence: medium
examples:
- |
$config = array(
"secret_key" => "sk_ubb4yN3mzqGR2x8#P7r5&@*xC$utE",
);
references:
- https://freemius.com/help/documentation/wordpress-sdk/integrating-freemius-sdk/

View file

@ -0,0 +1,41 @@
rules:
- name: Fullstory API Key
id: kingfisher.fullstory.1
pattern: |
(?xi)
\b
(?:fullstory|fs_api|fullstory_api)
(?:.|[\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 | append: ':' | b64enc }}"
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid

View file

@ -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

View file

@ -0,0 +1,82 @@
rules:
- name: Help Scout Client ID
id: kingfisher.helpscout.1
pattern: |
(?xi)
\b
(?:help[\s_-]?scout|helpscout)
(?:.|[\n\r]){0,48}?
(?:client[\s_.-]*id|app[\s_.-]*id|application[\s_.-]*id)
(?:.|[\n\r]){0,16}?
(
[A-Za-z0-9]{10,40}
)
\b
pattern_requirements:
min_digits: 1
min_uppercase: 1
min_lowercase: 1
min_entropy: 3.1
confidence: medium
visible: false
examples:
- HELPSCOUT_CLIENT_ID=Ab12Cd34Ef56Gh78Ij90
- 'helpscout_app_id: "a1B2c3D4e5F6g7H8i9J0K1L2"'
references:
- https://developer.helpscout.com/mailbox-api/
- name: Help Scout OAuth Client Secret
id: kingfisher.helpscout.2
pattern: |
(?xi)
\b
(?:help[\s_-]?scout|helpscout)
(?:.|[\n\r]){0,48}?
(?:
client[\s_.-]*secret
|
app[\s_.-]*secret
|
oauth[\s_.-]*secret
)
(?:.|[\n\r]){0,16}?
(
[A-Za-z0-9]{24,64}
)
\b
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 1
ignore_if_contains:
- example
- placeholder
- yoursecret
min_entropy: 3.7
confidence: medium
examples:
- HELPSCOUT_CLIENT_SECRET=a3B8f29E4d1C6a0578e23D9f41b6C8e2
- 'helpscout_client_secret: "E7d2A1f849c3B05d6e81F2a794c3D5b0"'
references:
- https://developer.helpscout.com/mailbox-api/
depends_on_rule:
- rule_id: kingfisher.helpscout.1
variable: CLIENT_ID
validation:
type: Http
content:
request:
method: POST
url: https://api.helpscout.net/v2/oauth2/token
headers:
Content-Type: application/x-www-form-urlencoded
Accept: application/json
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
words:
- '"access_token"'

View file

@ -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":"([^"]+)"[^{}]{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":"([^"]+)"[^{}]{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"

View file

@ -0,0 +1,129 @@
rules:
- name: JFrog Cloud Host
id: kingfisher.jfrog.1
pattern: |
(?xi)
\b
(
[a-z0-9]
(?:
[a-z0-9\-]{0,61}
[a-z0-9]
)?
\.jfrog\.io
)
\b
min_entropy: 2.5
confidence: medium
visible: false
examples:
- company.jfrog.io
- my-team.jfrog.io
references:
- https://jfrog.com/help/api/khub/documents/xrOb4ANk_fqUw5nctnsIww/content
- name: JFrog API Key
id: kingfisher.jfrog.2
pattern: |
(?xi)
\b
(?:
jfrog |
artifactory |
bintray |
xray
)
(?:.|[\n\r]){0,32}?
(?:
api[_-]?key |
password |
token |
secret
)
(?:.|[\n\r]){0,12}?
(
[A-Za-z0-9]{73}
)
\b
pattern_requirements:
min_digits: 4
min_uppercase: 2
min_lowercase: 6
min_entropy: 3.5
confidence: medium
examples:
- jfrog_api_key=Ab12Cd34Ef56Gh78Ij90Kl12Mn34Op56Qr78St90Uv12Wx34Yz56Ab78Cd90Ef12Gh34Ij5Kl
- jfrog_password=Ab12Cd34Ef56Gh78Ij90Kl12Mn34Op56Qr78St90Uv12Wx34Yz56Ab78Cd90Ef12Gh34Ij5Kl
references:
- https://jfrog.com/help/api/khub/documents/xrOb4ANk_fqUw5nctnsIww/content
- https://jfrog.com/article/access-service/
depends_on_rule:
- rule_id: kingfisher.jfrog.1
variable: JFROG_HOST
validation:
type: Http
content:
request:
method: GET
url: https://{{ JFROG_HOST }}/artifactory/api/repositories
headers:
X-JFrog-Art-Api: '{{ TOKEN }}'
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid
- name: JFrog Identity Token
id: kingfisher.jfrog.3
pattern: |
(?xi)
\b
(?:
jfrog |
artifactory |
bintray |
xray
)
(?:.|[\n\r]){0,32}?
(?:
identity[_-]?token |
access[_-]?token |
bearer |
token
)
(?:.|[\n\r]){0,12}?
(
[A-Za-z0-9]{64}
)
\b
pattern_requirements:
min_digits: 4
min_uppercase: 2
min_lowercase: 6
min_entropy: 3.4
confidence: medium
examples:
- jfrog_identity_token=Ab12Cd34Ef56Gh78Ij90Kl12Mn34Op56Qr78St90Uv12Wx34Yz56Ab78Cd90Ef12
- artifactory_access_token=Zx12Cv34Bn56Mm78Aa90Ss12Dd34Ff56Gg78Hh90Jj12Kk34Ll56Qq78Ww90Ee12
references:
- https://jfrog.com/help/api/khub/documents/xrOb4ANk_fqUw5nctnsIww/content
- https://jfrog.com/article/access-service/
depends_on_rule:
- rule_id: kingfisher.jfrog.1
variable: JFROG_HOST
validation:
type: Http
content:
request:
method: GET
url: https://{{ JFROG_HOST }}/artifactory/api/repositories
headers:
Authorization: 'Bearer {{ TOKEN }}'
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid

View file

@ -0,0 +1,31 @@
rules:
- name: Kraken API Secret
id: kingfisher.kraken.1
pattern: |
(?xi)
\b
kraken
(?:.|[\n\r]){0,32}?
(?:
api[_-]?secret |
secret |
private[_-]?key |
token
)
(?:.|[\n\r]){0,12}?
(
[A-Za-z0-9+/=_-]{80,90}
)
(?:[^A-Za-z0-9+/=_-]|$)
pattern_requirements:
min_digits: 4
min_uppercase: 2
min_lowercase: 8
min_entropy: 4.0
confidence: medium
examples:
- KRAKEN_API_SECRET=dGhpcy1sb29rcy1saWtlLWEtYmFzZTY0LWtyYWtlbi1zZWNyZXQtdGhhdC1pcy1sb25nLWVub3VnaA==
- kraken_secret="Aq1Bq2Cr3Ds4Et5Fu6Gv7Hw8Ix9Jy0Kz1La2Mb3Nc4Od5Pe6Qf7Rg8Sh9Ti0Uj1Vk2Wm3Xn4Yo5Za6Bc7"
references:
- https://docs.kraken.com/api/docs/guides/spot-rest-auth/
- https://docs.kraken.com/api/docs/rest-api/get-account-balance/

View file

@ -0,0 +1,116 @@
rules:
- name: Kubernetes API Server URL
id: kingfisher.kubernetes.1
pattern: |
(?xi)
\b
(?:
kube(?:rnetes)?(?:_api)?_server
|
api_server
)
\s*[:=]\s*
["']?
(
https://
(?:
\[[0-9a-f:.]+\]
|
[a-z0-9]
[a-z0-9.-]{1,253}
)
(?::\d{2,5})?
)
["']?
min_entropy: 2.0
confidence: medium
visible: false
examples:
- "kube_server: https://10.96.0.1:443"
- KUBE_API_SERVER=https://api.cluster.example.com:6443
references:
- https://kubernetes.io/docs/reference/access-authn-authz/authentication/
- name: Kubernetes Bootstrap Token
id: kingfisher.kubernetes.2
pattern: |
(?xi)
\b
(?:
bootstrap(?:[-_ ]token)?
|
tls[-_ ]bootstrap[-_ ]token
)
(?:.|[\n\r]){0,12}?
(
[a-z0-9]{6}
\.
[a-z0-9]{16}
)
\b
pattern_requirements:
min_digits: 2
min_entropy: 3.2
confidence: medium
examples:
- BOOTSTRAP_TOKEN=be8dfd.da8a689a46edc282
- --tls-bootstrap-token abcdef.1234567890abcdef
references:
- https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/
depends_on_rule:
- rule_id: kingfisher.kubernetes.1
variable: KUBE_API_SERVER
validation:
type: Http
content:
request:
method: GET
url: '{{ KUBE_API_SERVER }}/api/v1/namespaces?limit=1'
headers:
Accept: application/json
Authorization: 'Bearer {{ TOKEN }}'
response_matcher:
- report_response: true
- type: StatusMatch
status: [200, 403]
- type: JsonValid
- name: Kubernetes Bootstrap Token Pair
id: kingfisher.kubernetes.3
pattern: |
(?xis)
\btoken-id\b
\s*:\s*
(?P<TOKEN_ID>[a-z0-9]{6})
(?:.|[\n\r]){0,24}?
\btoken-secret\b
\s*:\s*
(?P<TOKEN_SECRET>[a-z0-9]{16})
\b
pattern_requirements:
min_digits: 2
min_entropy: 2.8
confidence: medium
examples:
- |
token-id: 07402b
token-secret: f395accd245ae53d
references:
- https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/
depends_on_rule:
- rule_id: kingfisher.kubernetes.1
variable: KUBE_API_SERVER
validation:
type: Http
content:
request:
method: GET
url: '{{ KUBE_API_SERVER }}/api/v1/namespaces?limit=1'
headers:
Accept: application/json
Authorization: 'Bearer {{ TOKEN_ID }}.{{ TOKEN_SECRET }}'
response_matcher:
- report_response: true
- type: StatusMatch
status: [200, 403]
- type: JsonValid

View file

@ -0,0 +1,62 @@
rules:
- name: KuCoin API Key
id: kingfisher.kucoin.1
pattern: |
(?xi)
\b
kucoin
(?:.|[\n\r]){0,32}?
(?:
api[_-]?key |
key
)
(?:.|[\n\r]){0,12}?
(
[a-f0-9]{24}
)
\b
pattern_requirements:
min_digits: 4
min_lowercase: 8
ignore_if_contains:
- xxxxxx
- your_api_key
min_entropy: 3.0
confidence: medium
examples:
- KUCOIN_API_KEY=4f4ecb6f11b1a70001c8e2ff
- 'kucoin_api_key: a1b2c3d4e5f60718293a4b5c'
references:
- https://www.kucoin.com/docs-new/authentication
- https://www.kucoin.com/docs-new/api-3470125
- name: KuCoin API Secret
id: kingfisher.kucoin.2
pattern: |
(?xi)
\b
kucoin
(?:.|[\n\r]){0,32}?
(?:
api[_-]?secret |
secret
)
(?:.|[\n\r]){0,12}?
(
[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}
)
\b
pattern_requirements:
min_digits: 6
min_lowercase: 8
ignore_if_contains:
- 00000000-0000-0000-0000-000000000000
- xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
min_entropy: 3.3
confidence: medium
examples:
- KUCOIN_API_SECRET=7d70f6c7-42e9-4261-8a8d-8ca2d5028d4f
- 'kucoin_secret: a1b2c3d4-e5f6-7890-abcd-ef1234567890'
references:
- https://www.kucoin.com/docs-new/authentication

View file

@ -0,0 +1,66 @@
rules:
- name: Octopus Deploy Server URL
id: kingfisher.octopusdeploy.1
pattern: |
(?xi)
(?:
\boctopus(?:[_\s.-]?deploy)?(?:[_\s.-]?(?:url|server|host))?\b
(?:.|[\n\r]){0,32}?
[:=]
|
\boctopus(?:url|server|host)\b
\s*[:=]
)
\s*["']?
(
https://
[A-Za-z0-9.-]+
(?::\d{2,5})?
)
["']?
min_entropy: 2.3
confidence: medium
visible: false
examples:
- OCTOPUS_URL=https://deploy.acme.example
- 'octopus_server: "https://octopus.internal.example:8443"'
references:
- https://octopus.com/docs/octopus-rest-api/getting-started
- name: Octopus Deploy API Key
id: kingfisher.octopusdeploy.2
pattern: |
(?x)
\b
(
API-[A-Z0-9]{26}
)
\b
pattern_requirements:
min_digits: 4
min_uppercase: 4
min_entropy: 3.4
confidence: medium
examples:
- OCTOPUS_API_KEY=API-ZNRMR7SL6L3ATMOIK7GKJDKLPY
- 'set apikey="API-A1B2C3D4E5F6G7H8J9K0LMNOPQ"'
references:
- https://octopus.com/docs/octopus-rest-api/getting-started
- https://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key
depends_on_rule:
- rule_id: kingfisher.octopusdeploy.1
variable: OCTOPUS_URL
validation:
type: Http
content:
request:
method: GET
url: '{{ OCTOPUS_URL }}/api'
headers:
X-Octopus-ApiKey: '{{ TOKEN }}'
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid

View file

@ -0,0 +1,78 @@
rules:
- name: OpenShift API Server URL
id: kingfisher.openshift.1
pattern: |
(?xi)
(?:
\boc\s+login\b
(?:.|[\n\r]){0,128}?
--server=
|
\bopenshift(?:[_-]?(?:server|api(?:[_-]?server)?|cluster(?:[_-]?url)?))?\b
(?:.|[\n\r]){0,32}?
\b(?:server|api(?:[_-]?server)?|cluster(?:[_-]?url)?)?\b
\s*[:=]\s*
)
["']?
(
https://
(?:
\[[0-9a-f:.]+\]
|
[a-z0-9]
[a-z0-9.-]{1,253}
)
(?::\d{2,5})?
)
["']?
min_entropy: 2.0
confidence: medium
visible: false
examples:
- oc login --token=sha256~kV46hPnEYhCWFnB85r5NrprAxggzgb6GOeLbgcKNsH0 --server=https://api.cluster.example.com:6443
- OPENSHIFT_SERVER=https://api.dev-cluster.example.net:6443
references:
- https://docs.redhat.com/en/documentation/openshift_container_platform/4.9/html-single/authentication_and_authorization/index
- https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/user_and_group_apis/user-user-openshift-io-v1
- name: OpenShift OAuth Access Token
id: kingfisher.openshift.2
pattern: |
(?x)
\b
(
sha256~[A-Za-z0-9_-]{43}
)
(?:[^A-Za-z0-9_-]|$)
pattern_requirements:
min_digits: 3
min_uppercase: 1
min_lowercase: 3
ignore_if_contains:
- put_your_token_here
- xxxxxx
min_entropy: 3.8
confidence: medium
examples:
- 'Authorization: Bearer sha256~kV46hPnEYhCWFnB85r5NrprAxggzgb6GOeLbgcKNsH0'
- oc login --token=sha256~ZBMKw9VAayhdnyANaHvjJeXDiGwA7Fsr5gtLKj3-eh- --server=https://api.cluster.example.com:6443
references:
- https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/oauth_apis/oauthaccesstoken-oauth-openshift-io-v1
- https://docs.redhat.com/en/documentation/openshift_container_platform/4.9/html-single/authentication_and_authorization/index
depends_on_rule:
- rule_id: kingfisher.openshift.1
variable: OPENSHIFT_API_SERVER
validation:
type: Http
content:
request:
method: GET
url: '{{ OPENSHIFT_API_SERVER }}/apis/user.openshift.io/v1/users'
headers:
Authorization: 'Bearer {{ TOKEN }}'
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200, 403]
- type: JsonValid

View file

@ -0,0 +1,85 @@
rules:
- name: Polymarket Builder Secret
id: kingfisher.polymarket.1
pattern: |
(?xi)
\b
poly(?:market)?
(?:.|[\n\r]){0,32}?
(?:builder|api)?
(?:.|[\n\r]){0,16}?
secret
(?:.|[\n\r]){0,12}?
(
[A-Za-z0-9+/]{40,88}={0,2}
)
(?:[^A-Za-z0-9+/=]|$)
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 8
min_entropy: 3.6
confidence: medium
visible: false
examples:
- POLY_BUILDER_SECRET=QmFzZTY0U2VjcmV0VGVzdEtleTEyMzQ1Njc4OTBBQkNERUY=
- 'polymarket_builder_secret: Q29tcGxleFNlY3JldE1hdGVyaWFsMTIzNDU2Nzg5MDEyMzQ='
references:
- https://docs.polymarket.com/trading/orders/attribution
- name: Polymarket Builder Passphrase
id: kingfisher.polymarket.2
pattern: |
(?xi)
\b
poly(?:market)?
(?:.|[\n\r]){0,32}?
(?:builder|api)?
(?:.|[\n\r]){0,16}?
passphrase
(?:.|[\n\r]){0,12}?
(
[A-Za-z0-9_]{8,128}
)
\b
pattern_requirements:
min_digits: 1
min_uppercase: 1
min_lowercase: 4
ignore_if_contains:
- example
- placeholder
min_entropy: 3.0
confidence: medium
visible: false
examples:
- POLY_BUILDER_PASSPHRASE=BuilderPass_2026
- 'polymarket_passphrase: AlphaPass_7788'
references:
- https://docs.polymarket.com/trading/orders/attribution
- name: Polymarket Builder API Key
id: kingfisher.polymarket.3
pattern: |
(?xi)
\b
poly(?:market)?
(?:.|[\n\r]){0,32}?
(?:builder|api)?
(?:.|[\n\r]){0,16}?
key
(?:.|[\n\r]){0,12}?
(
[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}
)
\b
pattern_requirements:
min_digits: 6
min_lowercase: 8
min_entropy: 3.2
confidence: medium
examples:
- POLY_BUILDER_API_KEY=12345678-abcd-1234-efab-1234567890ab
- 'polymarket_api_key: a1b2c3d4-e5f6-789a-bcde-f0123456789a'
references:
- https://docs.polymarket.com/trading/orders/attribution

View file

@ -0,0 +1,58 @@
rules:
- name: Private AI API Key
id: kingfisher.privateai.1
pattern: |
(?xi)
\b
(?:
private[_-]?ai
|
limina
)
(?:.|[\n\r]){0,32}?
(?:
api[_-]?key
|
x-api-key
|
token
)
(?:.|[\n\r]){0,12}?
(
[a-z0-9]{32}
)
\b
pattern_requirements:
min_digits: 4
min_lowercase: 8
ignore_if_contains:
- example
- placeholder
- insert
- your
min_entropy: 3.5
confidence: medium
examples:
- PRIVATEAI_API_KEY=4fa2d7c81be9063d4ea8bc1f6d2a7e9c
- 'privateai_x_api_key: 2ab4d6e8f0c1a3b5d7e9f1a2b4c6d8e0'
references:
- https://docs.private-ai.com/fundamentals/getting-started
- https://docs.private-ai.com/reference/4.0.0/operation/ner_text_ner_text_post/
validation:
type: Http
content:
request:
method: POST
url: https://api.private-ai.com/community/v4/process/text
headers:
Content-Type: application/json
x-api-key: '{{ TOKEN }}'
body: '{"text":["Hello Jane Doe"]}'
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid
- type: WordMatch
words:
- '"processed_text"'

View file

@ -0,0 +1,63 @@
rules:
- name: SettleMint Personal Access Token
id: kingfisher.settlemint.1
pattern: |
(?x)
\b
(
sm_pat_[A-Za-z0-9]{16}
)
\b
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 4
min_entropy: 3.2
confidence: medium
examples:
- SETTLEMINT_ACCESS_TOKEN=sm_pat_A1b2C3d4E5f6G7h8
- settlemint connect --pat=sm_pat_Z9y8X7w6V5u4T3s2
references:
- https://console.settlemint.com/documentation/blockchain-platform/platform-components/security-and-authentication/personal-access-tokens
- name: SettleMint Application Access Token
id: kingfisher.settlemint.2
pattern: |
(?x)
\b
(
sm_aat_[A-Za-z0-9]{16}
)
\b
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 4
min_entropy: 3.2
confidence: medium
examples:
- BLOCKSCOUT_SETTLEMINT_APPLICATION_ACCESS_TOKEN=sm_aat_A1b2C3d4E5f6G7h8
- 'x-auth-token: sm_aat_Z9y8X7w6V5u4T3s2'
references:
- https://console.settlemint.com/documentation/blockchain-platform/platform-components/security-and-authentication/application-access-tokens
- name: SettleMint Service Access Token
id: kingfisher.settlemint.3
pattern: |
(?x)
\b
(
sm_sat_[A-Za-z0-9]{16}
)
\b
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 4
min_entropy: 3.2
confidence: medium
examples:
- SETTLEMINT_SERVICE_TOKEN=sm_sat_A1b2C3d4E5f6G7h8
- 'Authorization: Bearer sm_sat_Z9y8X7w6V5u4T3s2'
references:
- https://console.settlemint.com/documentation

View file

@ -0,0 +1,58 @@
rules:
- name: Sidekiq Enterprise Credential
id: kingfisher.sidekiq.1
pattern: |
(?xi)
\b
(?:
BUNDLE_ENTERPRISE__CONTRIBSYS__COM
|
BUNDLE_GEMS__CONTRIBSYS__COM
)
\s*[:=]\s*
["']?
(
[a-f0-9]{8}:[a-f0-9]{8}
)
["']?
\b
pattern_requirements:
min_digits: 4
min_lowercase: 4
min_entropy: 2.8
confidence: medium
examples:
- BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafe1234:dead5678
- 'export BUNDLE_GEMS__CONTRIBSYS__COM="ca1eb4b3:d3ad533f"'
- name: Sidekiq Sensitive URL
id: kingfisher.sidekiq.2
pattern: |
(?xi)
(
https?://
[a-f0-9]{8}:[a-f0-9]{8}
@
(?:
gems\.contribsys\.com
|
enterprise\.contribsys\.com
)
(?:
/[^ \t\r\n"'<>]*
|
\?[^ \t\r\n"'<>]*
|
\#[^ \t\r\n"'<>]*
|
:[0-9]{1,5}(?:/[^ \t\r\n"'<>]*)?
)?
)
pattern_requirements:
min_digits: 4
min_lowercase: 4
min_entropy: 2.8
confidence: medium
examples:
- https://cafe1234:dead5678@gems.contribsys.com/
- http://ca1eb4b3:d3ad533f@enterprise.contribsys.com:80/path?param1=true

View file

@ -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}=?
)
(?:[^A-Za-z0-9_=-]|$)
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"'

View file

@ -0,0 +1,31 @@
rules:
- name: Trello API Token
id: kingfisher.trello.1
pattern: |
(?xi)
\b
trello
(?:.|[\n\r]){0,32}?
(?:
token |
api[_-]?token |
access[_-]?token
)
(?:.|[\n\r]){0,12}?
(
[A-Za-z0-9]{32}
)
\b
pattern_requirements:
min_digits: 2
min_lowercase: 6
ignore_if_contains:
- yourtoken
- placeholder
min_entropy: 3.1
confidence: medium
examples:
- TRELLO_TOKEN=0a1b2c3d4e5f6g7h8i9j0k1l2m3n4p5q
- trello_access_token="Ab12Cd34Ef56Gh78Ij90Kl12Mn34Op56"
references:
- https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/

View file

@ -0,0 +1,20 @@
rules:
- name: Zapier Webhook URL
id: kingfisher.zapier.1
pattern: |
(?x)
\b
(
https://hooks\.zapier\.com/hooks/catch/
[0-9]{5,10}
/
[a-z0-9]{5,12}
/?
)
min_entropy: 3.4
confidence: medium
examples:
- ZAPIER_WEBHOOK=https://hooks.zapier.com/hooks/catch/11595998/3ouwv7m/
- webhook_url="https://hooks.zapier.com/hooks/catch/2929690/ztd17n/"
references:
- https://help.zapier.com/hc/en-us/articles/8496288690317-Trigger-Zaps-from-webhooks

View file

@ -0,0 +1,105 @@
rules:
- name: Zendesk Subdomain
id: kingfisher.zendesk.1
pattern: |
(?xi)
\b
(
[a-z0-9]
(?:
[a-z0-9-]{0,61}
[a-z0-9]
)?
\.zendesk\.com
)
\b
min_entropy: 2.0
confidence: medium
visible: false
examples:
- acme-support.zendesk.com
- helpdesk-prod.zendesk.com
references:
- https://developer.zendesk.com/api-reference/introduction/doc-conventions/
- name: Zendesk Account Email
id: kingfisher.zendesk.2
pattern: |
(?xi)
\b
(?:zendesk|zd)
(?:.|[\n\r]){0,32}?
(?:email|user(?:name)?)
(?:.|[\n\r]){0,12}?
(
[A-Za-z0-9._%+\-]+
@
[A-Za-z0-9.\-]+\.[A-Za-z]{2,}
)
\b
min_entropy: 2.0
confidence: medium
visible: false
examples:
- ZENDESK_EMAIL=agent@example.com
- 'zendesk_user: "support.bot@example.org"'
references:
- https://developer.zendesk.com/api-reference/introduction/security-and-auth/
- name: Zendesk API Token
id: kingfisher.zendesk.3
pattern: |
(?xi)
\b
(?:zendesk|zd)
(?:.|[\n\r]){0,48}?
(?:
api[\s_.-]*token
|
token
|
api[\s_.-]*key
)
(?:.|[\n\r]){0,16}?
(
[A-Za-z0-9]{40}
)
\b
pattern_requirements:
min_digits: 2
min_uppercase: 1
min_lowercase: 1
ignore_if_contains:
- example
- placeholder
- yourtoken
min_entropy: 3.8
confidence: medium
examples:
- ZENDESK_API_TOKEN=a3B8f29E4d1C6a0578e23D9f41b6C8e2qR7tY4uI
- zendesk_token="E7d2A1f849c3B05d6e81F2a794c3D5b0pQ8wX1zK"
references:
- https://developer.zendesk.com/api-reference/introduction/security-and-auth/
- https://developer.zendesk.com/api-reference/ticketing/account-configuration/current_user/
depends_on_rule:
- rule_id: kingfisher.zendesk.1
variable: ZENDESK_HOST
- rule_id: kingfisher.zendesk.2
variable: ZENDESK_EMAIL
validation:
type: Http
content:
request:
method: GET
url: 'https://{{ ZENDESK_HOST }}/api/v2/users/me.json'
headers:
Accept: application/json
Authorization: 'Basic {{ ZENDESK_EMAIL | append: "/token:" | append: TOKEN | b64enc }}'
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid
- type: WordMatch
words:
- '"user"'

View file

@ -182,6 +182,45 @@ impl Filter for HmacSha256Filter {
}
}
// ── HMAC-SHA256 with base64-encoded key ──────────────────────────────────
#[derive(Debug, FilterParameters)]
struct HmacB64KeyArgs {
#[parameter(description = "Base64-encoded HMAC key", arg_type = "str")]
key: Expression,
}
#[derive(Clone, ParseFilter, FilterReflection, Default)]
#[filter(
name = "hmac_sha256_b64key",
description = "HMAC-SHA256 with a base64-encoded key decodes the key to raw bytes before signing. Returns Base64.",
parameters(HmacB64KeyArgs),
parsed(HmacSha256B64KeyFilter)
)]
pub struct HmacSha256B64Key;
#[derive(Debug, FromFilterParameters, Display_filter)]
#[name = "hmac_sha256_b64key"]
struct HmacSha256B64KeyFilter {
#[parameters]
args: HmacB64KeyArgs,
}
impl Filter for HmacSha256B64KeyFilter {
fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
let args = self.args.evaluate(runtime)?;
let key_b64 = args.key.to_kstr();
let key_bytes = general_purpose::STANDARD.decode(key_b64.as_bytes()).map_err(|e| {
LiquidError::with_msg(format!("hmac_sha256_b64key: invalid base64 key: {e}"))
})?;
let mut mac = Hmac::<Sha256>::new_from_slice(&key_bytes)
.map_err(|e| LiquidError::with_msg(format!("hmac_sha256_b64key: {e}")))?;
mac.update(input.to_kstr().as_bytes());
Ok(Value::scalar(general_purpose::STANDARD.encode(mac.finalize().into_bytes())))
}
}
// ── HMAC-SHA1 ─────────────────────────────────────────────
#[derive(Debug, FilterParameters)]
struct HmacSha1Args {
@ -439,6 +478,14 @@ impl Filter for B64DecFilter {
}
}
// {{ "any" | newline }} → "\n" (appends nothing, just returns a newline character)
static_filter!(
/// Returns a single newline character. Useful inside YAML block scalars where
/// a literal newline in the template would break indentation.
NewlineFilter, "newline",
|_input: &dyn ValueView| -> String { "\n".to_string() }
);
// -----------------------------------------------------------------------------
// Authentication & Security
// -----------------------------------------------------------------------------
@ -912,6 +959,7 @@ pub fn register_all(builder: liquid::ParserBuilder) -> liquid::ParserBuilder {
.filter(JwtHeaderFilter::default())
.filter(B64EncFilter::default())
.filter(B64DecFilter::default())
.filter(NewlineFilter::default())
.filter(RandomStringFilter::default())
.filter(SuffixFilter::default())
.filter(PrefixFilter::default())
@ -923,6 +971,7 @@ pub fn register_all(builder: liquid::ParserBuilder) -> liquid::ParserBuilder {
.filter(Base62Filter::default())
.filter(Base36Filter::default())
.filter(HmacSha256::default())
.filter(HmacSha256B64Key::default())
.filter(HmacSha1::default())
.filter(HmacSha384::default())
}
@ -1073,6 +1122,21 @@ mod tests {
assert_eq!(render(r#"{{ "hi!" | hmac_sha256: "secret" }}"#), expect);
}
#[test]
fn hmac_sha256_b64key_filter() {
// Key is base64-encoded; the filter must decode it to raw bytes before HMAC.
let raw_key: &[u8] = &[0x00, 0x80, 0xFF, 0x42, 0xDE, 0xAD, 0xBE, 0xEF];
let b64_key = general_purpose::STANDARD.encode(raw_key);
let data = b"hello azure";
let mut mac = Hmac::<Sha256>::new_from_slice(raw_key).unwrap();
mac.update(data);
let expect = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
let template = format!(r#"{{{{ "hello azure" | hmac_sha256_b64key: "{b64_key}" }}}}"#);
assert_eq!(render(&template), expect);
}
#[test]
fn hmac_sha384_filter() {
let key = b"topsecret";

View file

@ -315,10 +315,10 @@ let template = parser.parse("{{ secret | sha256 }}")?;
Available filters:
- **Encoding**: `b64enc`, `b64dec`, `b64url_enc`, `url_encode`, `json_escape`
- **Hashing**: `sha256`, `crc32`, `crc32_dec`, `crc32_hex`
- **HMAC**: `hmac_sha256`, `hmac_sha384`, `hmac_sha1`
- **Hashing**: `sha256`, `crc32`, `crc32_dec`, `crc32_hex`, `crc32_le_b64`
- **HMAC**: `hmac_sha256`, `hmac_sha384`, `hmac_sha1`, `hmac_sha256_b64key`
- **Encoding**: `base62`, `base36`
- **Strings**: `prefix`, `suffix`, `replace`, `lstrip_chars`, `random_string`
- **Strings**: `prefix`, `suffix`, `replace`, `lstrip_chars`, `random_string`, `newline`
- **Time**: `unix_timestamp`, `iso_timestamp`, `iso_timestamp_no_frac`
- **Other**: `uuid`, `jwt_header`

View file

@ -468,6 +468,7 @@ Below is the complete list of Liquid filters available in Kingfisher, along with
| `hmac_sha1` | `key` (string) | Computes HMAC-SHA1 over the input, returns Base64-encoded result. | `{{ TOKEN \| hmac_sha1: "secret-key" }}` |
| `hmac_sha256` | `key` (string) | Computes HMAC-SHA256 over the input, returns Base64-encoded result. | `{{ TOKEN \| hmac_sha256: "secret-key" }}` |
| `hmac_sha384` | `key` (string) | Computes HMAC-SHA384 over the input, returns Base64-encoded result. | `{{ TOKEN \| hmac_sha384: "secret-key" }}` |
| `hmac_sha256_b64key` | `key` (string, base64-encoded) | Decodes the key from Base64 to raw bytes, then computes HMAC-SHA256. Returns Base64. Use for Azure SAS and other protocols where the signing key is base64-encoded. | `{{ to_sign \| hmac_sha256_b64key: TOKEN }}` |
| `random_string` | `len` (integer, optional) | Generates a cryptographically-secure random alphanumeric string of the specified length (default: 32). | `{{ "" \| random_string: 16 }}` |
| `prefix` | `len` (integer, optional) | Returns the first `len` characters from the string (default: full). | `{{ TOKEN \| prefix: 6 }}` |
| `suffix` | `len` (integer, optional) | Returns the last `len` characters from the string (default: full). | `{{ TOKEN \| suffix: 6 }}` |
@ -480,6 +481,8 @@ Below is the complete list of Liquid filters available in Kingfisher, along with
| `uuid` | | Generates a random UUIDv4 string. | `{{ "" \| uuid }}` |
| `jwt_header` | | Builds a minimal JWT header JSON (`{"typ":"JWT","alg":…}`) and Base64URL-encodes it. | `{{ "HS256" \| jwt_header }}` |
| `replace` | `from` (string), `to` (string) | Replaces every occurrence of `from` with `to` in the input string. | `{{ "hello world" \| replace: "world", "mars" }}` |
| `newline` | | Returns a single newline character (`\n`). Useful inside YAML block scalars where a literal newline would break indentation. | `{{ "" \| newline }}` |
| `base36` | `width` (integer, optional) | Encodes the input number as Base36, left-padding with zeros as needed. | `{{ TOKEN \| crc32 \| base36: 6 }}` |
**Chaining & Composition:** Filters can be stacked; e.g.: