feat(cli): add --passphrase-file flag for agent create-token#67
Merged
Conversation
… creation Lets `zerion agent create-token` read the wallet passphrase from a chmod-0600 file instead of a TTY prompt, so CI, headless servers, and scripted agent setup can issue scoped tokens without an interactive shell. Argv-passed passphrases are intentionally not supported (they leak to ps, shell history, CI logs); a file at SSH-private-key parity is the security/UX balance. - `readPassphraseFromFile()` enforces mode 0600 on POSIX, strips one trailing LF/CRLF (leading/trailing spaces inside the passphrase are preserved), rejects empty / non-regular files - Windows skips the perm check (NTFS ACLs, not POSIX bits, are authoritative there — documented in SKILL.md) - Scope limited to `agent create-token`; `wallet create/import` etc. still require TTY - 8 new unit tests cover missing file, loose perms, 0600 happy path, CRLF, embedded spaces, empty file - Docs updated: zerion-agent-management SKILL.md gains a non-interactive section + env table (Docker / k8s / GH Actions / Vault) + new `passphrase_file_error` row; zerion-sign and zerion-trading point CI users at the new flag; README adds a row to the agent-tokens table Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three input-validation gaps in `agent create-token`: - `--passphrase <value>` silently fell through to the TTY prompt instead of refusing. Now rejects with `unsupported_flag` and points the user at `--passphrase-file` (argv secrets leak to ps, shell history, and CI logs). - `--passphrase-file` with no path (parsed as boolean true) reached `readPassphraseFromFile(true)` and surfaced a confusing "Cannot read passphrase file" error. Now fails fast with `missing_args` and a usage example. - `--passphrase-file=` (empty string) fell through to the TTY prompt instead of erroring. Now also rejected. These checks moved above policy resolution so flag-shape errors report before keystore/policy state is touched — fail-fast on user input regardless of environment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…docs Pre-landing review of the --passphrase-file branch surfaced two issues. 1. cli/utils/common/prompt.js — readPassphraseFromFile only checked mode bits, not ownership. A symlink at the user's path resolving to another user's 0600 file (shared dev box, multi-tenant CI, even /etc/shadow on misconfigured boxes) would pass perm validation and leak that user's content into the calling process as a passphrase. Added stat.uid === process.getuid() ownership check on POSIX, matching SSH IdentityFile behavior. Skipped on Windows where uid isn't meaningful (NTFS ACLs already enforced via OS). 2. skills/zerion-agent-management/SKILL.md — "Agent vs manual" table classified `agent create-token` as Manual unconditionally and the section header read "Manual — humans only", contradicting the new non-interactive `--passphrase-file` subsection. Split the table row into default (Manual) vs --passphrase-file (Agent-capable), renamed the section header, and replaced the Linux-only /run/zerion/pass example with a portable ~/.zerion-pass primary path plus an explicit Linux-tmpfs / macOS-equivalent block. Documented the new owner-uid refusal rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewers (and users) asked what the file actually contains. The flag description previously assumed the reader would infer that the file's entire contents are the passphrase. Made it explicit: - Added a one-line "what this is" sentence at the top of the section: the path points to a plain-text file whose contents are the wallet passphrase (the same string a human would type at the TTY prompt). - Added a "File format" subsection with the encoding contract: plain UTF-8, raw bytes only, one optional trailing newline stripped, spaces inside the passphrase preserved, empty rejected. - Added a worked-examples table showing what each common shell command produces and how the CLI interprets it, including the quote-as-literal footgun. - Promoted the existing rule list to its own "Rules" subsection so format constraints and security constraints are visually separate. - Expanded the router help string to mention plain-text contents, ownership, UTF-8, and the newline strip rule on one line so the flag is self-explanatory from `zerion --help`. No behavior change. Tests: 167/167. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--passphrase-file <path>tozerion agent create-tokenso the wallet passphrase can be read from achmod 600file instead of an interactive TTY prompt. Unblocks CI, headless servers, Docker / k8s / Vault, and scripted agent provisioning.--passphrase <value>→unsupported_flagerror) because they leak tops aux, shell history, and CI logs. SSH-style file at parity is the security/UX balance.agent create-token.wallet create,wallet import, and other passphrase-gated commands still require an interactive TTY. Skill docs (zerion-agent-management,zerion-sign,zerion-trading) and the README updated to point CI users at the new flag.File format (documented in
zerion-agent-management/SKILL.md)The file
<path>points at is a plain UTF-8 text file whose entire contents are the wallet passphrase (same string a human would type at the TTY prompt). No JSON, nokey=value, no headers, no quotes. One optional trailing\n/\r\nis stripped; leading/trailing spaces inside the passphrase are preserved.Canonical usage:
Security model
readPassphraseFromFileenforces, on POSIX:0600— any group/other bits → refused withpassphrase_file_error.IdentityFilebehavior; prevents a planted symlink from leaking a different principal's secret into this process).On Windows, perm + ownership checks are skipped (POSIX mode bits and uid not meaningful there) — users are directed at NTFS ACLs in the docs.
Argv passphrase is rejected before any keystore state is touched; flag-shape errors fail fast and never reach OWS.
What's in the diff
Test plan
Out of scope (future work)
🤖 Generated with Claude Code