From bc1093ca4affab268b6618c68eb83a190550f7e5 Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Sun, 15 Mar 2026 13:59:07 -0700 Subject: [PATCH] v1.90.0 --- CHANGELOG.md | 4 + Cargo.toml | 2 +- README.md | 2 + .../data/rules/artifactory.yml | 24 ++++++ .../data/rules/cloudflare.yml | 37 +++++++- .../kingfisher-rules/data/rules/confluent.yml | 28 +++++- .../kingfisher-rules/data/rules/doppler.yml | 86 ++++++++++++++++++- crates/kingfisher-rules/data/rules/mapbox.yml | 30 +++++++ .../data/rules/particle.io.yml | 28 +++++- crates/kingfisher-rules/data/rules/twitch.yml | 34 +++++++- crates/kingfisher-rules/data/rules/vercel.yml | 39 +++++++++ docs/REVOCATION_PROVIDERS.md | 42 +++++++++ 12 files changed, 350 insertions(+), 6 deletions(-) create mode 100644 docs/REVOCATION_PROVIDERS.md diff --git a/CHANGELOG.md b/CHANGELOG.md index b2df903..ea2e8d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [v1.90.0] +- Expanded built-in revocation support with new YAML revocation flows for Cloudflare, Confluent, Doppler, Mapbox, Particle.io, Twitch, and additional Vercel token formats. +- Added revocation coverage documentation: new `docs/REVOCATION_PROVIDERS.md` matrix and README links highlighting supported revocation providers/rule IDs. + ## [v1.89.0] - Access Map: added Microsoft Teams provider. Parses Incoming Webhook URLs (legacy and workflow-based) to extract tenant and webhook identity, probes for active status, and reports channel-level blast radius. Supports standalone `access-map microsoftteams` (alias `msteams`) and automatic mapping for validated `kingfisher.msteams.*` and `kingfisher.microsoftteamswebhook.*` findings. - Added Microsoft Teams scan target: `kingfisher scan teams "QUERY"` searches Teams messages via Microsoft Graph Search API and scans them for secrets, mirroring the Slack integration. diff --git a/Cargo.toml b/Cargo.toml index 0315541..37cc75e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ http = "1.4" [package] name = "kingfisher" -version = "1.89.0" +version = "1.90.0" description = "MongoDB's blazingly fast and accurate secret scanning and validation tool" edition.workspace = true rust-version.workspace = true diff --git a/README.md b/README.md index f7af10c..e113b49 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Kingfisher is a high-performance, open source secret detection tool for source c - **Performance**: multithreaded, Hyperscan‑powered scanning built for huge codebases - **Extensible rules**: hundreds of built-in detectors plus YAML-defined custom rules ([docs/RULES.md](/docs/RULES.md)) - **Validate & Revoke**: live validation of discovered secrets, plus direct revocation for supported platforms (GitHub, GitLab, Slack, AWS, GCP, and more) ([docs/USAGE.md](/docs/USAGE.md)) +- **Revocation support matrix**: current built-in revocation coverage across providers and rule IDs ([docs/REVOCATION_PROVIDERS.md](/docs/REVOCATION_PROVIDERS.md)) - **Blast Radius Mapping**: instantly map leaked keys to their effective cloud identities and exposed resources with `--access-map`. Supports AWS, GCP, Azure, GitHub, GitLab, Slack, Microsoft Teams, and more. - **Broad AI SaaS coverage**: finds and validates tokens for OpenAI, Anthropic, Google Gemini, Cohere, AWS Bedrock, Voyage AI, Mistral, Stability AI, Replicate, xAI (Grok), Ollama, Langchain, Perplexity, Weights & Biases, Cerebras, Friendli, Fireworks.ai, NVIDIA NIM, Together.ai, Zhipu, and many more - **Compressed Files**: Supports extracting and scanning compressed files for secrets @@ -645,6 +646,7 @@ kingfisher scan /tmp/repo --branch feature-1 \ | [ACCESS_MAP.md](docs/ACCESS_MAP.md) | Access map: supported tokens and credential formats (GitHub/GitLab/Slack/AWS/GCP/Azure Storage/Postgres/MongoDB/Microsoft Teams) | | [ADVANCED.md](docs/ADVANCED.md) | Advanced features: baselines, confidence levels, validation tuning, CI scanning, and more | | [RULES.md](docs/RULES.md) | Writing custom detection rules, pattern requirements, and checksum intelligence | +| [REVOCATION_PROVIDERS.md](docs/REVOCATION_PROVIDERS.md) | Built-in revocation coverage by provider and rule ID | | [BASELINE.md](docs/BASELINE.md) | Baseline management for tracking known secrets and detecting new ones | | [LIBRARY.md](docs/LIBRARY.md) | Using Kingfisher as a Rust library in your own applications | | [FINGERPRINT.md](docs/FINGERPRINT.md) | Understanding finding fingerprints and deduplication | diff --git a/crates/kingfisher-rules/data/rules/artifactory.yml b/crates/kingfisher-rules/data/rules/artifactory.yml index 18f9d69..8116f0a 100644 --- a/crates/kingfisher-rules/data/rules/artifactory.yml +++ b/crates/kingfisher-rules/data/rules/artifactory.yml @@ -100,3 +100,27 @@ rules: depends_on_rule: - rule_id: "kingfisher.artifactory.2" variable: JFROGURL + + - name: Artifactory NPM Auth (base64) + id: kingfisher.artifactory.4 + pattern: | + (?x) + // [A-Za-z0-9._:-]+ + /artifactory/api/npm/[A-Za-z0-9._\-/]+/:_auth + \s*=\s* + ( + [A-Za-z0-9+/]{20,}={0,2} + ) + \b + min_entropy: 3.0 + confidence: medium + examples: + - | + registry=https://artifactory.corp.company.com/artifactory/api/npm/npm-cloud/ + //artifactory.corp.company.com/artifactory/api/npm/npm-cloud/:_auth=bW9uZ29kYi1jbF81ZDpHMzhoZCE2aElSTjk= + - | + @cloud-company-js:registry=https://artifactory.corp.company.com/artifactory/api/npm/compass-data-explorer/ + //artifactory.corp.company.com/artifactory/api/npm/compass-data-explorer/:_auth=bW9uZ29kYi1jbF81ZDpHMzhoZCE2aElSTjk= + references: + - https://docs.npmjs.com/cli/v10/configuring-npm/npmrc + - https://jfrog.com/help/r/jfrog-platform-administration-documentation/npm-registry diff --git a/crates/kingfisher-rules/data/rules/cloudflare.yml b/crates/kingfisher-rules/data/rules/cloudflare.yml index 295fdd6..2458191 100644 --- a/crates/kingfisher-rules/data/rules/cloudflare.yml +++ b/crates/kingfisher-rules/data/rules/cloudflare.yml @@ -24,6 +24,7 @@ rules: cloudflare_key="y3u7gjcxzpboe2hs50hvuewsx10koco3z327z_1i" references: - https://developers.cloudflare.com/api/resources/user/subresources/tokens/methods/verify/ + - https://developers.cloudflare.com/api/resources/user/subresources/tokens/methods/delete/ validation: type: Http content: @@ -38,6 +39,40 @@ rules: - 200 type: StatusMatch url: https://api.cloudflare.com/client/v4/user/tokens/verify + revocation: + type: HttpMultiStep + content: + steps: + - name: lookup_token_id + request: + method: GET + url: https://api.cloudflare.com/client/v4/user/tokens/verify + headers: + Authorization: Bearer {{ TOKEN }} + Accept: application/json + response_matcher: + - type: StatusMatch + status: [200] + - type: JsonValid + extract: + TOKEN_ID: + type: JsonPath + path: "$.result.id" + + - name: revoke_token + request: + method: DELETE + url: https://api.cloudflare.com/client/v4/user/tokens/{{ TOKEN_ID }} + headers: + Authorization: Bearer {{ TOKEN }} + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"success":true'] + match_all_words: true - name: Cloudflare CA Key id: kingfisher.cloudflare.2 @@ -74,4 +109,4 @@ rules: - status: - 200 type: StatusMatch - url: https://api.cloudflare.com/client/v4/certificates?per_page=1 \ No newline at end of file + url: https://api.cloudflare.com/client/v4/certificates?per_page=1 diff --git a/crates/kingfisher-rules/data/rules/confluent.yml b/crates/kingfisher-rules/data/rules/confluent.yml index f8a1411..c70c9c6 100644 --- a/crates/kingfisher-rules/data/rules/confluent.yml +++ b/crates/kingfisher-rules/data/rules/confluent.yml @@ -39,6 +39,7 @@ rules: - kafka_token=cbaDEFGHIJKLMNOPQRSTUVWXYZ3214567890cbadefghijklmnopqrstuvwxyzAB references: - https://docs.confluent.io/cloud/current/api.html#tag/API-Keys-(iamv2)/operation/getIamV2ApiKey + - https://docs.confluent.io/cloud/current/api.html#tag/API-Keys-(iamv2)/operation/deleteIamV2ApiKey validation: type: Http content: @@ -52,6 +53,18 @@ rules: - 200 type: StatusMatch url: https://api.confluent.cloud/iam/v2/api-keys/{{ CLIENTID }} + revocation: + type: Http + content: + request: + headers: + Authorization: 'Basic {{ CLIENTID | append: ":" | append: TOKEN | b64enc }}' + method: DELETE + url: https://api.confluent.cloud/iam/v2/api-keys/{{ CLIENTID }} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] depends_on_rule: - rule_id: "kingfisher.confluent.1" variable: CLIENTID @@ -76,6 +89,7 @@ rules: - confluent secret=cfltcUBElySxR0ubmwjcLaVic7aOYceZ1HzCyW9BbhBhC+KbPgaTcGc9S4HfrjhA references: - https://docs.confluent.io/cloud/current/api.html#tag/API-Keys-(iamv2)/operation/getIamV2ApiKey + - https://docs.confluent.io/cloud/current/api.html#tag/API-Keys-(iamv2)/operation/deleteIamV2ApiKey validation: type: Http content: @@ -89,6 +103,18 @@ rules: - 200 type: StatusMatch url: https://api.confluent.cloud/iam/v2/api-keys/{{ CLIENTID }} + revocation: + type: Http + content: + request: + headers: + Authorization: 'Basic {{ CLIENTID | append: ":" | append: TOKEN | b64enc }}' + method: DELETE + url: https://api.confluent.cloud/iam/v2/api-keys/{{ CLIENTID }} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] depends_on_rule: - rule_id: "kingfisher.confluent.1" - variable: CLIENTID \ No newline at end of file + variable: CLIENTID diff --git a/crates/kingfisher-rules/data/rules/doppler.yml b/crates/kingfisher-rules/data/rules/doppler.yml index dd120ab..e86f3b2 100644 --- a/crates/kingfisher-rules/data/rules/doppler.yml +++ b/crates/kingfisher-rules/data/rules/doppler.yml @@ -18,6 +18,7 @@ rules: - https://docs.doppler.com/reference/api - https://docs.doppler.com/reference/auth-token-formats - https://docs.doppler.com/reference/auth-me + - https://docs.doppler.com/reference/auth-revoke validation: type: Http content: @@ -32,6 +33,19 @@ rules: - type: StatusMatch status: - 200 + revocation: + type: Http + content: + request: + method: POST + url: https://api.doppler.com/v3/auth/revoke + headers: + Authorization: Bearer {{ TOKEN }} + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] - name: Doppler Personal Token id: kingfisher.doppler.2 pattern: | @@ -47,6 +61,7 @@ rules: - https://docs.doppler.com/reference/api - https://docs.doppler.com/reference/auth-token-formats - https://docs.doppler.com/reference/auth-me + - https://docs.doppler.com/reference/auth-revoke validation: type: Http content: @@ -61,6 +76,19 @@ rules: - type: StatusMatch status: - 200 + revocation: + type: Http + content: + request: + method: POST + url: https://api.doppler.com/v3/auth/revoke + headers: + Authorization: Bearer {{ TOKEN }} + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] - name: Doppler Service Token id: kingfisher.doppler.3 @@ -77,6 +105,7 @@ rules: - https://docs.doppler.com/reference/api - https://docs.doppler.com/reference/auth-token-formats - https://docs.doppler.com/reference/auth-me + - https://docs.doppler.com/reference/auth-revoke validation: type: Http content: @@ -91,6 +120,19 @@ rules: - type: StatusMatch status: - 200 + revocation: + type: Http + content: + request: + method: POST + url: https://api.doppler.com/v3/auth/revoke + headers: + Authorization: Bearer {{ TOKEN }} + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] - name: Doppler Service Account Token id: kingfisher.doppler.4 @@ -107,6 +149,7 @@ rules: - https://docs.doppler.com/reference/api - https://docs.doppler.com/reference/auth-token-formats - https://docs.doppler.com/reference/auth-me + - https://docs.doppler.com/reference/auth-revoke validation: type: Http content: @@ -121,6 +164,19 @@ rules: - type: StatusMatch status: - 200 + revocation: + type: Http + content: + request: + method: POST + url: https://api.doppler.com/v3/auth/revoke + headers: + Authorization: Bearer {{ TOKEN }} + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] - name: Doppler SCIM Token id: kingfisher.doppler.5 @@ -137,6 +193,7 @@ rules: - https://docs.doppler.com/reference/api - https://docs.doppler.com/reference/auth-token-formats - https://docs.doppler.com/reference/auth-me + - https://docs.doppler.com/reference/auth-revoke validation: type: Http content: @@ -151,6 +208,19 @@ rules: - type: StatusMatch status: - 200 + revocation: + type: Http + content: + request: + method: POST + url: https://api.doppler.com/v3/auth/revoke + headers: + Authorization: Bearer {{ TOKEN }} + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] - name: Doppler Audit Token id: kingfisher.doppler.6 @@ -167,6 +237,7 @@ rules: - https://docs.doppler.com/reference/api - https://docs.doppler.com/reference/auth-token-formats - https://docs.doppler.com/reference/auth-me + - https://docs.doppler.com/reference/auth-revoke validation: type: Http content: @@ -180,4 +251,17 @@ rules: - report_response: true - type: StatusMatch status: - - 200 \ No newline at end of file + - 200 + revocation: + type: Http + content: + request: + method: POST + url: https://api.doppler.com/v3/auth/revoke + headers: + Authorization: Bearer {{ TOKEN }} + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] diff --git a/crates/kingfisher-rules/data/rules/mapbox.yml b/crates/kingfisher-rules/data/rules/mapbox.yml index 313eb9e..260a824 100644 --- a/crates/kingfisher-rules/data/rules/mapbox.yml +++ b/crates/kingfisher-rules/data/rules/mapbox.yml @@ -44,6 +44,8 @@ rules: - 'export MAPBOX_SECRET_TOKEN=sk.eyJ1IjoiY2FwcGVsYWVyZSIsImEicf99c1BaTkZnIn0.P4lD1eHeSEx7AsBq1zbJ4g' references: - https://docs.mapbox.com/api/accounts/tokens/#token-format + - https://docs.mapbox.com/api/accounts/tokens/#retrieve-a-token + - https://docs.mapbox.com/api/accounts/tokens/#delete-a-token - https://docs.mapbox.com/help/getting-started/access-tokens/ - https://docs.mapbox.com/help/troubleshooting/how-to-use-mapbox-securely validation: @@ -57,6 +59,34 @@ rules: - type: StatusMatch status: [200] - type: JsonValid + revocation: + type: HttpMultiStep + content: + steps: + - name: lookup_token_metadata + request: + method: GET + url: https://api.mapbox.com/tokens/v2?access_token={{ TOKEN }} + response_matcher: + - type: StatusMatch + status: [200] + - type: JsonValid + extract: + TOKEN_USER: + type: JsonPath + path: "$.user" + TOKEN_AUTHORIZATION: + type: JsonPath + path: "$.authorization" + + - name: revoke_token + request: + method: DELETE + url: https://api.mapbox.com/tokens/v2/{{ TOKEN_USER }}/{{ TOKEN_AUTHORIZATION }}?access_token={{ TOKEN }} + response_matcher: + - report_response: true + - type: StatusMatch + status: [204] - name: Mapbox Temporary Access Token id: kingfisher.mapbox.3 diff --git a/crates/kingfisher-rules/data/rules/particle.io.yml b/crates/kingfisher-rules/data/rules/particle.io.yml index eb8dd6b..ee3a1c3 100644 --- a/crates/kingfisher-rules/data/rules/particle.io.yml +++ b/crates/kingfisher-rules/data/rules/particle.io.yml @@ -39,6 +39,19 @@ rules: - type: WordMatch match_all_words: true words: ['"username":'] + revocation: + type: Http + content: + request: + method: DELETE + url: https://api.particle.io/v1/access_tokens/current?access_token={{ TOKEN }} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + match_all_words: true + words: ['"ok":'] - name: particle.io Access Token id: kingfisher.particleio.2 @@ -73,4 +86,17 @@ rules: status: [200] - type: WordMatch match_all_words: true - words: ['"username":'] \ No newline at end of file + words: ['"username":'] + revocation: + type: Http + content: + request: + method: DELETE + url: https://api.particle.io/v1/access_tokens/current?access_token={{ TOKEN }} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + match_all_words: true + words: ['"ok":'] diff --git a/crates/kingfisher-rules/data/rules/twitch.yml b/crates/kingfisher-rules/data/rules/twitch.yml index 7139ccb..7cd365d 100644 --- a/crates/kingfisher-rules/data/rules/twitch.yml +++ b/crates/kingfisher-rules/data/rules/twitch.yml @@ -22,6 +22,7 @@ rules: - "twitch_api_token: '0123456789ABcdefghijklmnopqrst'" references: - https://dev.twitch.tv/docs/authentication/validate-tokens/ + - https://dev.twitch.tv/docs/authentication/revoke-tokens/ validation: type: Http content: @@ -35,4 +36,35 @@ rules: - report_response: true - type: StatusMatch status: [200] - - type: JsonValid \ No newline at end of file + - type: JsonValid + revocation: + type: HttpMultiStep + content: + steps: + - name: lookup_client_id + request: + method: GET + url: https://id.twitch.tv/oauth2/validate + headers: + Authorization: 'OAuth {{ TOKEN }}' + Accept: application/json + response_matcher: + - type: StatusMatch + status: [200] + - type: JsonValid + extract: + CLIENT_ID: + type: JsonPath + path: "$.client_id" + + - name: revoke_token + request: + method: POST + url: https://id.twitch.tv/oauth2/revoke + headers: + Content-Type: application/x-www-form-urlencoded + body: "client_id={{ CLIENT_ID | url_encode }}&token={{ TOKEN | url_encode }}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] diff --git a/crates/kingfisher-rules/data/rules/vercel.yml b/crates/kingfisher-rules/data/rules/vercel.yml index 49e6cad..efdfb37 100644 --- a/crates/kingfisher-rules/data/rules/vercel.yml +++ b/crates/kingfisher-rules/data/rules/vercel.yml @@ -34,9 +34,22 @@ rules: - '"user":' - '"email":' match_all_words: true + revocation: + type: Http + content: + request: + method: DELETE + url: https://api.vercel.com/v3/user/tokens/current + headers: + Authorization: "Bearer {{TOKEN}}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] references: - https://vercel.com/kb/guide/how-do-i-use-a-vercel-api-access-token - https://docs.vercel.com/docs/rest-api/reference/welcome#authentication + - https://vercel.com/docs/rest-api/reference/endpoints/authentication/delete-an-authentication-token examples: - "vercel-key = DdZV6ZDZW6Vpl7n7JqtrCE5i" - "vercel_token = zyMBA1qVEMAf4UNNZtCAbg6u" @@ -81,10 +94,23 @@ rules: - '"user":' - '"email":' match_all_words: true + revocation: + type: Http + content: + request: + method: DELETE + url: https://api.vercel.com/v3/user/tokens/current + headers: + Authorization: "Bearer {{TOKEN}}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] references: - https://vercel.com/kb/guide/how-do-i-use-a-vercel-api-access-token - https://docs.vercel.com/docs/rest-api/reference/welcome#authentication - https://vercel.com/changelog/new-token-formats-and-secret-scanning + - https://vercel.com/docs/rest-api/reference/endpoints/authentication/delete-an-authentication-token examples: - "vcp_35UYJwYZDigYATKhxJUAhPqRhit2Xe3dtiG60LsUTHeklEXDQ94Jafpu" - "vercel_access_token=vcp_4mcjwVDwqtVCVGWCcxRjdzGpkGZ3NkwXZv8ktcoQ0EG0dnjpMP1Rzi71" @@ -124,9 +150,22 @@ rules: words: - '"user":' match_all_words: false + revocation: + type: Http + content: + request: + method: DELETE + url: https://api.vercel.com/v3/user/tokens/current + headers: + Authorization: "Bearer {{TOKEN}}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200, 204] references: - https://vercel.com/docs/integrations/vercel-api-integrations#create-an-access-token - https://vercel.com/changelog/new-token-formats-and-secret-scanning + - https://vercel.com/docs/rest-api/reference/endpoints/authentication/delete-an-authentication-token examples: - "Vercel Integration Token: vci_35UYJwYZDigYATKhxJUAhPqRhit2Xe3dtiG60LsUTHeklEXDQ94Jafpu" diff --git a/docs/REVOCATION_PROVIDERS.md b/docs/REVOCATION_PROVIDERS.md new file mode 100644 index 0000000..45a4d53 --- /dev/null +++ b/docs/REVOCATION_PROVIDERS.md @@ -0,0 +1,42 @@ +# Revocation Support Matrix + +Kingfisher supports direct secret revocation through rule-level `revocation:` blocks. + +Current coverage in built-in rules: +- `23` provider families +- `39` revocation-enabled rules + +Use `kingfisher revoke --rule ` to invoke these flows. See [USAGE.md](USAGE.md#direct-secret-revocation-with-kingfisher-revoke) for command details. + +## Supported Providers + +| Provider | Revocation Rule Count | Rule IDs | +|---|---:|---| +| `aws` | 1 | `kingfisher.aws.2` | +| `browserstack` | 1 | `kingfisher.browserstack.1` | +| `buildkite` | 1 | `kingfisher.buildkite.1` | +| `cloudflare` | 1 | `kingfisher.cloudflare.1` | +| `confluent` | 2 | `kingfisher.confluent.2`, `kingfisher.confluent.3` | +| `deviantart` | 1 | `kingfisher.deviantart.1` | +| `doppler` | 6 | `kingfisher.doppler.1`, `kingfisher.doppler.2`, `kingfisher.doppler.3`, `kingfisher.doppler.4`, `kingfisher.doppler.5`, `kingfisher.doppler.6` | +| `gcp` | 1 | `kingfisher.gcp.1` | +| `github` | 3 | `kingfisher.github.1`, `kingfisher.github.2`, `kingfisher.github.5` | +| `gitlab` | 2 | `kingfisher.gitlab.1`, `kingfisher.gitlab.4` | +| `harness` | 1 | `kingfisher.harness.pat.1` | +| `mapbox` | 1 | `kingfisher.mapbox.2` | +| `mongodb` | 1 | `kingfisher.mongodb.1` | +| `npm` | 2 | `kingfisher.npm.1`, `kingfisher.npm.2` | +| `particle.io` | 2 | `kingfisher.particleio.1`, `kingfisher.particleio.2` | +| `sendgrid` | 1 | `kingfisher.sendgrid.1` | +| `slack` | 2 | `kingfisher.slack.1`, `kingfisher.slack.2` | +| `sumologic` | 1 | `kingfisher.sumologic.2` | +| `tailscale` | 1 | `kingfisher.tailscale.1` | +| `twilio` | 1 | `kingfisher.twilio.2` | +| `twitch` | 1 | `kingfisher.twitch.1` | +| `unkey` | 1 | `kingfisher.unkey.2` | +| `vercel` | 5 | `kingfisher.vercel.1`, `kingfisher.vercel.2`, `kingfisher.vercel.3`, `kingfisher.vercel.4`, `kingfisher.vercel.5` | + +## Notes + +- Coverage above is derived from built-in YAML rules under `crates/kingfisher-rules/data/rules/` that currently define a `revocation:` block. +- A provider may have additional detection/validation rules that do not yet support revocation.