{"slug": "ai-coding-agents-are-leaking-your-secrets-building-a-local-pre-commit-dlp-for", "title": "AI Coding Agents Are Leaking Your Secrets: Building a Local Pre-Commit DLP for Claude Code", "summary": "A developer built AgentSweep, an open-source tool that scans the local JSONL conversation histories kept by AI coding agents such as Claude Code and Codex for credential-shaped strings and redacts them without breaking the history format, plus a pre-commit hook to check the separate secret store before commits. The tool walks Claude Code's project trees for *.jsonl files, honors CLAUDE_CONFIG_DIR and CODEX_HOME, and uses source adapters to handle differing storage formats across Cursor, Windsurf, and Aider. The author notes that rotating a leaked key does not remove its bytes from transcripts, backups, or filesystem snapshots, and cites Aikido's analysis of the compromised @bitwarden/cli@2026.4.0, whose preinstall payload targeted .env, cloud credentials, and ~/.claude.json.", "body_md": "You paste a `.env` file into Claude Code to debug a database connection. You remove the credentials from your next message, clean the repository, and rotate the database password. Your local conversation history still contains the original paste.\n\nClaude Code keeps JSONL transcripts beneath `~/.claude/projects/`. Codex keeps session JSONL beneath `~/.codex/sessions/`. A process running as your user can read those files without asking either agent for permission.\n\nI built [AgentSweep](https://github.com/Ishannaik/agent-sweep) to scan that history, report credential-shaped strings, and redact them without breaking the history format. I also added a pre-commit hook so developers can check this separate store of secrets before committing code.\n\nThe headline describes a credential exposure, not proof that your coding assistant has sent a key to an attacker. Developers create the exposure when they paste secrets or let an agent capture secret-bearing tool output. Attackers exploit the copy left on disk.\n\nA repository scanner checks the files you stage or commit. Your agent's history lives elsewhere:\n\n```\nDeveloper home\n|\n+ .claude/\n|  + projects/\n|     + <encoded-project-path>/\n|        + <session>.jsonl\n|        + conversations/<session>.jsonl  (layout varies)\n|\n+ .codex/\n|  + sessions/<year>/<month>/<day>/rollout-*.jsonl\n|  + history.jsonl\n|\n+ workspace/\n   + .env\n   + .git/\n```\n\nAgent versions use different layouts. AgentSweep walks Claude Code's project trees for `*.jsonl` rather than assuming that a `conversations/` directory exists. It also honors `CLAUDE_CONFIG_DIR` and `CODEX_HOME`, because a scan of the default home misses a developer's relocated profile.\n\nDevelopers accumulate more than API keys in these records: PostgreSQL URLs with passwords, AWS access key IDs, GitHub tokens, private-key blocks, and wallet recovery phrases. An assistant response can repeat a credential from the prompt. Tool output can introduce another copy.\n\nCursor, Windsurf, and Aider keep local history too, but their storage formats differ. Aider uses Markdown history; other integrations use JSON or SQLite. Treating all coding assistants as JSONL producers would miss those stores. AgentSweep uses source adapters for discovery, string extraction, and format-specific redaction.\n\nRotating a key invalidates that credential at its provider. It does not remove its bytes from a transcript, backup, or filesystem snapshot. Cleaning Git history does not touch these directories either.\n\nAn attacker who compromises a package can run code during installation. An npm `postinstall` script inherits the installing user's access. Attackers can use Python package build or installation execution paths for the same purpose; PyPI does not use npm's lifecycle-hook names.\n\nResearchers have documented this class of theft. In [Aikido's analysis of the compromised `@bitwarden/cli@2026.4.0`](https://www.aikido.dev/blog/shai-hulud-npm-bitwarden-cli-compromise), the malicious `preinstall` payload targeted `.env`, cloud credentials, `~/.claude.json`, and `~/.claude/mcp.json`, among other files. That report establishes theft of AI-tool configuration credentials. It does not establish that this particular payload harvested conversation JSONL.\n\nA transcript harvester needs the same filesystem access. The diagram below models that attack path:\n\n``` php\nflowchart TD\n    D[Developer installs an npm dependency] --> P[Compromised package runs postinstall]\n    P --> U[Payload runs as the developer's user]\n    U --> H[Read files beneath ~/.claude/projects/]\n    H --> J[Parse JSONL prompts and responses]\n    J --> K[Extract tokens and database credentials]\n    K --> E[Send credentials to attacker endpoint]\n    E --> A[Attacker uses credentials that remain valid]\n```\n\nNo exploit in JSONL parsing is necessary. The attacker reads an ordinary file. An owner-only permission mode helps against other local users, but it does not stop malware running under your account.\n\nAgentSweep reduces the stored-history exposure. It cannot undo exfiltration, remove a provider's copy of a prompt, or stop malware from reading a live `.env` file.\n\nI separated discovery and detection from mutation. `scan` ends with a findings report. `fix` adds the redaction step, and developers still need to revoke exposed credentials themselves.\n\n``` php\nflowchart LR\n    D[1. Discover: walk source history roots] --> S[2. Scan: Aho-Corasick and regex rules plus BIP-39]\n    S --> F[3. Findings: masked report and locations]\n    F --> C{Developer selects fix}\n    C -->|No| X[Stop without modifying history]\n    C -->|Yes, after safety checks| R[4. Redact: backup, temp write, validation, atomic replace]\n    R --> K[5. Revocation guidance: provider URLs]\n```\n\nDuring scanning and redaction, AgentSweep makes zero network requests. It reads local history, performs local matching, and writes local results. Installation downloads packages, and opening a revocation URL uses your browser. The separate update command contacts PyPI. None of those actions belongs to the scan or redaction pipeline.\n\nI chose that boundary because a credential-cleanup tool should not upload the material it inspects.\n\nFor JSONL, a source adapter parses records and yields string values with their locations. The scanner can associate a finding with a file, physical line, and nested key path. The redactor can then reach that value in the parsed record.\n\nThe source layer matters for SQLite and Markdown too. A database adapter needs row-aware updates and an integrity check. A text adapter needs line-preserving replacement. Sharing detection rules does not require sharing a serialization strategy.\n\nAgentSweep documents 207 regex rules, plus mnemonic detection. Applying each pattern to each prompt would repeat a lot of work on ordinary source code and prose.\n\nI use Aho-Corasick pre-filtering to identify rule keywords in a shared pass. The matcher represents those keywords in a trie with failure links. After finding a keyword, the scanner selects the associated rules for regex evaluation.\n\nA keyword hit does not prove that the string contains a credential. The regex still checks the shape and boundaries. Conversely, a rule without a safe keyword gate must remain eligible without a keyword hit. Otherwise the optimization would introduce false negatives.\n\nFor wallet phrases, membership in a word list is insufficient. The BIP-39 detector checks candidate lengths of 12, 15, 18, 21, or 24 words and verifies the checksum. That rejects many stretches of English prose that happen to contain mnemonic words. A valid checksum identifies a mnemonic-shaped value; it does not prove that someone funded the associated wallet.\n\nThe default installation uses Python's `re`. Developers can install the optional native backend:\n\n```\nuv tool install 'agentsweep[fast]'\n```\n\nAgentSweep uses `google-re2` for compatible patterns. RE2 avoids backtracking and provides linear-time matching for those expressions, which helps when developers paste large logs into a session.\n\nRE2 does not support every Python regex construct. Lookarounds, Python-specific anchors, and Unicode semantics require care. AgentSweep retains the Python path for unsupported rules and semantic edge cases, including guarded non-ASCII inputs. It also retains that path for short strings and dense matches where native dispatch would not help.\n\nI did not claim linear-time scanning for the entire mixed pipeline. Python fallback rules still use Python's engine.\n\nThe checked-in [compatibility audit](https://github.com/Ishannaik/agent-sweep/blob/main/docs/RE2_COMPATIBILITY.md) records 144 RE2 rules and 58 stdlib rules in a 202-rule snapshot. That snapshot predates the README's 207-rule count. Treat it as evidence for the routing design, not a current inventory. Rule counts need a version alongside them.\n\nAgentSweep requires Python 3.11 or newer. Install the CLI in an isolated tool environment, then select a source:\n\n```\nuv tool install agentsweep\nagentsweep list-sources --detected\nagentsweep scan --source claude-code --no-color\nagentsweep scan --source codex --no-color\nagentsweep scan --all --detected --json -o findings.json\n```\n\nThe scan exit codes give scripts a small contract:\n\n| Exit code | Meaning | \n|---|---|\n| `0` | No findings | \n| `1` | Findings exist | \n| `2` | An error occurred | \n\nFor a concrete report example, suppose a test transcript contains the AWS documentation sample key `AKIAIOSFODNN7EXAMPLE`. The following block illustrates the finding information, not an execution transcript or a byte-for-byte promise about terminal formatting:\n\n```\nsource       claude-code\nfile         <project>/session.jsonl\nline         1\nrule         aws-access-key\npreview      AKIA...MPLE\nnext action  Review the finding; revoke a real exposed key\nscan status  Findings exist (exit 1)\n```\n\nReview the report without copying full credentials into an issue or another chat. A detector can recognize credential syntax without knowing whether the provider still accepts the value. AgentSweep stays offline, so it does not test key validity.\n\nFor a known false positive, use a narrow `.agentsweepignore` entry. Suppressing an entire provider rule makes future leaks from that provider invisible to the hook.\n\nA raw substitution over serialized JSON can damage escaping or consume syntax outside the intended value. I redact string values in parsed records, then serialize the records again.\n\nConsider this illustrative one-record transcript. The AWS key comes from a public documentation example, not a working credential.\n\nBefore:\n\n```\n{\"type\":\"user\",\"message\":{\"content\":\"AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\"}}\n```\n\nAfter:\n\n```\n{\"type\":\"user\",\"message\":{\"content\":\"AWS_ACCESS_KEY_ID=[REDACTED:aws-access-key]\"}}\n```\n\nValue-level diff:\n\n```\n- AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\n+ AWS_ACCESS_KEY_ID=[REDACTED:aws-access-key]\n```\n\nThe redactor preserves the record's structure. Re-serialization can change whitespace, so I do not promise byte-for-byte identity outside the secret.\n\nFor production history, close the agent and allow the recent-write window to expire before fixing:\n\n```\nagentsweep fix --source claude-code --allow-production\n```\n\nIn interactive mode, review the findings and type `REDACT` when prompted. The current alpha requires the production-root opt-in. Keep backups enabled.\n\nI use eight core protections around this operation. The project also documents an alpha production gate and an audit log.\n\n| Protection | Engineering reason | \n|---|---|\n| Parse before replacement | Replace string values without editing JSON delimiters | \n| Atomic replacement | Write a sibling temporary file, flush it with `fsync()` , then call`os.replace()` | \n| Format-aware validation | Parse non-empty JSONL records and compare line counts before committing the replacement | \n| Backup with no clobber | Create `.bak` by default with exclusive creation and mode`0600` | \n| Path containment | Refuse targets outside the selected source's history roots | \n| Symlink refusal | Refuse symlink targets rather than follow them into another file | \n| Recent-write gate | Refuse files modified within the previous 60 seconds | \n| Running-process gate | Refuse redaction when the relevant agent appears to be running | \n\nThe `fsync()` and replacement sequence prevents a partial rewrite of the target during normal atomic-replace operation. It does not justify a blanket guarantee about storage hardware or power-loss durability on every filesystem.\n\nThe format check validates the rewritten content before `os.replace()` commits it. Describing that as validation after replacing the original would give readers the wrong failure model.\n\nOn POSIX, mode `0600` limits backup access to the owner. Windows uses ACLs with different semantics; do not interpret Python's mode argument as an equivalent Windows security boundary.\n\nDevelopers can bypass the recent-write and process gates with `--force`. That flag does not bypass containment, symlink rejection, or invalid-content failures. Prefer closing the agent over overriding its concurrency checks.\n\nAdd the project's hook to `.pre-commit-config.yaml`:\n\n```\nrepos:\n  - repo: https://github.com/Ishannaik/agent-sweep\n    rev: v0.1.9\n    hooks:\n      - id: agentsweep\n```\n\nThe project's README uses this released tag in its example. Review release changes before choosing a newer pin.\n\nInstall the hook:\n\n```\npre-commit install\n```\n\nThe [hook definition](https://github.com/Ishannaik/agent-sweep/blob/main/.pre-commit-hooks.yaml) invokes:\n\n```\nagentsweep scan --all --detected\n```\n\nIt sets `pass_filenames: false` and `always_run: true`. Developers therefore get one history scan per commit regardless of which files they staged. With no detected history root, the scan exits clean.\n\nThe hook blocks a commit when the detector finds a credential-shaped value. It does not redact anything during the commit. Developers review the report, revoke real exposed credentials, and run a separate fix operation.\n\nThis is a local data-loss-prevention checkpoint with limits. A developer can bypass Git hooks. Malware can read a transcript before the next commit. A clean result means the scanner found nothing within the selected sources and rules, not that the workstation contains no secrets.\n\nKeep a staged-file secret scanner alongside it. The two scans inspect different stores.\n\nA redaction changes your local history. The provider still accepts a live key until you revoke it.\n\nAgentSweep includes provider-specific guidance. You can inspect a rule without scanning:\n\n```\nagentsweep explain stripe-live\n```\n\nFor common findings, developers can review [GitHub token settings](https://github.com/settings/tokens), [OpenAI API keys](https://platform.openai.com/api-keys), or [Anthropic API keys](https://console.anthropic.com/settings/keys). AWS credentials require the appropriate IAM access-key workflow. AgentSweep prints guidance; it does not invoke those providers' APIs.\n\nBackups contain the original plaintext. A same-user attacker who can read the transcript can also read its `.bak` file. After rotating the exposed credentials and confirming that you no longer need recovery, purge the backups:\n\n```\nagentsweep purge --source claude-code\n```\n\nIf you need recovery before that point:\n\n```\nagentsweep undo --source claude-code\n```\n\nUndo restores the secret-bearing original. Purge removes the backup files; it does not promise secure erasure from SSD blocks, snapshots, or cloud backup systems.\n\nMy workflow is to avoid pasting secrets, scan existing history, review findings, revoke exposed keys, redact local copies, and remove recovery backups when I no longer need them. The pre-commit hook provides another checkpoint as new history accumulates.\n\nYou can inspect the source, detection rules, and safety code in [Ishannaik/agent-sweep](https://github.com/Ishannaik/agent-sweep). If you report a missed credential format, share a synthetic example with the same shape. Do not attach the transcript you are trying to clean.\n\n*Written by [Ishan Naik](https://github.com/Ishannaik).*", "url": "https://wpnews.pro/news/ai-coding-agents-are-leaking-your-secrets-building-a-local-pre-commit-dlp-for", "canonical_source": "https://dev.to/ishannaik/ai-coding-agents-are-leaking-your-secrets-building-a-local-pre-commit-dlp-for-claude-code-1i9g", "published_at": "2026-10-07 03:15:10+00:00", "updated_at": "2026-10-07 03:17:47.944826+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety"], "entities": ["AgentSweep", "Claude Code", "Codex", "Cursor", "Windsurf", "Aider", "Aikido", "@bitwarden/cli"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/ai-coding-agents-are-leaking-your-secrets-building-a-local-pre-commit-dlp-for", "markdown": "https://wpnews.pro/news/ai-coding-agents-are-leaking-your-secrets-building-a-local-pre-commit-dlp-for.md", "text": "https://wpnews.pro/news/ai-coding-agents-are-leaking-your-secrets-building-a-local-pre-commit-dlp-for.txt", "jsonld": "https://wpnews.pro/news/ai-coding-agents-are-leaking-your-secrets-building-a-local-pre-commit-dlp-for.jsonld"}}