A local password-proxy CLI built for AI coding tools. Credentials stay encrypted on disk; the AI writes only {{placeholders}}; pwhide injects the real secrets, runs the command, and redacts the output.
All six security invariants are implemented; the full threat model ships with the source.
No command can display a password; output is streamed through the redactor first; secrets go into child processes and never onto your terminal.
Weak secrets (password = common phrase) are rejected at entry; echo-probe commands are blocked; high-frequency collisions raise alerts. Redacted positions still read as placeholders, so the AI has nothing to diff against - this side channel is closed end to end.
Root ownership plus immutable flags: the vault can only be replaced wholesale. Elevated installs move ciphertext only, guarded by symlink, ownership, and inode checks - three real attack classes verified in adversarial Docker runs.
Script-stdin (recommended: secrets in neither argv nor environ) > environment injection > inline args. Wraps bash / sh / pwsh / cmd.
Type, username, password, tenant, and custom fields. Metadata is queryable (so the AI can assemble commands); secrets are not. list --json is the AI's primary interface.
286 个测试(单元 + 集成), CI on three OSes (including a real-sudo hardening pass), 22 rounds of 3-agent security review, six Native AOT builds with SHA256 checksums.
Grab the binary for your platform from Releases (or the repo's dist/, checksums included), put it on PATH, and four commands get you going:
The full path from recording credentials to everyday AI usage.
set reads the password with hidden input; -f field=value adds custom fields (sensitive ones like api_key: use -f name for hidden interactive input to keep them out of shell history); non-interactive flows use --password-stdin (passwords and field values are trimmed on entry). Each field is interactively asked whether to encrypt: non-sensitive info such as IPs and protocols can be stored plain (values then appear in list --json, no unlock needed); scripts use -pf name=value to opt in explicitly. Weak passwords are rejected outright.
{{db}} is the password, {{db.user}} the username, {{db.tenant}} the tenant, and {{db.field}} a custom field. Entry names are discoverable via list; passwords have no query interface. Clashing with Helm / Jinja {{ }} templates? Add --ph '#' to switch to #db# (or --ph '@' for @db@); parsing and redaction semantics stay identical.
Inline args are the most intuitive but briefly visible to ps; --env entry:VAR injects into the environment (note: ancestor processes can read /proc on Linux); the recommended script-stdin mode keeps placeholders in the script, and pwhide exec -f deploy.sh substitutes purely in memory - nothing hits disk or argv. Pipes are supported and binary-safe: cat data.sql | pwhide exec -- mysql ..., and -f - reads the script from a pipe (with a piped stdin, the passphrase must come from keychain/env/file).
0 success (child exit code passed through); 2 usage error or echo-probe block; 3 passphrase/vault error or elevation needed; 4 unknown placeholder (the command never runs); 124 timeout, process tree killed.
doctor checks protection state and repairs interruptions; rotate replaces the key pair; tired of typing the passphrase? pwhide keychain set stores it in the OS keychain and exec becomes zero-interaction; automation can also use PWHIDE_PASSPHRASE_FILE (chmod 600). When a secret contains shell metacharacters, pwhide warns and suggests --env.
The win-x64 package includes PwHide.Gui.exe — unlock with the master passphrase, then visually create and list entries. Same encrypted vault as the CLI, always in sync.
Paste this contract into your project's AGENTS.md / CLAUDE.md, or install the bundled skill (skills/pwhide/install.sh):
Six invariants run through the whole implementation; the honest boundary statement lives in the threat-model.md。
| Invariant | Meaning |
|---|---|
| I1No plaintext output | No get/show commands exist; metadata is queryable, secrets are not (single exception: --verify human check - interactive terminal with a typed passphrase; unreachable from pipes, scripts, or AI) |
| I2Unknown means no run | Any unresolved placeholder exits with code 4; the child never starts |
| I3Output redaction | Byte-accurate replacement back to placeholders across buffer boundaries - the final line of defense |
| I4Private key stays local | A leaked ciphertext alone cannot be decrypted |
| I5Never echo resolved commands | Errors and logs only ever contain the placeholder version |
| I6Wholesale replacement only | After hardening, vault changes go through one controlled path; the exec read path never elevates |
Honest disclosure is part of this project.
pwhide injects the secret into the child process; the parent shell, the conversation, and history only ever see placeholders. Even if a command prints the password, pwhide replaces it with the placeholder before the AI sees the output.
Three gates: weak secrets cannot be entered (common phrases rejected); echo/printf probe commands are refused outright (--allow-echo opts in explicitly); more than 32 occurrences of a secret in one command's output triggers an alert. This defends against accidental leakage - a malicious agent deliberately exfiltrating via encodings is outside the declared boundary (see the threat model).
Yes (win-x64 / win-arm64 binaries; the pwsh path is fully tested). cmd has unusual quoting - prefer pwsh; PowerShell 5.1 encoding pitfalls are worked around internally.
There is no recovery. No backdoor - by design. Run list first (metadata needs no passphrase) to export an inventory, then re-init and re-enter.
The human verification channel: pwhide verify entry (or inspect entry --verify) requires you to type the master passphrase in a real terminal (keychain and env vars are ignored), then decrypts and displays the password and fields for your review. pwhide exec --verify shows the decrypted injection values and the full command before running, and only proceeds on confirmation. This channel exists only on a real interactive terminal and refuses redirection or AI invocation.
Yes. Run pwhide keychain set once (it validates the passphrase before storing it in the OS keychain); afterwards exec / set pick it up automatically and AI calls need zero interaction. Undo with keychain clear; disable temporarily with PWHIDE_NO_KEYCHAIN=1. The passphrase only ever lives in pwhide's process memory and never appears in any output.
{{ }} templates (Helm/Jinja).Add --ph '#' to exec and write placeholders as #db# (or --ph '@' for @db@). {{db}} becomes a literal, only #db# is resolved; output redaction and error messages render with the active delimiters. Security semantics are identical to brace mode.
MIT open source, free. Binaries come from GitHub Releases with SHA256 checksums.