Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Kingfisher: detect, validate and revoke a secret

By updated

Improve this page

Logo Kingfisher

Kingfisher is a secrets scanner published by MongoDB, under the Apache-2.0 license and written in Rust, that does not stop at detection: it checks that a secret is active, maps what it can reach, then can revoke it. Where the three previous scanners of the module stop at the finding, at verification or, for TruffleHog, at a permissions analysis, Kingfisher chains five steps in a single tool: detect, validate, map, triage, revoke. This lesson walks through all five, with emphasis on the last three, which Gitleaks, Betterleaks and TruffleHog do not cover, or not in the same way.

What you will learn

  • Scan a folder, the history of a repository and an organization with Kingfisher, then read the result.
  • Tell an active, inactive, inconclusive and merely assumed-valid secret apart.
  • Map the blast radius of a valid secret with --blast-radius, and know when to do it.
  • Revoke a supported secret with kingfisher revoke, knowing the limits of the action.
  • Wire the tool into a pre-commit hook and into GitHub Actions, using its exit codes.
  • Tie the lesson to the SOCLE: the requirement SOCLE-SEC-GEN-6 (in French), secret leak detection and response procedure, finds its detection and response tooling here.

Prerequisites

This lesson assumes what the three previous lessons of the module teach: why a committed secret stays readable in Git history, what a false positive is, and why a secret is validated with its provider. If these notions are fuzzy, read Gitleaks and then TruffleHog first, and the guide on secrets in code and Git history. You also need to be able to read a YAML file and run commands in a terminal.

On the workstation side, plan for Docker, Homebrew or uv, your choice, and a throwaway Git repository. No provider account is needed for the first experiments: the secret used here is fake.

Five steps in a single tool

A classic scanner returns a list of candidates: strings that look like a key. Kingfisher extends that list in five steps, which its documentation sums up as "Detect, Validate, Map, Triage, Revoke". The diagram below puts them in order, with the command behind each one. Look mostly at the second box, which turns a list of candidates into a list of proven secrets, and at the last one, in red, the only one that changes something at a third party.

The five Kingfisher steps in order: detect with kingfisher scan, validate with the provider (active, inactive or inconclusive result), map with the --blast-radius option, triage with kingfisher view, then revoke with kingfisher revoke. A dotted branch shows that the --no-validate option removes every call to the provider.Detectkingfisher scanValidate:active, inactive or inconclusiveMap:--blast-radiusTriage:kingfisher viewRevoke:kingfisher revokeNo call to the provider:--no-validate
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

The first two steps exist elsewhere, in a similar form. Validation is what TruffleHog calls verification and what Betterleaks enables with its dedicated option: it sends the secret to the provider to find out whether it works. TruffleHog can also analyze the permissions of some credentials. What Kingfisher brings together, according to its README, is mapping of access, a report viewer and revocation for the supported providers, all in the free version, with no separate paid offer.

The Kingfisher repository also publishes comparative scan times, where the tool comes first on every repository tested. They come from a 16-core macOS machine and are not a benchmark of the current version, as the repository itself states: measure on your own repositories before relying on them.

Installing Kingfisher

Three routes are enough to get started. Homebrew and the PyPI package install the binary on the workstation; the container image avoids any installation and suits CI.

Terminal window
# Homebrew (macOS, Linux)
brew install kingfisher
# PyPI package that ships the binary
uv tool install kingfisher-bin==2.16.0
# Container image: version tag and digest
docker run --rm \
ghcr.io/mongodb/kingfisher:2.16.0@sha256:76ffeabd207296512359dccf740587e76f2dcd6821d3e53431a734627cb510aa \
--version

The command prints the installed version.

kingfisher 2.16.0

The following examples call kingfisher directly. The image needs three more settings, otherwise the report is lost or refused: it runs as user 10001 with the working directory /app, which is not your folder. So mount the repository in /src, pass your own user ID with -u so that the report belongs to you, and write it under /src.

