CLI command
skarbiec scan
Find where a vault value still sits in plain text on this machine — a .env left behind after env import, a configuration file, a shell history, a note — so the copy can be deleted or the value replaced; with --history, find the vault values a repository's history already holds, since a copy committed once stays in every clone after the file is fixed.
Invocation#
skarbiec scan <path>... --fields <field,...> [--min-length <N>] [--refuse]
skarbiec scan <repo> --history --fields <field,...> [--min-length <N>]Purpose#
Find where a vault value still sits in plain text on this machine — a .env left behind after env import, a configuration file, a shell history, a note — so the copy can be deleted or the value replaced; with --history, find the vault values a repository's history already holds, since a copy committed once stays in every clone after the file is fixed.
Required inputs and options#
- One or more files or directories; relative paths are taken in the working directory, and directories are walked to the bottom without following symbolic links. --fields names the fields whose values are looked for, such as password,token,api_key. --min-length leaves values shorter than it uncompared, for a short username or PIN that would match everywhere. --refuse makes a copy found an error, for a git hook or a CI step. --history takes exactly one path, a repository's directory, and reads its history instead of its files.
Output and state effects#
- Opens every live item, takes the non-empty text of each named field, compares each value byte for byte with every regular file read, journals a scan entry with the paths, fields and counts, and prints ok, paths, fields, values_compared, below_min_length (each item and field left uncompared), files_read, found (each copy with its file, the line it starts on, the item and the field), unreadable_files (each path that could not be read, with its error) and unreadable_items (each item that would not open, with its error). With --history it reads git log --all -p over every branch, compares each value with the lines each commit added, journals a scan-history entry with the counts, and prints ok, repo, fields, values_compared, below_min_length, commits_read, found (each commit, file, item and field once, with repair saying to replace the value where it is issued and store the new one) and unreadable_items. No value is printed. ok is true only when no copy was found and everything could be read. The operator API answers the same at POST /v1/operator/scan with path, an absolute path on the machine it runs on, fields and optional min-length, and at POST /v1/operator/scan-history with repo as an absolute path, on the machine it runs on only.
Refusals#
- No path or no --fields exits 2 with the usage, as does --fields naming no field; --min-length that is not a whole number is refused naming it. --history with anything but one repository exits 2 with `usage: scan <repo> --history --fields <field,...> [--min-length <N>]`, and a directory git cannot read the history of is refused with git's status and sentence. Fails when the vault cannot be opened. With --refuse, a copy found fails naming every copy as file:line holds <item>'s <field>, after the journal entry is written. A file, directory or item that cannot be read is reported rather than skipped, so a clean answer never hides what was not compared.
Examples#
skarbiec scan ~/projects ~/.zsh_history --fields password,token,api_key,secret_access_key --min-length 12 skarbiec scan . --fields token | jq -r '.found[] | "\(.file):\(.line) \(.item)"' skarbiec scan dist --fields token,api_key --refuse # in CI, before publishing skarbiec scan . --history --fields password,token,api_key | jq -r '.found[] | "\(.commit) \(.file) \(.item)"'