Skip to main content

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​

terminal
envshed scan [options]

Options​

OptionDescription
-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-onlyOnly look for known token formats, without reading the vault
--no-markReport only: do not mark secrets as exposed on the server
--cleanReplace found values in the files, after confirmation
--yesSkip the confirmation for --clean
--undo [id]Restore the lines changed by a clean (the newest one, or the given backup id)
--forget-backupsDelete all clean backups and their keys. After this, a clean cannot be undone.
--output jsonPrint a single JSON document for scripts and agents

Exit codes​

CodeMeaning
0Nothing found, or an undo finished
1Error, or --clean was refused
2Something was found, even if --clean then removed it: the value is still exposed until it is rotated

What is scanned​

  • By default, projects/ and history.jsonl in the Claude Code directory: CLAUDE_CONFIG_DIR if set, otherwise ~/.claude.
  • Files ending in .jsonl, .json, .txt, .md and .log. A file given directly to --path is 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 as https://, or an AWS region such as us-east-1.
  • Values that are the organization or project slug, or one part of it: for project acme-billing, acme, billing and acme-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:

terminal
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.

Cleaning does not replace rotating

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.

terminal
$ 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 .json file 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​

terminal
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.