Terminal window
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD":/src \
ghcr.io/mongodb/kingfisher:2.16.0@sha256:76ffeabd207296512359dccf740587e76f2dcd6821d3e53431a734627cb510aa \
scan /src --format json --output /src/findings.json

The findings.json file appears in the current folder. For the viewer, the server must listen on all interfaces of the container (--address 0.0.0.0), and the port is published on your workstation's loopback only (-p 127.0.0.1:7890:7890): no other computer on the network can reach it. The container has no browser, so open http://127.0.0.1:7890 yourself.

Terminal window
docker run --rm -u "$(id -u):$(id -g)" -p 127.0.0.1:7890:7890 -v "$PWD":/src \
ghcr.io/mongodb/kingfisher:2.16.0@sha256:76ffeabd207296512359dccf740587e76f2dcd6821d3e53431a734627cb510aa \
view /src/findings.json --address 0.0.0.0

First scan on a fake manifest

To see the validation statuses without risk, create a throwaway repository with a fake Kubernetes manifest. The token below has the shape of a GitHub personal token but matches no account: the provider will reject it.

Terminal window
mkdir demo-kingfisher && cd demo-kingfisher
git init --quiet
cat > secret.yaml <<'EOF'
apiVersion: v1
kind: Secret
metadata:
name: ci-github
stringData:
token: ghp_R7kQ2mZ9xT4vB8nW1cL6yD3sF5hJ0aPeUgXi
EOF
git add secret.yaml
git -c user.name=demo -c user.email=demo@example.org commit --quiet -m "add a fake secret"

Then run the scan. By default, Kingfisher scans the full history of the repository (--git-history full) and attempts live validation. The exit code matters as much as the display, which is why it is printed.

Terminal window
kingfisher scan . --format json --output findings.json
echo "exit code: $?"

The scan found something. The code 200 means "visible findings". The code 205, "visible and validated findings", is reserved for secrets proven active: the provider rejected the fake token, so it does not apply here.

exit code: 200

The findings.json report then carries, for the fake token, the validation status verified_inactive and the provider's response (excerpt):

{"outcome":"verified_inactive","status":"Inactive Credential","response":"{\"reason\":\"Unauthorized\",\"result\":\"invalid\"}"}

With --no-validate, the same scan also returns the code 200, but the status becomes not_attempted ("Not Attempted") and no request goes to GitHub.

Open the report to read the status. In the JSON, the field to use in automation is finding.validation.outcome, which carries a stable value, whereas the displayed label may change.

Terminal window
kingfisher view findings.json

The viewer starts locally on 127.0.0.1:7890 and opens the browser (with the image, use the container view command given above). Since the token is fake, the expected result is the Inactive Credential status, with the value verified_inactive. The JSON report contains exactly this information, which you can also read without a browser:

Terminal window
grep -o '"outcome":"[a-z_]*"' findings.json
"outcome":"verified_inactive"

If you get the Inconclusive Validation status instead, the provider did not answer (workstation offline, rate limit): see the troubleshooting section below. Then delete the test repository.

Terminal window
cd .. && rm -rf demo-kingfisher

Reading the validation verdict

Validation is the heart of the tool, and its result is not reduced to "active or not". The Kingfisher documentation distinguishes several statuses, and the triage of alerts depends on them. The table below lists the main ones; the machine value is the one to test in a script.

Displayed statusMachine valueWhat it proves
Active Credentialverified_activeA validator proved the secret works: handle it first.
Inactive Credentialverified_inactiveThe provider rejected the secret: it is dead, but still needs cleaning up.
Inconclusive ValidationunavailableThe check did not complete (timeout, limit, server error): neither proof of activity nor of inactivity.
Assumed Valid (Not Live-Validated)assumedThe secret is very probably real, but nobody tested it live.
Not Attemptednot_attemptedNo validation produced: no validator for this rule, or --no-validate.

