envshed scan
Search the local history of your AI coding agents for secrets. It looks for the values in your vault and for known token formats, and reports the key, file and date. It never shows a value or the text around it.
By default it changes no file. It starts with Claude Code.
Usage
envshed scan [options]
Options
| Option | Description |
|---|---|
-o, --org <slug> | Organization slug |
-p, --project <slug> | Project slug |
-e, --env <slug> | Check only this environment. Default: every environment you can read. |
--path <path> | File or directory to scan instead of the agent history |
--formats-only | Only look for known token formats, without reading the vault |
--no-mark | Report only: do not mark secrets as exposed on the server |
--clean | Replace found values in the files, after confirmation |
--yes | Skip the confirmation for --clean |
--undo [id] | Restore the lines changed by a clean (the newest one, or the given backup id) |
--forget-backups | Delete all clean backups and their keys. After this, a clean cannot be undone. |
--output json | Print a single JSON document for scripts and agents |
Exit codes
| Code | Meaning |
|---|---|
0 | Nothing found, or an undo finished |
1 | Error, or --clean was refused |
2 | Something was found, even if --clean then removed it: the value is still exposed until it is rotated |
What is scanned
- By default,
projects/andhistory.jsonlin the Claude Code directory:CLAUDE_CONFIG_DIRif set, otherwise~/.claude. - Files ending in
.jsonl,.json,.txt,.mdand.log. A file given directly to--pathis read whatever its name. Symlinks inside a directory are not followed.
Files are streamed, so large histories do not fill memory.
Vault values
Values are matched as written, and in their JSON-escaped (one and two levels), URL-encoded and base64 forms. Without a project, or with --formats-only, this part is skipped and the report says so.
Some values are not checked, and the report gives the count:
- Values too short or too common to match reliably, such as
3000,true,development, a bare URL scheme such ashttps://, or an AWS region such asus-east-1. - Values that are the organization or project slug, or one part of it: for project
acme-billing,acme,billingandacme-billing. These names appear in the paths and prompts of almost every session. - Placeholders and disabled secrets, which the API does not return.
- A key where you have a personal override: the override value is checked, not the team value.
- Environments you cannot read.
Known formats
Prefixed tokens such as GitHub, Stripe, AWS and OpenAI keys, private keys and JWTs, in all projects, whether or not they are in your vault. To skip one, add a line to .envshed-allow. The looser rules from the agent hooks (entropy and phrases such as "the password is") are not used, because a history is full of code samples that would match.
Exposed secrets
Each vault value found marks that secret as Exposed on the server, and shows a badge in the dashboard. Only key names are sent. The file, the date and the value stay on your machine. Each mark is recorded in the audit log.
- Marking needs write access. With read access you still get the local report, and the command tells you nothing was marked.
- A personal override is not marked, because the team value is not what leaked.
- A key shared from another environment is marked by scanning that environment.
- A key whose value is a reference, such as
${OTHER_KEY}, is not marked. The keys it points to are reported and marked instead, and rotating them is the fix. - Saving a different value clears the mark, however it is saved:
envshed secret set,envshed push, the dashboard, a rollback or a restore. Saving the same value again does not.
Rotating
The report prints the commands. Run the first in a separate terminal, not in an agent chat:
envshed secret set API_KEY -e development
envshed request API_KEY -e development --reason "rotate after exposure"
Use the second to ask someone else for the new value. A token found only by its format is not in this vault, so revoke it with its provider.
If an agent saw a value, it was probably sent to a model provider. Removing it from local files does not undo that.
Cleaning
--clean replaces each value with [ENVSHED:KEY], or the format name for a token that is not in the vault.
$ envshed scan --clean
- It needs confirmation. Without a terminal, or with
--output json, it refuses unless you pass--yes. - Files are replaced atomically and keep their permissions and modification time.
- Only the content of JSON strings changes, so a JSONL line or a pretty-printed
.jsonfile stays valid and everything else on the line keeps its exact bytes. - In plain text, a private key or a vault value that runs across lines is removed from every line it covers. Each of those lines holds a marker, so the file keeps its line count.
- A private key split across separate JSON strings, such as an array with one line per string, is left in place and the scan keeps reporting it. Remove it by hand.
- A file that changes while being cleaned, usually a session still in use, is skipped and reported. Close the session and run again.
- A line over 8 MB is reported in the scan but not rewritten. After cleaning, the command scans the files again and lists anything still there.
Undo
envshed scan --undo
This restores the original lines. Lines added since the clean are kept. A cleaned line edited since then is left alone and reported, and the backup is kept.
The original lines are kept in ~/.envshed/scan-backups/, encrypted with AES-256-GCM under a random key stored in the OS keychain (macOS Keychain, or secret-tool on Linux). The backup and key are deleted after a complete undo, and --forget-backups deletes them at any time.
With no keychain available, for example in CI, in many containers, on Windows, or with ENVSHED_KEYCHAIN=off, --clean refuses and changes nothing. The key is never stored beside the backup, because that would be the same as keeping a plaintext copy. The scan itself still works.
What is kept on disk
Vault values are fetched from the API and held in memory for the run. There is no cache, and no hash of a value is written anywhere. Reading the values is recorded in the audit log, like any envshed pull.
Limits
This helps you find and clean up accidental leaks. It is not a barrier against an agent with shell access, which can read a value it can reach and transform it into a form this does not recognize. A value encoded in a way not listed above, or cut in pieces, or base64 of a larger text that includes the value, will not be found. Finding nothing does not prove nothing leaked.