Repository navigation
feat(secret-vault): add 'set' verb — stdin-fed programmatic write path - #2132
Conversation
The vault could only be written through the chat-interception path (process_and_scrub); list/get/env existed but no set. Callers that already hold a value programmatically (desktop Settings BYO-key entry, scripts) had no supported write. - vault_intercept.set_vault_key(key, value): public setter over the same storage as chat 'vault set' (security add-generic-password -a sutando -U + manifest registration). Env-var-safe key validation, empty-value refusal. - CLI 'set KEY' reads the value from stdin — never argv, so the secret stays out of ps and shell history; a value passed on argv is refused loudly. Exactly one trailing newline stripped; inner whitespace preserved. - 6 new tests (26 total green) + live Keychain roundtrip verified (set -> get -> list -> security delete cleanup). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VwMjjL4xVu4qUnAoCzdbTH
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 51%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
qingyun-wu
left a comment
There was a problem hiding this comment.
Cold review — LGTM, no blocking findings. CI green at review time.
Verified:
- stdin-not-argv is enforced both ways: value read from stdin AND a loud refusal when a value appears on argv (
len(args) > 2) — the leak vector is closed, not just avoided. Exactly the shape the desktop Settings BYO-key entry (G6/G7) should shell to. - Newline semantics are right and tested: exactly one trailing newline stripped (
printfadds none,echoadds one), inner whitespace/extra newlines preserved as data. set_vault_keymirrors the chat-intercept storage (_store_in_keychain,-Uupdate) with env-safe key validation + empty-value refusal; success message never echoes the value.
One non-blocking nit: _ENV_KEY_RE uses $, and Python's $ also matches just before a trailing newline — so set_vault_key("KEY\n", v) passes validation and stores a service name with an embedded newline. \Z instead of $ (or key = key.strip() before the match) closes it; your "k\nnewline" test case has the newline mid-string so it doesn't catch this edge. Two-character fix, fine as a follow-up or squash.
This unblocks the G6 Settings write path nicely — once the owner calls G7 (vault vs .env), Settings shells printf '%s' "$val" | secret-vault.py set KEY and secrets never touch disk. — @sutando-qingyun-001
The argv guards live in main(), not cmd_set — the gate flagged them uncovered. Adds dispatch tests: set→cmd_set happy path, missing KEY, value-on-argv refusal (cmd_set never called), and unknown-subcommand usage mentioning the set verb. 30/30 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VwMjjL4xVu4qUnAoCzdbTH
|
Coverage gate addressed: the uncovered lines were |
|
No blocking findings on head 410a7ce. I re-reviewed the latest head after the coverage-test push. The new Verification in an isolated worktree:
Non-blocking caveats:
Approval signal. Reviewed by Qingyun's Codex. |
liususan091219
left a comment
There was a problem hiding this comment.
Justification: [change-requested: missing_before_after]
Daily triage sweep (gate 2) — conditions fired on this PR:
- missing_before_after (behavior claim needs a significant BEFORE/AFTER, not just unit tests): Add before/after CLI output proving stdin set then get works.
Addressing these (push fixes or reply with evidence) resets the clock; the re-review loop re-judges automatically after new commits.
|
Supplying the before/after asked in the sweep CR (produced on a fleet machine, PR head 410a7ce, isolated clone): BEFORE (main) — `set` verb doesn't exist: AFTER (PR #2132) — stdin-fed set + get roundtrip works: Notes: the value never appeared in argv (stdin-fed, as claimed); the run also exercised the legacy-manifest migration path ("read legacy manifest … migrating to /state/secret-vault/"). Test key deleted from Keychain + manifest after the check. This covers the missing_before_after ask — review stands until the author confirms/absorbs it. |
|
LGTM. Focused verification on
I don’t have actionable findings on this version. Qingyun's Personal Codex Agent |
|
@cla-assistant check |
…manifest (#2177) * feat(secret-vault): add 'delete' verb — remove a key from Keychain + manifest Counterpart to #2132's 'set' verb. delete_vault_key removes the Keychain item (security delete-generic-password) AND deregisters the manifest entry (atomic rewrite, same pattern as _register_key). Drift-tolerant: a manifest entry whose Keychain item is already gone (or vice versa) is cleaned up rather than erroring; KeyError only when the key exists in neither place. ValueError on invalid key names (same _ENV_KEY_RE gate as set). CLI: `secret-vault.py delete KEY` → "deleted 'KEY'" / exit 1 on missing. Motivation: consumers that need to FORGET a stored secret — first user is the ag2space desktop's cloud_logout (forgetting the sutk_ cloud bearer), which is currently impossible because the vault surface is set/list/get-only. Tests: 8 new (delete: keychain+manifest, drift cases, KeyError, key validation; deregister: rewrite + no-op; CLI: confirmation + exit 1). Full vault-skill suite 38 pass; vault-intercept suite 47 pass. Live smoke: set → list → delete → get-fails → manifest-clean → re-delete KeyError. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194Kffr9WaeHAu19TZ8rjhK * test(vault): cover delete error/exit branches — missing key, invalid name, no-KEY dispatch, manifest drift, deregister no-op (coverage gate) * fix(vault): only tolerate not-found (rc 44) on Keychain delete — other failures raise and keep the manifest; unshadow TestVaultCliDelete (Codex P1+P2) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Qingyun Wu <qingyun@ag2.ai>
The vault has
list/get/envbut noset— storage only happens inside the chat-interception path (process_and_scrub), so a caller that already holds a value programmatically (the desktop Settings BYO-key entry being built in the onboarding-parity track, or any script) has no supported write path.Changes
vault_intercept.set_vault_key(key, value)— public setter over the exact storage the chatvault setuses (security add-generic-password -a sutando -s KEY -U+ manifest registration). Validates env-var-safe key names (keys double as env vars via theenvverb) and refuses empty values.set KEY— value read from stdin, never argv (printf '%s' "$VAL" | secret-vault.py set KEY), keeping the secret out ofpsand shell history; a value passed on argv is refused loudly rather than stored. Exactly one trailing newline stripped, inner whitespace preserved.Verification
tests/vault-skill.test.py: 26/26 (6 new — setter delegation, key/value validation, stdin read + newline semantics, error exit).set→getreturns the value →listshows the key → cleaned up viasecurity delete-generic-password.Motivation: G6/G7 of the desktop onboarding-parity track — a Tauri
vault_setcommand will shell out to this verb instead of duplicating Keychain layout knowledge.🤖 Generated with Claude Code
https://claude.ai/code/session_01VwMjjL4xVu4qUnAoCzdbTH