A filter selects what stays in the report. The --validation-filter option accepts three values: all, which keeps everything and is the default; active, which keeps only secrets proven active; and actionable, which adds assumed-valid secrets and those recognized by a local computation. The --only-valid option is an alias of --validation-filter active, and the two cannot be combined.

Terminal window
# Keep only the secrets proven active
kingfisher scan . --validation-filter active --format json

When all the secrets in the repository are dead, the resulting report has an empty findings array ({"findings":[],"audit":{...). On the fake token, the exit code is still 200, because the code counts the findings before the filter. To decide on a block, test the content of the report or the code 205, not the code 200 alone.

Why not filter on active systematically? Because an inactive secret is not necessarily harmless: copied elsewhere, it may still open another service, and the history keeps it readable. The filter is there to order the fixes, not to erase findings.

Mapping the blast radius

Knowing that an AWS key is active does not tell you whether it opens a test bucket or the whole production environment. The --blast-radius option, whose alias is --access-map, answers that question for the supported secret types: it identifies the owner of the secret (user, role, service account), its recorded permissions and the visible resources. For AWS, it also follows the roles the key can assume; for GCP, the service accounts it can impersonate.

Terminal window
kingfisher scan . --only-valid --blast-radius --format json --output findings.json

The JSON report is the same as usual, enriched with an access_map field for each active secret of a supported provider. The output therefore depends on a really active secret: the mapping queries the provider's API with the secret, and a fake token gives nothing to map. With the fake token of the lesson, the command returns the code 200 and a report with an empty findings array, with no access_map field at all, because --only-valid drops everything that is not proven active. To see a real map, you need an active secret and an account you are allowed to inspect.

Three precisions prevent misreading the result. The mapping is read-only and "best effort": an access that was not observed is not an impossible access, because the policies and candidate paths give context without proving each access. It runs network requests on top of validation, and the documentation says to use it only if you are authorized to inspect the targeted account. Finally, it only applies to supported providers: the documentation counts forty-three at this tag, and the list is authoritative, not this figure.

To inspect a single known secret without scanning a repository, the standalone kingfisher blast-radius command produces the same map from a rule and the secret supplied directly. Use --aws-region or --gcp-project to widen discovery to other regions or projects. With the fake token of the lesson, kingfisher blast-radius --rule github-pat <token> fails cleanly, with the exit code 1:

Error: Blast-radius mapping failed for all rules matching 'github-pat':
- betterleaks.github-pat: Identity mapping failed: GitHub access-map: user lookup failed with HTTP 401 Unauthorized

Revoking a secret

When an active secret is found and its owner cannot be found, has left or is unreachable, waiting for rotation prolongs the exposure. The kingfisher revoke command disables the secret directly with the provider, without going through the employee who created it. It is an explicit action: no scan ever revokes anything.

Terminal window
kingfisher revoke --rule github-pat "$COMPROMISED_TOKEN"

The command prints a success or failure message for each rule applied. The exit code is 0 if at least one rule revoked the secret, and 1 if all failed or if an error occurred. With the fake token of the lesson (ghp_R7kQ...), the output of 2.16.0 is:

Rule: Uncovered a GitHub Personal Access Token, potentially leading to unauthorized repository access and sensitive content exposure. (betterleaks.github-pat)
Result: REVOKED
Status: 202
Response: {
}

The exit code is 0. Keep in mind what this proves: the provider accepted the request (HTTP 202), not that the token existed. A fake token gets the same REVOKED. After a revocation, check the status of the secret with the provider rather than relying on the message alone.

Revocation is deliberately conservative. Kingfisher only offers the action when it can identify the exact key and gather the values the provider requires; at this tag, the documentation counts 34 revocation rules for 15 provider families (AWS, GitHub, GitLab, npm, Cloudflare, Hugging Face, among others), and the capabilities file in the repository is the reference list. For a secret outside the list, you must go through the provider's console.

Choosing the scan target

A local repository is only one of the targets. The kingfisher scan command accepts subcommands that enumerate organizations, storage and collaboration tools, each with its token in an environment variable prefixed KF_. The table gives the most useful ones; the full list is in the repository's integrations guide.

TargetCommandAuthentication
Local folder or repositorykingfisher scan .none
Remote repositorykingfisher scan https://github.com/org/repo.gitKF_GITHUB_TOKEN if private
GitHub organizationkingfisher scan github --organization orgKF_GITHUB_TOKEN
GitLab groupkingfisher scan gitlab --group group --include-subgroupsKF_GITLAB_TOKEN
Container imagekingfisher scan docker image:tagKF_DOCKER_TOKEN if the registry is private
S3 bucketkingfisher scan s3 my-bucket--profile or KF_AWS_KEY and KF_AWS_SECRET
Issues and wikiskingfisher scan jira and kingfisher scan confluenceKF_JIRA_TOKEN, KF_CONFLUENCE_TOKEN
Messageskingfisher scan slack "api_key OR password"KF_SLACK_TOKEN

Before scanning an organization, the --list-only option prints the repositories that would be scanned, without scanning anything. It is a good habit: a token that sees a thousand repositories would scan them all, and it is better to know it beforehand.

Terminal window
KF_GITHUB_TOKEN="$READ_TOKEN" kingfisher scan github --organization my-org --list-only

The command prints the clone URL of each repository of the organization visible with this token, one per line, with no scan result. With three repositories, the output of 2.16.0 is:

https://github.com/my-org/billing-api.git
https://github.com/my-org/infra-terraform.git
https://github.com/my-org/marketing-site.git

Counting the lines gives the number of repositories the scan would go through. The command queries the forge's API, so it does not work offline. If your workstation has no network, you get the exit code 1 and a DNS resolution error instead of the list.

On history, the default is to go through everything. Three options bound it: --git-history none scans only the current state of the files; --since-commit <ref> scans the commits added since a reference, which suits a pull request review; --staged scans the changes waiting to be committed. The --path-prefix, --skip-path-prefix and --file-extension options also restrict the paths.

Choosing the output format

The format is set with --format, and --output writes to a file instead of the screen. Four formats cover the essentials: json for scripts, sarif for a forge's security tab, html for a standalone audit report, and pretty for the terminal. A fifth, toon, is designed to be handed to an AI agent.

The viewer is not limited to Kingfisher reports: it also reads the reports of Gitleaks and TruffleHog and merges them while deduplicating. If you already have these scanners in place, kingfisher view gitleaks.json trufflehog.jsonl gives a common triage table without changing your pipelines.

Wiring Kingfisher into the pipeline

The exit codes make Kingfisher usable in automation: they distinguish a secret that was merely found from a secret proven active.

CodeMeaning
0No visible finding.
200Visible findings.
205Visible and validated findings.
1Fatal scan error.
2Invalid arguments.
3Nothing to scan.

These codes allow a two-level policy. Blocking the merge on 205 only avoids bothering teams over dead secrets, while blocking on 200 as well is stricter and suits a sensitive repository. A scan can continue after a read error and return a success code with partial coverage: in production, also check audit.summary.scan_partial and audit.summary.scan_failed in the JSON report.

Pre-commit hook

The pre-commit framework runs checks before each commit. The Kingfisher repository does publish ready-to-use hooks, but the kingfisher-auto hook downloads the latest version of the binary without verifying its checksum, and the kingfisher-docker hook pulls the latest image: neither of them is pinned, even if rev is. We set them aside here in favor of a local hook, which runs the digest-pinned image with language: docker_image. The framework then mounts the repository in /src and passes your user ID, which settles two of the Docker route pitfalls seen above. A third is specific to the --staged option: to scan the indexed files, Kingfisher creates a snapshot with git commit-tree, and Git refuses to do so without an identity. The framework does not pass your Git configuration to the container, hence the four GIT_* variables at the head of the entry, which provide a service identity unrelated to yours.

repos:
- repo: local
hooks:
- id: kingfisher
name: kingfisher
language: docker_image
entry: >-
-e GIT_AUTHOR_NAME=kingfisher
-e GIT_AUTHOR_EMAIL=kingfisher@localhost
-e GIT_COMMITTER_NAME=kingfisher
-e GIT_COMMITTER_EMAIL=kingfisher@localhost
ghcr.io/mongodb/kingfisher:2.16.0@sha256:76ffeabd207296512359dccf740587e76f2dcd6821d3e53431a734627cb510aa
scan . --staged --quiet --no-update-check
pass_filenames: false

Install the hook with pre-commit install. At each commit, --staged limits the scan to the files waiting to be committed (pass_filenames: false only prevents the framework from appending their names to the command), and the commit is refused if the exit code is not 0. The repository must already contain at least one commit: without it, the scan fails on a Git error. In both Git failure cases (no commit, no identity), the hook shows Passed and the commit goes through, secret included: see the troubleshooting section.

Here is what git commit prints in a repository that already has a commit, when secret.yaml, which contains the fake token of the lesson, is staged but not yet committed, with Kingfisher 2.16.0 (the output is shortened). Without network access, the provider does not answer, hence the inconclusive status.

kingfisher...............................................................Failed
- hook id: kingfisher
- exit code: 200
GITHUB-PAT => [BETTERLEAKS.GITHUB-PAT]
|Finding.........: ghp_R7kQ2mZ9xT4vB8nW1cL6yD3sF5hJ0aPeUgXi
|Description.....: Uncovered a GitHub Personal Access Token, potentially leading to unauthorized repository access and sensitive content exposure.
|Fingerprint.....: 667680336578602825
|Confidence......: high
|Entropy.........: 5.22
|Validation......: Inconclusive Validation
|__Response......: Betterleaks validation request failed
|Language........: YAML
|Line Num........: 6
|Path............: secret.yaml
...

The hook's exit code is 200, so the commit is refused: git log shows no new commit. With network access, the Validation line shows Inactive Credential for this fake token, and the code stays 200. With no secret in the index, the hook shows Passed and the commit is created.

GitHub Actions workflow

The following workflow scans the commits of a pull request with the pinned image. The base SHA goes through an environment variable rather than being inserted into the command, so that no GitHub context data is interpreted as shell. The step fails on any non-zero code.

name: secrets
on:
pull_request:
permissions: {}
jobs:
kingfisher:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Scan the pull request commits
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
code=0
docker run --rm -v "$PWD":/src \
ghcr.io/mongodb/kingfisher:2.16.0@sha256:76ffeabd207296512359dccf740587e76f2dcd6821d3e53431a734627cb510aa \
scan /src --since-commit "$BASE_SHA" --quiet --no-update-check --format json || code=$?
echo "exit code: $code"
exit "$code"

The step prints exit code: 0 when the pull request adds no secret, and exit code: 200 or 205 with a failed step otherwise. If the second commit of the pull request adds the fake token, the JSON report is printed first, then the last line reads:

exit code: 200

If the second commit adds no secret, the report starts with {"findings":[],"audit":... and the last line reads exit code: 0. The minimal permissions, the pinned SHA, persist-credentials: false and passing the context through env follow the rules of actionlint and zizmor: run both tools on your own workflows before merging them.

To block only on validated secrets, replace the last line with a test that fails only if code equals 205. On a pull request coming from a fork, validation sends the secret it found to a third party: if the content is not trusted, add --no-validate.

Troubleshooting

The symptoms below are the ones you meet while following the lesson. Two of them look alike: a 200 code while the report is empty, and a 0 code while Git failed. In both cases the exit code alone misleads, and it is the report that says what really happened.

SymptomLikely causeFix
manifest unknown when pulling the imageTag written v2.16.0 with a vUse ghcr.io/mongodb/kingfisher:2.16.0, without v.
With the image, findings.json is not found, or Permission deniedWithout -u, the container runs as user 10001 and writes to /app; its mounted /src is not writablePass -u "$(id -u):$(id -g)" and --output /src/findings.json.
kingfisher view in the container, nothing in the browserNo published port, or server bound to 127.0.0.1 inside the containerAdd -p 127.0.0.1:7890:7890 and --address 0.0.0.0.
Exit code 3No repository or file to scan (user without repositories, everything excluded)Check the target with --list-only and the exclusions.
Exit code 200 although everything seems deadThe code counts the findings before the filter, so it stays 200 even when the report is emptyAdd --validation-filter active to empty the report, then block on the code 205 or on a non-empty findings array.
Git command failed (exit status: 128) with --staged, yet code 0Repository with no commit at all, or container without a Git identity (the pre-commit hook does not pass your configuration): Git cannot create the snapshot of the indexed filesMake a first commit, and add -e GIT_AUTHOR_NAME=... -e GIT_AUTHOR_EMAIL=... -e GIT_COMMITTER_NAME=... -e GIT_COMMITTER_EMAIL=... before the image, with -u "$(id -u):$(id -g)" outside pre-commit; do not rely on the exit code alone, run pre-commit run --verbose to see the error.
Inconclusive Validation statusProvider unreachable, timeout or rate limitRerun the scan after a few minutes; do not conclude the secret is dead.
Not Attempted status everywhere--no-validate active, or no validator for the ruleRemove --no-validate; for a rule without a validator, check by hand.
Error when combining options--only-valid and --validation-filter togetherKeep only one of the two.
Green pipeline although a repository was not readPartial scan that returns a success codeRead audit.summary.scan_partial in the JSON report.
--since-commit finds nothing in CIShallow clone without the base commitClone with fetch-depth: 0.

Gitleaks, Betterleaks, TruffleHog or Kingfisher: which one should you choose?

All four scanners detect secrets, and their differences are mostly about what they do after detection: verify, measure, revoke. Kingfisher is the only one to document these three steps together in a stable version, but the table also shows what the others do better or differently, for example the breadth of sources of TruffleHog or the simplicity of Gitleaks.

The table compares the four scanners on what matters when you choose: where they look, what they do with a secret they find, and how they fit into a delivery pipeline. Every cell comes from the tool's own documentation at the version shown in the header. When that documentation says nothing about a capability, the cell reads "not documented": this does not prove the tool is unable to do it, only that its documentation does not announce it.

CriterionGitleaks 8.30.1Betterleaks 1.9.0TruffleHog 3.99.7Kingfisher 2.16.0
Git repository and historyyes, gitleaks git (via git log -p)yes, betterleaks gityes, trufflehog gityes, history included
Remote repository, organizationnot documented (three modes: git, dir, stdin)GitHub (organization, user, PRs, issues, Actions, releases), GitLab (group, project, merge requests), Hugging FaceGitHub (--org), GitLab, Hugging FaceGitHub, GitLab, Azure Repos, Bitbucket, Gitea, Hugging Face
Files and directoriesyes, gitleaks diryes, betterleaks diryes, trufflehog filesystemyes
Container imagenot documentednot documentedyes, trufflehog dockeryes, kingfisher scan docker
Other sourcesstandard inputstandard input, S3 (including Cloudflare R2)S3, GCS, Postman, Jenkins, Elasticsearch, CircleCI, Travis CI, syslog, standard inputS3, GCS, Azure Blob Storage, Jira, Confluence, Slack, Teams, Postman
Checks that a secret is activenot documentedyes, through a validate expression in the rule, opt-in only, --validation in 1.9.0 (-v or --validate in version 2); statuses valid, invalid, revoked, unknown, erroryes, by calling the provider's API, enabled by default (--no-verification turns it off)yes, enabled by default (--no-validate turns it off)
Measures the blast radiusnot documentednot documented in 1.9.0; the version 2 preview adds -a or --analyze (account, capabilities and severity of valid tokens)yes, trufflehog analyze (permissions and reachable resources) for some credential typesyes, --blast-radius for the supported providers (identities, permissions, resources)
Revokes a secretnot documentednot documented in 1.9.0 (revocation only appears there as a status returned by validation); the version 2 preview announces betterleaks revokenot documentedyes, kingfisher revoke, for the supported providers
Pre-commit hookyes, pre-commit hooks providedyes, pre-commit hooks providedyes, documented (hooksPath, pre-commit framework, Husky)yes, install script and pre-commit framework
CI integrationgitleaks-action action (separate repository), Docker imageDocker image ghcr.io/betterleaks/betterleaks; no dedicated action in the READMEGitHub action trufflesecurity/trufflehog, Docker imageDocker image ghcr.io/mongodb/kingfisher; no dedicated action in the documentation
Output formatsJSON, CSV, JUnit, SARIF, templateJSON, CSV, JUnit, SARIF, template (JSONL for validate)text, JSON, SARIF, GitHub Actions formathuman-readable text, JSON, JSONL, BSON, TOON, SARIF, HTML
LicenseMITMITAGPL-3.0Apache-2.0
LanguageGoGoGoRust

Survey carried out on 2026-10-30 on the latest published version of each tool. Versions move fast: check the date before relying on a cell.

The right tool depends first on what you want to do with a secret once it is found. To block a commit or a merge, all four will do, and Gitleaks is enough when its rules are mastered and it is already in place. To tell live secrets from leftover examples, you need a tool that validates: Betterleaks (opt-in), TruffleHog or Kingfisher. To know what an active token opens up, TruffleHog (analyze) and Kingfisher (--blast-radius) answer, each on the providers it supports; Betterleaks adds a comparable analysis in its version 2, still a preview. To act without waiting for the owner of the secret, only Kingfisher documents revocation from the command line in the stable versions surveyed; Betterleaks announces one for its version 2 as well.

If the target goes beyond the repository (issues, chat, images, buckets), look at the "Other sources" row: the scope varies a lot from one tool to another. An AGPL-3.0 license can also weigh in the choice depending on how your organization uses the tool. Finally, a scanner that finds nothing proves nothing: test the one you pick on a repository containing a known fake secret before wiring it into CI. To freeze what already exists and block only new findings, the detect-secrets guide (in French) covers a baseline approach, and the comparison of secret detection tools widens the view to other candidates.


Key takeaways

  • Kingfisher chains five steps in one tool: detect, validate, map, triage, revoke, with one command per step.
  • Only validation, mapping and revocation leave your machine: run them only on accounts you are allowed to inspect.
  • A secret is classified as active, inactive, inconclusive or assumed valid; in a script, test finding.validation.outcome rather than the displayed label.
  • The exit code distinguishes 200 (visible finding) from 205 (validated finding), which lets you block only on proven secrets.
  • --blast-radius maps in read-only and best-effort mode: an access absent from the result is not an impossible access.
  • kingfisher revoke is cautious and explicit; it replaces neither rotation nor an access audit.
  • The image is pulled without a v (2.16.0) and pinned by digest, in CI as in the pre-commit hook: the hooks supplied by the repository are not.
  • For the SOCLE-SEC-GEN-6 requirement, a Kingfisher scan wired into CI or a hook on your repositories provides only the first half of the evidence, the detection system. The other half is a written response procedure: even if Kingfisher can revoke, that procedure says who decides, when to run kingfisher revoke and what follows (rotation, audit, history cleanup).

Knowledge check

Check that you have the essentials of this guide. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn