47  Recipe: hunt leaked AI-provider keys in repos and notebooks

Field Value
Tool trufflehog (A-1, safe) + huggingface-org-scan + colab-crawler (A-1)
Section Administrator
Level easy
Time ~20 min per scope
Layer/Category ATTACK / a-01-ai-recon (+ C-8 secondary on trufflehog)
Tags admin, secrets, recon, api-keys, hunting, owasp-llm02

47.1 Problem

Your organisation ships AI code. That means notebooks, fine-tune scripts and demo repos that read model-provider API keys — and the documented failure mode is one OPENAI_API_KEY = "sk-..." pasted into a shared notebook, committed, and forgotten. On Kailash’s manifest this is Layer ATTACK’s a-01-ai-recon surface: trufflehog (the native secret scanner, A-1 primary + C-8 secondary) carries the detector engine, while the org-scan/crawler probes frame the shared-compute and model-hub scope where those keys leak. As the administrator you own the hunt inward — your own repos, your own notebooks — before someone else runs it outward.

47.2 Solution

Scan the git scope first (history included — a key removed from HEAD still lives in the objects), then the filesystem scope for the notebooks that never got committed:

# 1. What the manifest declares for the A-1 recon surface
kailash list --category a-01-ai-recon
kailash info trufflehog       # -> A-1 primary + C-8 secondary, ATLAS, OWASP

# 2. The org sweep (verified results only — see Discussion for why):
trufflehog github --org=<your-org> --results=verified
#    exclude archived repos from the sweep:
trufflehog github --org=<your-org> --exclude-archived

# 3. One repo, full history, JSON for the tracker:
trufflehog git https://github.com/<your-org>/<repo> --results=verified --json

# 4. The un-committed surface (CI workspaces, shared notebooks,
#    scratch checkouts):
trufflehog filesystem /srv/notebooks /home/<user>/scratch

# 5. The model-hub scope around your org (planned first-party probe —
#    PENDING CLI; manifest-contract description below):
huggingface-org-scan <your-org>
#    -> enumerates the org's models/datasets/spaces + freshness as
#       attack surface

# 6. The shared-compute scope (planned first-party probe — PENDING CLI;
#    requires-target class):
colab-crawler <your-shared-notebook-scope>
#    -> crawls shared compute notebooks for leaked credentials,
#       mounted secrets and exposed model endpoints

Expected output shape (abridged, shapes not transcripts):

🐷🔑🐷  TruffleHog. Unearth your secrets. 🐷🔑🐷

Found verified result 🐷🔑
Detector Type: <provider, e.g. AWS / OpenAI>
Decoder Type: <PLAIN|Base64|...>
Raw result: <redacted-in-display key material>
Line: <n>
Commit: <sha>
File: <path>
Email: <committer>
Repository: <url>
Timestamp: <when the credential landed in history>

Then the response — and the order is not negotiable:

# 7. REVOKE FIRST. Every confirmed live key is burned before anything
#    else happens: rotate it at the provider, confirm the old value
#    is dead, THEN clean the repo (history rewrite is a separate,
#    later problem — see the discipline note in Discussion).
#    (provider-side rotation — no command here; do it in the
#     provider's console while the finding is fresh)

# 8. Record the finding + disposition against your own tracker
#    (the JSON output from step 3 is the machine-readable half;
#     the timestamp/commit/file triple is what your report cites)

# 9. Put the posture back: keys out of repos entirely
#      model API keys live at runtime from ~/.config/kailash/keys/
#      (the OS secrets posture — store paths are world-readable)

47.3 Discussion

  • --results=verified is the whole discipline: trufflehog does discovery, classification, validation and analysis — of 800+ detector types — and validation is the step that separates a live credential from a string that looks like one. A verified finding is an active danger; an unverified match is a hypothesis. The upstream demo output above IS the verified-result shape. AI-provider coverage in the detector set is real and checkable at the pin: the pkg/detectors tree at main carries openai, anthropic, huggingface, deepseek, gemini, groq, openrouter, xai, replicate, elevenlabs, deepgram, assemblyai, alibaba among its ~900 detector packages — verify your specific providers’ detectors at your lock before you rely on them.
  • Revoke-first is not negotiable — and the ATLAS mapping is why. The manifest rows map trufflehog to AML.T0004 + AML.T0055 (Search Victim-Owned Websites / Unsecured Credentials) and colab-crawler to AML.T0003 + T0055, with OWASP LLM02 (2026) / LLM02+LLM07 (v2): the attack this hunt imitates is one that does not clean up — it uses the key. Any cleanup you do before revocation is time the live credential keeps working. History rewriting (git filter-repo class) comes after rotation, because the objects still exist even after the rewrite on any fork or clone you do not control.
  • The inward scope is the administrator’s part of an ATTACK row: the category is offensive, but the hunt against your OWN org is the defender-validation case the responsible-use statement names — written authorisation is still the standing requirement, and this recipe’s scope (your org, your notebooks) is exactly the authorised-work shape. Running the same engines against someone else’s scope is the A-1 recon lane’s business, not this recipe’s.
  • The probes frame the hub and the notebook farm: both huggingface-org-scan and colab-crawler are planned first-party packs (kailash-os/ per the C-10 precedent; LicenseRef-Unknown until first-party licence grant) — their surfaces above are the manifest-contract descriptions, each marked PENDING with its row URL as the follow-up pointer, per the no-fake-surfaces rule that governs this book. colab-crawler is additionally requires-target: it refuses to self-fire and states its scope on the invocation.
  • Posture after the burn: the OS-side secrets posture puts model API keys at runtime from ~/.config/kailash/keys/ (sops-nix for declarative secrets) precisely because Nix store paths are world-readable — the recurrence prevention half of this recipe is configuration, not vigilance.
  • When the results say nothing: an all-clear is only as good as the detectors at your lock. Record the detector set (the manifest row pins the scanner version) with the sweep date — this recipe re-runs at every release window otherwise, per the book’s upkeep rule.

47.4 See also


Recipe provenance. TruffleHog CLI surfaces (github --org=…, --results=verified, --exclude-archived, git <url> --json, filesystem <paths>, the verified-result output shape, the discovery/classification/validation/analysis framing, “800+ secret types”) are from the upstream README at trufflesecurity/trufflehog main, verified 2026-10-14 (the manifest row pins it native 3.99.0, AGPL-3.0-only, ATLAS AML.T0004/T0055, OWASP LLM02 / LLM02+LLM07). Detector-coverage claims are checked against the pkg/detectors tree at main the same day. Manifest facts for the probes (planned first-party packs, LicenseRef-Unknown, colab-crawler requires-target, ATLAS T0003/T0055 + T0002/T0007 on org-scan) are from the manifest rows + lane comments. Worked sweep against a real org scope: PENDING — operator-run by nature; the detector-set record is the standing instrument.

ref: a-01 manifest rows + trufflehog detector tree at main - verified 2026-10-14.