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 endpointsExpected 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=verifiedis 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: thepkg/detectorstree at main carriesopenai,anthropic,huggingface,deepseek,gemini,groq,openrouter,xai,replicate,elevenlabs,deepgram,assemblyai,alibabaamong 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-scanandcolab-crawlerare planned first-party packs (kailash-os/per the C-10 precedent; LicenseRef-Unknownuntil 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-crawleris additionallyrequires-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
- Guide safety classes chapter (the
safe/requires-targetpostures this recipe rides): https://docs.kailash.tools/governance/safety-classes.html - Serve a local model for scanning — the endpoint-side twin: the keys you protect here are the ones the serving stack loads there
- Attack the platform, then verify — the pairing structure this hunt feeds (findings → verification leg)
- Validate your injection detector against a real payload corpus — the other wave-2 pair recipe
- Upstream: trufflehog README (validation + org/JSON/filesystem surfaces): https://github.com/trufflesecurity/trufflehog
- Manifest row URLs for the planned probes (CLI PENDING, tracked at these rows): https://github.com/kailash-os/huggingface-org-scan · https://github.com/kailash-os/colab-crawler
- The A-1 manifest lane (rows + the native-at-pin correction): kailash-os#76
- Recipe directory
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.