Output formats
KeyHog’s --format flag takes one of nine values: text (default),
json, jsonl, sarif, csv, github-annotations, gitlab-sast,
html, and junit. Pick the one that fits the consumer. csv emits a
spreadsheet-importable row per finding, github-annotations emits
GitHub Actions workflow-command annotations, gitlab-sast emits a
GitLab SAST security report, html emits a
self-contained report page, and junit emits a JUnit XML test-report
(one <testcase> per finding) for CI systems that ingest JUnit.
--format text (default)
Human-readable boxes. Best for terminal use, pre-commit hook output,
and screenshots. Colors auto-detect TTY; pipe through cat (or set
NO_COLOR=1) to disable.
┌ CRITICAL ─── Stripe Secret Key
│ Secret: sk_l...p7dc
│ Location: src/config/staging.env:14
│ Confidence: ■■■■■■ 100%
│ Action: Roll the exposed Stripe secret key in the Dashboard, update production consumers, then delete the old key.
│ Docs: https://docs.stripe.com/keys#roll-api-key
└─────────────────────────────────────────────
━━━ Results ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1 secret found · 1 unverified
Each finding is a severity-colored box: the header carries the severity
and detector name, then Secret: (the credential redacted to its first
and last few characters), Location: (file:line:offset), a
Confidence: bar, and an Action:/Docs: remediation hint. Verified
runs add the liveness state and commit/author rows when known. The
Results footer joins the counts with ·.
--format json
Stable-schema JSON array. Every finding has every documented field
present. See Your first scan for the
schema. The confidence field is canonicalized to three decimal places before
the reporting-floor decision, so equivalent CPU and GPU scans serialize the
same value.
keyhog scan . --format json | jq '.[] | .detector_id' | sort | uniq -c
That sample command dedups findings by detector, which is the most common “what kinds of leaks do I have” question.
--format sarif
- Static Analysis Results Interchange Format. GitHub Code Scanning, GitLab Security Dashboard, and most IDE security plugins consume this.
keyhog scan . --format sarif > keyhog-results.sarif
Upload to GitHub:
# .github/workflows/secrets.yml
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: keyhog-results.sarif
Findings show up in the Security → Code scanning tab with the detector ID as the rule, file path + line as the location, and the redacted credential as the message.
--format github-annotations
GitHub Actions workflow commands - one annotation line per finding. Use this when you want findings to appear inline in the Actions log without uploading SARIF:
keyhog scan . --format github-annotations
Critical and high findings render as error annotations, medium and
low as warning, and info as notice. Each annotation carries the file,
line, title, detector, service, redacted credential, verification state,
and confidence when available. The plaintext credential is not emitted.
--format gitlab-sast
GitLab SAST security-report JSON. Use it with artifacts:reports:sast
when GitLab should ingest KeyHog findings into the pipeline Security tab:
keyhog:
script:
- keyhog scan . --format gitlab-sast --output gl-sast-report.json
artifacts:
reports:
sast: gl-sast-report.json
GitLab SAST reports require every finding to have a file path and a
one-based line number. If a non-file source cannot be represented in that
schema, KeyHog fails the report with an error instead of fabricating a
location. Use json or sarif for mixed file and non-file sources.
--format jsonl
Newline-delimited JSON - one finding per line, no outer array. Better
than --format json for streaming consumers that want to start
processing before the scan finishes:
keyhog scan /huge/monorepo --format jsonl \
| while read line; do
echo "$line" | jq -r '.location.file_path'
done
Combining with --verify
--verify calls each detector’s verification endpoint to confirm the
credential is live. Live credentials keep their severity; dead ones get
downgraded one tier. The output format doesn’t change - the
verification field of each finding becomes "live" or "dead"
instead of "skipped". (The JSON value is the lowercase
VerificationResult variant - "live", "dead", "revoked",
"rate_limited", "unverifiable", "skipped", or an {"error": "..."}
object - not the verified-live/verified-dead labels the text
reporter prints.)
keyhog scan . --verify --format json \
| jq '.[] | select(.verification == "live")'
Findings-only output
On an interactive terminal keyhog scan shows a banner, a live progress
ticker, and a completion summary on stderr. Most of the time you do not need to
silence it: the banner and ticker are printed only when stderr is a TTY (they
never appear in a pipe, a file, or CI logs), and the structured formats
(json, jsonl, sarif, csv, github-annotations, gitlab-sast,
junit) carry findings only, with no banner or footer prose. So a CI script
that wants machine output just selects a structured format:
keyhog scan . --format json
The text format does print a footer summary (counts + any skip
summary) to stdout alongside the findings; if you want findings only,
choose json/jsonl/sarif/csv/github-annotations/gitlab-sast instead. The
animated banner is TTY-gated and never reaches a pipe or a file. Exit code
semantics are unchanged by the format choice (see
exit codes).
When you do want to silence the interactive chrome on a TTY (for example a
local run whose stderr you are capturing), pass --quiet. It suppresses the
banner, the progress ticker, and the “Scan complete” summary, but still prints
coverage FAIL/WARN lines and fatal errors so a quiet scan can never be
mistaken for a clean one. Use --no-color to drop ANSI styling regardless of
whether output is a TTY (the NO_COLOR convention is also
honored).