Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Mass scanning

One command, whole account

For a single organization, group, workspace, or bucket, keyhog does the inventory traversal itself. No loop, no clone script:

export KEYHOG_GITHUB_TOKEN="$GH_PAT"
keyhog scan --daemon=off --github-org acme \
  --format json-envelope --output acme.json
KEYHOG_GITLAB_TOKEN="$GL_PAT" \
  keyhog scan --daemon=off --gitlab-group acme \
  --format json-envelope --output gitlab.json

KEYHOG_BITBUCKET_USERNAME="$U" KEYHOG_BITBUCKET_TOKEN="$P" \
  keyhog scan --daemon=off --bitbucket-workspace acme \
  --format json-envelope --output bitbucket.json

keyhog scan --daemon=off \
  --s3-bucket logs-prod --s3-prefix config/ \
  --format json-envelope --output s3.json

Each run walks every repository or object under the target and writes one envelope report carrying source identity and coverage. That is the whole setup for a single provider target. The rest of this guide covers scanning across many targets (multiple orgs, mixed local + cloud, thousands of repos) where you partition and aggregate. If one org or bucket is all you need, the command above is complete.

Use one bounded report and one exit status per partition when scanning many repositories, buckets, or files. Keep the partition manifest outside the scan tree so a scanner never treats its own answer key as input.

Local partitions

This shell pattern preserves every report and status without turning a partial partition into a clean result:

#!/usr/bin/env bash
set -u

out="${1:-keyhog-results}"
mkdir -p "$out"
overall=0

while IFS= read -r -d '' partition; do
  name="$(basename -- "$partition")"
  report="$out/$name.json"
  set +e
  keyhog scan --daemon=off "$partition" \
    --max-file-size 100MiB \
    --format json-envelope --output "$report"
  rc=$?
  set -e
  printf '%s\t%s\t%s\n' "$partition" "$rc" "$report" \
    >> "$out/status.tsv"
  # Preserve the strongest actionable state: findings (1), live credentials
  # (10), coverage incomplete (13), and system failures remain visible.
  (( rc > overall )) && overall="$rc"
done < <(find ./partitions -mindepth 1 -maxdepth 1 -type d -print0)

exit "$overall"

The envelope records scan-wide coverage and the resolved policy. Keep status.tsv with the reports; an aggregator must not discard a nonzero status just because another partition was clean. If a partition is retried, replace its report atomically and append a new attempt column or manifest row rather than overwriting the only evidence.

--max-file-size bounds each regular file. The default is 100 MiB. A larger file is skipped and recorded as a coverage gap. --limit-stdin-bytes does not bound a directory partition. It applies only to --stdin.

For CI, upload the whole output directory as an artifact and make the job fail on any status that the policy treats as actionable. Exit 13 means the scan completed with coverage gaps, not that it found nothing; inspect the envelope before deciding whether a retry is safe. Exit 2 or 3 is an input or system failure and needs operator attention. See exit codes.

Hosted Git and cloud inventories

The source flags keep inventory traversal inside KeyHog so source identity and coverage remain in the report:

keyhog scan --daemon=off --github-org "$ORG" \
  --limit-hosted-git-pages 100 \
  --format json-envelope --output github.json

keyhog scan --daemon=off --gitlab-group "$GROUP" \
  --limit-hosted-git-pages 100 \
  --format json-envelope --output gitlab.json

keyhog scan --daemon=off \
  --s3-bucket "$BUCKET" --s3-prefix "$PREFIX" \
  --limit-cloud-max-objects 10000 --limit-s3-object-bytes 100MiB \
  --format json-envelope --output s3.json

keyhog scan --daemon=off \
  --gcs-bucket "$BUCKET" --gcs-prefix "$PREFIX" \
  --limit-cloud-max-objects 10000 --limit-gcs-object-bytes 100MiB \
  --format json-envelope --output gcs.json

Use the credential environment variables documented by keyhog scan --help and environment variables; do not put provider tokens in the command line. Azure Blob uses --azure-container-url and its matching prefix/object limits. A page or object cap is deliberate bounded coverage: the report names the limit and exits 13 when more inventory remains.

Hosted APIs and cloud listings can return transient transport or rate-limit errors. Retry only the failed source with bounded exponential backoff, keep the original partial envelope, and preserve the provider request diagnostics. Do not increase object/page caps automatically, and do not classify a rate-limit failure as a clean scan. Respect each provider’s pagination and retry headers.

Daemon and corpus semantics at scale

The examples use --daemon=off to make the execution contract explicit. Directory trees, Git history, hosted inventories, cloud buckets, archives, multiple roots, and source-limit changes require the in-process orchestrator. A running daemon does not accelerate them.

Omitting the flag gives --daemon=auto on Unix. These unsupported request classes are recognized before a socket connection and stay in process. --daemon=on rejects them before scanning. It does not fall back:

# Supported in process.
keyhog scan --daemon=off ./partitions/team-a

# Unsupported. This exits before scanning the directory.
keyhog scan --daemon=on ./partitions/team-a

The daemon is useful only when a partitioner emits repeated eligible stdin or single-regular-file requests. Start it with an explicit replacement corpus only when each client selects the exact same corpus:

keyhog daemon start --detectors ./reviewed-detectors
keyhog scan --daemon=on \
  --detectors ./reviewed-detectors --detectors-mode=replace \
  one-object.txt

Overlay composition is unsupported by the daemon. Use --daemon=off with --detectors-mode=overlay. A replacement identity mismatch is a handshake error. Required daemon mode exits instead of using either the daemon’s corpus or an in-process scan. See daemon and warm scans for the lifecycle, socket, eligibility, and retry matrix.

For a large inventory, partition at the provider or repository boundary. Calibrate autoroute on the actual worker class and retain the per-partition resolved policy and coverage envelope. A missing or stale autoroute decision uses visible scalar correctness recovery. It does not silently claim a calibrated CPU, Hyperscan, or GPU route. Treat complete_after_recovery as a recalibration signal even when scan byte coverage is complete.

Report aggregation

Aggregate only after every partition has a terminal envelope. Preserve the partition identity, source inventory, resolved policy, coverage state, finding count, and exit code. JSON and JSONL legacy formats contain findings only; json-envelope and jsonl-envelope are the recommended machine contracts for mass scans because they carry terminal coverage and identity. Never concatenate JSON arrays or merge findings before deduplicating with the partition and location identity.