Recipes
Every recipe is a copy-paste command. Find your goal, paste the line, done. Put provider tokens in the documented environment variables, never on the command line. See environment variables and exit codes.
Find the right recipe
Each command scans one explicit source boundary. Run several recipes when your
review spans several boundaries, and retain each json-envelope report with its
raw exit code.
| Goal | Recipe | Coverage reminder |
|---|---|---|
| Scan local files or choose a detection preset | Scan code you have locally | A filesystem scan does not add Git history. |
| Gate staged content, a diff, or reachable commits | Gate commits and pull requests | Staged, diff, history, and working-tree bytes are different inputs. |
| Add a maintained GitHub gate | Add it to CI | The Action owns one checked-out repository path. |
| Inventory GitHub, GitLab, or Bitbucket | Scan an entire GitHub organization or Scan a GitLab group or Bitbucket workspace | Partition large estates and preserve one status per partition. |
| Inspect issues, pull requests, discussions, wikis, or gists | Scan collaboration surfaces | Collaboration content is separate from repository files and Git objects. |
| Inspect an image, archive, or cloud bucket | Scan a Docker image, scan third-party archives, or audit a cloud bucket | Preserve coverage gaps for encrypted, corrupt, unsafe, truncated, or limited content. |
| Inspect a URL, response, HAR capture, or stdin | Scan a URL or pipe arbitrary text | URL mode fetches selected responses. It is not a crawler. |
| Audit a local host | Sweep an entire machine | The space ceiling and mount policy bound coverage. |
| Test whether eligible credentials are live | Confirm a finding | Verification sends credential-derived requests to providers. |
| Adopt existing findings or approve one fixture | Adopt on a noisy repo or approve one fixture | A baseline and an exact suppression solve different policy problems. |
| Export to CI, a SIEM, or another tool | Emit for any pipeline | Envelope formats retain source status and coverage state. |
Scan code you have locally
keyhog scan . # canonical default policy
keyhog scan path/to/file.env # one file; may use a ready Unix daemon
keyhog scan . --fast # pattern-only: no decode, entropy, or ML
keyhog scan . --deep # bounded highest-recall preset
keyhog scan . --precision # 0.85 floor, no entropy/relaxed keyword bridge
keyhog scan . --lockdown # Linux; requires sufficient memlock
Gate commits and pull requests
keyhog scan --git-staged # pre-commit: staged blobs (uses guard daemon if live)
keyhog scan --git-diff main # only files changed since a base ref
keyhog scan --git-history . # added lines from reachable commits, bounded by max_commits
keyhog scan --git-history . --max-commits 500
Guard a repository for instant pre-commit scans
# 1. Start daemon in background (reconciles durable roots)
keyhog guard up
# 2. Register repository (indexes baseline into memory once)
keyhog guard add /path/to/repo --mode repo
# 3. Inspect in-memory status and attestation metrics
keyhog guard status /path/to/repo
# 4. Staged commits now execute with sub-millisecond in-memory attestation caching
cd /path/to/repo && keyhog scan --git-staged
# 5. List all active guarded repositories
keyhog guard list
# 6. Free daemon memory whenever you finish working on a repository
keyhog guard remove /path/to/repo
Pre-commit framework: keyhog ships a hook, so a .pre-commit-config.yaml
repo: https://github.com/santhreal/keyhog entry wires keyhog scan --git-staged into every commit. See perpetual guard and
pre-commit.
Add it to CI (one workflow file)
# .github/workflows/keyhog.yml
name: keyhog
on: [push, pull_request]
permissions: { contents: read, security-events: write }
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: santhreal/keyhog@v0
with: { path: ., severity: high, format: sarif, preset: default, lockdown: 'false' }
Findings upload to the GitHub Security tab as SARIF. Commit a baseline first so CI fails only on new secrets. See Adopt on a noisy repo, the GitHub Action guide, and the direct CI guide.
Scan an entire GitHub organization
export KEYHOG_GITHUB_TOKEN="$GH_PAT"
keyhog scan --github-org acme --format json-envelope --output acme.json
The command traverses the organization until the configured page, repository, and byte limits bind. The envelope records source identity and any remaining inventory as coverage gaps. See mass scanning.
Scan a single repo’s collaboration surfaces
Issues, pull requests, discussions, wikis, and gists carry secrets that never land in the tree:
export KEYHOG_GITHUB_TOKEN="$GH_PAT"
keyhog scan --github-collaboration acme/service --github-all
See GitHub collaboration scans.
Scan a GitLab group or Bitbucket workspace
KEYHOG_GITLAB_TOKEN="$GL_PAT" keyhog scan --gitlab-group acme # incl. subgroups
KEYHOG_BITBUCKET_USERNAME="$BB_USER" KEYHOG_BITBUCKET_TOKEN="$BB_APP_PASSWORD" \
keyhog scan --bitbucket-workspace acme
Scan a Docker image before you ship it
keyhog scan --docker-image registry/app:v1 # unpacks image layers
Audit a cloud bucket
keyhog scan --s3-bucket logs-prod --s3-prefix config/ # --s3-endpoint for non-AWS
keyhog scan --gcs-bucket logs-prod --gcs-prefix config/
keyhog scan --azure-container-url "$AZURE_CONTAINER_URL" --azure-prefix config/
Scan a URL, endpoint response, or HAR capture
keyhog scan --url https://api.example.com/config # one or more URLs
Pipe arbitrary text through
echo "$SOME_BLOB" | keyhog scan --stdin
kubectl get secret app -o yaml | keyhog scan --stdin
A producer that fails writes nothing to stdout. The scan then reads zero bytes
and exits 13 with a scan covered nothing gap row, which is honest but
blames the scanner rather than the producer. Make the pipeline carry the real
failure:
set -o pipefail
kubectl get secret app -o yaml | keyhog scan --stdin
With pipefail, a missing kubectl or a denied request surfaces the
producer’s own exit code. See
tell a real clean from a skipped input.
Sweep an entire machine
keyhog scan-system --space 50G # eligible mounts and discovered Git history, bounded at 50 GiB
See system-wide triage.
Confirm a finding is a live credential
keyhog scan . --verify # validate against provider APIs (exit 10 if live)
keyhog scan . --verify --verify-oob # out-of-band verification server
See verification.
Adopt on a legacy or noisy repo
keyhog scan . --create-baseline .keyhog-baseline.json # snapshot existing findings once
keyhog scan . --baseline .keyhog-baseline.json # then report only NEW findings
Commit the first file. An entry matches on the detector and the credential value, not on the path, so moving a baselined secret does not fail the gate but rotating it does. The complete CI path, including monorepo partitions, is Fail only on new secrets.
Approve one exact fixture finding
Append a detector, path, and credential hash to the same rule:
cat >> .keyhogignore.toml <<'EOF'
[[suppress]]
detector = "aws-access-key"
path_eq = "fixtures/aws.env"
credential_hash = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
EOF
keyhog scan .
All three fields must match. A different value in the fixture, or the same
value in another path, still reports and keeps the findings exit. Invalid TOML
stops the scan with exit 2; KeyHog does not ignore a broken policy. See
suppressions.
Ignore one generated tree
cat >> .keyhogignore <<'EOF'
path:generated/**
EOF
keyhog scan .
The rooted pattern matches generated/app.js, not
packages/web/generated/app.js. Use path:**/generated/** only if every
generated directory is reviewed and safe to exclude. .keyhogignore has no
negation or last-rule-wins override. An invalid entry stops the scan with exit
2.
Scan third-party archives without a false clean
rc=0
keyhog scan incoming/ --format json-envelope -o keyhog-archives.json || rc=$?
jq '{scan_status, coverage_gap_summary, findings: (.findings | length)}' \
keyhog-archives.json
printf 'keyhog exit=%s\n' "$rc"
Corrupt, encrypted, unsafe, oversized, or truncated members produce coverage
gaps. With no blocking finding, incomplete coverage exits 13, not 0.
Blocking findings in the covered portion take exit 1, or 10 when
verification confirms a live credential, while scan_status remains partial.
See source archives.
Make the CI loop fast
keyhog scan . --incremental # BLAKE3 Merkle skip of unchanged inputs
keyhog scan . --incremental --incremental-cache .keyhog-cache
Emit for any pipeline or SIEM
One engine, every dialect. Pick with --format:
keyhog scan . --format sarif -o keyhog.sarif # GitHub / GitLab code scanning
keyhog scan . --format github-annotations # inline PR annotations
keyhog scan . --format gitlab-sast -o gl-sast.json # GitLab SAST report
keyhog scan . --format junit -o keyhog.xml # JUnit for any CI dashboard
keyhog scan . --format jsonl-envelope # streaming machine contract
keyhog scan . --format csv -o findings.csv
Available formats: text · json · json-envelope · jsonl · jsonl-envelope · sarif · csv · html · junit · github-annotations · gitlab-sast.
Filter and set the gate
keyhog scan . --severity high # info | client-safe | low | medium | high | critical
keyhog scan . --min-confidence 0.5 # raise the reporting confidence floor
keyhog scan . --exclude-paths vendor,node_modules
Exit 0 means no finding blocks the active evidence policy and no failing
source gap occurred. It can still accompany advisory skip gaps and
scan_status: partial, so it is not proof that skipped content was clean. Exit
1 means a finding blocks the selected evidence policy; 10 means at least one
live credential under --verify; and 13 means failing source or coverage
gaps when no blocking finding took precedence. A blocking or live finding can
therefore exit 1 or 10 while scan_status remains partial. See the full
precedence table in exit codes.