GUIDE · HOW-TO 9 min ·

Scan a repo for leaked secretsHistory, stashes, agent files, published packages.

The February 2026 disclosures from Knostic added a new category to the leaked-secrets playbook: AI-agent state files. Scanning for them takes one extra pass. Here is how to run the full audit.

TL;DR· the answer, in twenty seconds

What: Most secret scans miss two things: Git history (they scan the working tree only) and AI-agent state files (.claude/, .cursor/, .aider/, .codex/) disclosed en masse by Knostic in February 2026.

Fix: Run gitleaks detect --source . --log-opts="--all" for history coverage, then add a targeted pass for agent paths: git log --all -p -- '.claude/*' '.cursor/*' '.aider/*' '.codex/*'. If you publish npm packages, pull each published tarball and grep it too.

Lesson: The scanner matters less than the scope. History scans, stash scans, and published-artifact checks are the gaps most teams skip.

The Knostic disclosures in early 2026 added a third category to the standard repo audit. Before February, most teams scanned for tokens in source files and environment configs. Now there are also AI-agent state files: small JSON or YAML files that coding agents write into your repo root, in dotdirectories you may never open. .claude/, .cursor/, .aider/, .codex/. They hold environment variable snapshots, session context, and OAuth tokens.

Knostic found Claude Code's settings.local.json in roughly 1 in 13 npm packages they scanned. Cursor had an equivalent disclosure three weeks earlier. GitGuardian's 2026 State of Secrets Sprawl report put AI-service token leaks up 81% year over year. This is not a single-vendor problem.

This guide covers the full audit, not the quick scan: history, stashes, agent files, published packages. Four tools compared, then concrete recipes.

What to know in 60 seconds

  • Working-tree scans miss everything that was committed and deleted. Run history scans.
  • Stashes are not history. Scan them separately.
  • Agent state files live in .claude/, .cursor/, .aider/, .codex/. Add these paths explicitly.
  • Published npm/PyPI tarballs are immutable. A history rewrite does not unpublish them. Check the tarballs.
  • Rotate credentials before you clean history. Cleaning first is a common mistake that creates a false sense of done while live secrets remain in CI logs and forks.

Pick a scanner

Four tools cover this space. Each has a real trade-off.

gitleaks

Fast, actively maintained, default ruleset covering ~160 secret types. Written in Go, single binary, no daemon. History scanning via --log-opts is first-class. Misses custom token formats unless you add rules; the TOML config is easy to extend.

Best for: CI integration, repeatable audits, any team that wants a fast first pass.

# Full history scan
gitleaks detect --source . --log-opts="--all" --verbose

# Working tree only
gitleaks detect --source . --verbose

# Stashes (gitleaks does not scan stashes natively; extract first)
git stash list --format="%H" | xargs -I{} git stash show -p {} | gitleaks detect --pipe

truffleHog v3

Deeper than gitleaks: entropy analysis plus optional live verification. The verifier mode calls the upstream API to confirm whether a token still works. That is the meaningful differentiator. It runs slower, sometimes significantly. Entropy alone produces a high false-positive rate; run it with --only-verified or you will chase noise all afternoon.

Best for: post-incident triage where you need to know which secrets are still live.

# Full history with verification
trufflehog git file://. --only-verified

# Without verification (faster, more noise)
trufflehog git file://. --no-verification

# Single commit range
trufflehog git file://. --since-commit=HEAD~30

git-secrets (AWS)

Hook-based tool from AWS Labs, focused on AWS credentials. It works for teams that only care about AWS keys. No meaningful release in years. No native history scan. If you already run Gitleaks or TruffleHog, skip it; it covers a subset of either.

Best for: teams locked to git hooks with AWS-only scope.

hasp check-repo

Targeted scanner for AI-agent state files and modern AI-service token formats. It knows the specific paths and field names that Claude Code, Cursor, Codex CLI, and Aider write to. Small scope by design, fast to run.

Best for: a focused second pass after gitleaks to catch agent-specific patterns.


For most audits: gitleaks for history and general coverage, hasp check-repo for agent-file patterns. Add truffleHog with --only-verified when you need to know which findings are still live before you start rotating.

Run the full audit

Use this sequence for any repo where you suspect a leak, or where an AI agent ran.

Step 1: scan full history

# Install gitleaks (macOS/Linux)
brew install gitleaks         # macOS
# or download from github.com/gitleaks/gitleaks/releases

gitleaks detect \
  --source . \
  --log-opts="--all --full-history" \
  --report-format json \
  --report-path ./gitleaks-report.json \
  --verbose

--log-opts="--all" tells gitleaks to walk every branch, not just the current one. --full-history picks up commits reachable only through reflog. Both flags make the scan slower. On a large repo, run it overnight.

Step 2: scan stashes

Stashes are not walked by git log --all. They live in refs/stash and hold diffs that were never committed. Most teams skip them.

# Show all stash contents as a unified diff and pipe to gitleaks
git stash list --format="%H" | while read hash; do
  git stash show -p "$hash"
done | gitleaks detect --pipe --verbose

If gitleaks flags something in a stash, drop the stash and rotate the credential. You cannot rewrite a stash out of history.

Step 3: scan agent state files explicitly

Even when gitleaks picks up some agent files, run a targeted pass to verify coverage:

# Check if any agent state files were ever committed
git log --all --oneline -- \
  '.claude/*' \
  '.cursor/*' \
  '.aider/*' \
  '.codex/*' \
  'settings.local.json'

# Inspect every diff that touched those paths
git log --all -p -- \
  '.claude/*' \
  '.cursor/*' \
  '.aider/*' \
  '.codex/*' \
  'settings.local.json'

Read the diffs. They are usually short. Look for API keys, bearer tokens, database URLs with embedded passwords, and OAuth client secrets. Field names vary by tool, but the files are readable JSON or YAML.

Step 4: scan published artifacts

History rewrites do not touch published packages. Pull the actual tarballs and inspect them:

# For each published version, check what shipped
PACKAGE="your-package-name"
VERSION="1.2.3"

npm pack "${PACKAGE}@${VERSION}"
tar -tf "${PACKAGE}-${VERSION}.tgz" | grep -iE '(claude|cursor|aider|codex|\.env)'

# Extract and inspect any suspicious files
tar -xzf "${PACKAGE}-${VERSION}.tgz" package/.claude/ 2>/dev/null
ls -la package/.claude/ 2>/dev/null || echo "No .claude/ in tarball"

Repeat for every version published since the first agent session in the repo. If you cannot remember when that was, start from 12 months ago.

For PyPI packages, download the .tar.gz or .whl and do the same:

pip download your-package --no-deps -d ./pkg-audit/
cd ./pkg-audit/
unzip -l your_package-*.whl | grep -iE '(claude|cursor|aider|codex)'

Step 5: check CI logs

CI logs are the blind spot history rewrites miss. A cat .claude/settings.local.json step in a GitHub Actions workflow puts the file contents into the action log. GitHub stores action logs for 90 days by default.

# Look for workflow steps that might print agent state
grep -rn "cat .claude\|cat .cursor\|echo.*settings.local\|debug.*env" .github/workflows/

Remove those steps from the workflow files. Then check the GitHub Actions log retention setting under your repo's Settings > Actions > General. Purge older runs manually if the retention period is long and you found a hit. One more place to check: Dependabot logs, CodeQL scan logs, and any third-party CI integrations that pull your workflow file often inherit its environment. Search those separately.

After the leak: rotate first, then clean

Teams make the expensive mistake here. You find a leaked token in Git history, run git filter-repo or BFG, push a force-rewrite, and feel done. The token is still live. The registry tarball still has it. Forks have it. Bots scraped it within minutes of the original commit appearing on GitHub.

Correct order:

  1. Rotate the credential at the provider (Stripe dashboard, OpenAI API keys page, GitHub token settings). Do this first. The credential is already public.
  2. Update all consumers of the old credential (other services, CI secrets, .env files in other repos).
  3. Verify the rotation worked by confirming the old credential is rejected.
  4. Clean history. Use git filter-repo (the maintained tool) rather than BFG if you have the option. BFG still works but git filter-repo handles more edge cases.
# Remove a specific file from all history with git filter-repo
pip install git-filter-repo

git filter-repo --path .claude/settings.local.json --invert-paths
git filter-repo --path-glob '.claude/*' --invert-paths

After the rewrite, force-push every branch and ask downstream forks to re-clone. Document what happened. Bots that scraped the token before the rewrite still have it. The rotation is the fix. The history clean is cleanup.

For npm packages: you cannot unpublish a version more than 72 hours old. Deprecate the affected versions with a message that credentials were rotated and users should upgrade.

npm deprecate your-package@"<= 1.2.3" "Security: agent state file included in package, credentials rotated. Upgrade to 1.2.4."

What most audits miss

Most teams run gitleaks against the default branch, read the report, and call it done. Three gaps stay open each time.

Archived branches hold history. If someone worked in a feature branch for three months and it was archived without merging, --all catches it. Running gitleaks without --all does not. This is the most common miss, and also the easiest to fix: just add the flag.

Submodules are separate Git repositories. A parent-repo scan does not descend into them. If you have submodules with their own histories (common in monorepos), run the audit in each one independently.

The tarball check feels redundant after a history scan, but it is not. The history scan tells you what was committed. The tarball check tells you what actually shipped. A file can be untracked, never in Git history, and still appear in an npm tarball if .npmignore is absent or misconfigured. Knostic's February finding was partly this: the file was on disk but not in history, and ended up in the published tarball.

One pattern worth calling out explicitly: developers often add a tool to a project, let it write its config, and commit only the config that looks like project settings. The agent state file sits next to it in the same directory, gets included in the .npmignore-free publish, and nobody notices. The history is clean. The tarball is not.

A pre-publish check guards this. Add it to your release pipeline once, and you do not have to think about it again:

# In package.json prepublishOnly
test ! -d .claude && test ! -d .cursor && test ! -d .aider && test ! -d .codex

A checklist you can paste into a PR

## Repo secret audit checklist

- [ ] gitleaks run with --log-opts="--all" (full history, all branches)
- [ ] stash scan run (git stash show -p for each stash)
- [ ] agent state paths checked: .claude/ .cursor/ .aider/ .codex/
- [ ] git log --all -p -- '.claude/*' '.cursor/*' '.aider/*' '.codex/*' reviewed
- [ ] published npm/PyPI tarballs checked for agent state files
- [ ] CI workflow files reviewed for steps that print env or agent files
- [ ] any found secrets rotated at the provider BEFORE history rewrite
- [ ] downstream consumers of rotated credentials updated and verified
- [ ] git filter-repo run if sensitive files found in history
- [ ] npm deprecate run on affected package versions
- [ ] .gitignore updated: .claude/ .cursor/ .aider/ .codex/ settings.local.json
- [ ] .npmignore / MANIFEST.in / .dockerignore updated with same paths
- [ ] pre-commit hook or CI check added to block future agent file commits

What this means for your stack

The scanner comparison above is useful, but every tool in it finds secrets after they have been written somewhere they should not be. The deeper fix keeps secrets out of the places agents can write to.

What that looks like: secrets in an encrypted local vault, agents receiving a reference or per-process injection at exec time, nothing sensitive persisting to agent state files because nothing sensitive entered the agent's environment.

hasp is one working implementation. curl -fsSL https://gethasp.com/install.sh | sh, hasp setup, connect a project, and the next agent session inherits references rather than values. Source-available (FCL-1.0), local-first, macOS and Linux, no account.

The audit is the right response to a suspected leak. Prevention is keeping sensitive values out of the paths scanners look for them in.

Sources· cited above, in one place

NEXT STEP~90 seconds

Stop handing the agent your real keys.

hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.

  • Local, encrypted vault — no account, no cloud, no telemetry by default.
  • Brokered run — agent gets a reference, the child process gets the value.
  • Pre-commit + pre-push hooks catch managed values before they ship.
  • Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
→ okvault unlocked · binding ./api
→ okgrant once · pid 88421
→ okagent never read

macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.

Browse all clusters· eight threads, one index