feat(B-0852.10): per-cred type handlers — parse <id>=<source> + resolve literal/@file/env:VAR + per-type validation (60 unit tests; pure TS)#5418
Merged
Conversation
…ve literal/@file/env:VAR + per-type validation (60 unit tests) B-0852 sub-row .10 — pure TS module composing B-0852.5 (declarative manifest; landed PR #5414) + B-0852.1 (crypto module; landed PR #5411) toward B-0852.9 zflash --bake-cred CLI override per Aaron 2026-05-27 CLI-override design. Three pure layers: 1. parseBakeCredArg(arg) — splits "<id>=<source>" preserving = in value 2. resolveValueSource(source) — handles literal / @file / env:VAR 3. validateValue(buf) per cred-type handler — PAT / JSON / SSH pubkey Per-type handlers (one per default manifest entry): - GH_CLI_HANDLER — literal/file/env; non-empty string - CLAUDE_HANDLER — literal/file; valid JSON object - GEMINI_HANDLER — literal/file; valid JSON object - CODEX_HANDLER — literal/file; valid JSON object - SSH_OPERATOR_PUBKEY — literal/file; OpenSSH key-type prefix check - SSH_HOST_KEYS — Phase 1 deferred (empty supportedSources) resolveBakeCred() full pipeline composes the three layers + gates unsupported source types per handler.supportedSources (e.g., claude rejects env: source — JSON creds belong in files, not env vars). Test output: 60 pass / 0 fail / 71 expect() calls / 106ms. Format prettier-clean. No third-party deps; node:fs + node:os only. Composes with: - B-0852 parent row (CLI-override design per Aaron 2026-05-27 sharpening) - B-0852.5 (manifest schema) — handler.id matches manifest entry id - B-0852.1 (crypto module) — resolved bytes feed encrypt() in next slice - B-0852.9 future — zflash --bake-cred CLI consumes this module
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Pull request overview
Adds a new pure-TS credential “bake” handler pipeline for B-0852.10, covering CLI arg parsing (<id>=<source>), value-source resolution (literal / @file / env:VAR), and per-credential validation, with an accompanying acceptance test suite.
Changes:
- Introduces
parseBakeCredArg,resolveValueSource, andresolveBakeCredplus a default per-cred handler registry (PAT / JSON / SSH pubkey / deferred host keys). - Adds 60 Bun tests covering parsing edge cases, source resolution, per-type validation, and end-to-end pipeline behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tools/installer/zeta-cred-handlers.ts | New handler registry + parse/resolve/validate pipeline for --bake-cred sources |
| tools/installer/zeta-cred-handlers.test.ts | Acceptance tests for all pipeline layers and default handler behaviors |
…attribution + ghp_ sweep + beforeAll/afterAll cleanup + pure-resolution wording PR #5418 Copilot review caught 5 findings on the per-cred type handler module. All 5 valid; fixed in single commit before re-arming auto-merge. P0 SECURITY (parseBakeCredArg secret leak): Before: error messages included raw `arg` which may contain a PAT / JSON cred / SSH key. Operator typo → secret echoed to logs. After: errors include only id (operator-controlled name); value- source explicitly omitted with comment naming the leak risk. P1 codebase convention (name attribution in code): Before: header attributed CLI-override design to a named individual. After: role-ref form "operator-named; substrate-anchor in B-0852 row body" per repo's no-name-in-code rule. P1 operational (ghp_ token fixtures trip secret scanners): Before: 3 occurrences of "ghp_*" in test fixtures. After: swept to "TEST-NOT-A-REAL-TOKEN-*" prefix (same pattern as PR #5411 B-0852.1 already used). P1 maintainability (tmp-dir cleanup as teardown test): Before: describe block created tmpdir at evaluation time; cleanup via dedicated "teardown" test (skip-filterable; order-dependent; leaks if earlier test throws). After: beforeAll() + afterAll() — afterAll always runs even on test failure; canonical bun:test cleanup pattern. P2 documentation (mislabeled "pure" function): Before: "Pure resolution layer (file read + env access only — no network)." — the file/env reads are side effects. After: "Local-only side effects: file read + env access. No network. Not pure." All 60 tests still pass (now 59 actually — removed the dedicated "teardown" test since beforeAll/afterAll handle cleanup correctly). Resolves Copilot threads PRRT_kwDOSF9kNM6FBxi3 + PRRT_kwDOSF9kNM6FBxjO + PRRT_kwDOSF9kNM6FBxjc + PRRT_kwDOSF9kNM6FBxjp + PRRT_kwDOSF9kNM6FBxj3 on PR #5418.
This was referenced May 27, 2026
AceHack
added a commit
that referenced
this pull request
May 27, 2026
…xt schema (17 unit tests) (#5421) B-0852 sub-row .2 first slice — the on-disk wire format that B-0852.2b persist/restore CLIs will use to read/write /esp/zeta-creds.enc. Pure functions; no I/O. Two layers: 1. Envelope serialization (binary; little-endian; v1 magic "ZCV1"): - 8-byte header: 4-byte magic + 4-byte reserved - Length-prefixed: salt (u16) + iv (u16) + tag (u16) + ciphertext (u32) - Trailing bytes rejected in v1 (v2 will explicit-version-bump) 2. CredBundle plaintext schema (post-decryption JSON): - schemaVersion: 1 - globalCreds: { id: bytes } (personaScoped=false manifest entries) - personaCreds: { persona: { id: bytes } } (personaScoped=true) - Bytes base64-encoded inside JSON for safe transport - Composes with B-0852.5 manifest's personaScoped flag Full pipeline (covered by 1 integration test): CredBundle → encodeBundle → encrypt (B-0852.1) → serializeEnvelope → [disk write/read simulation] → parseEnvelope → decrypt → decodeBundle → CredBundle (byte-identical) Test output: 17 pass / 0 fail / 29 expect() calls / 1.67s (scrypt dominates timing per B-0852.1 OWASP-recommended N=2^17). Composes with: - B-0852 parent (cred persistence) - B-0852.1 crypto module (merged PR #5411) — Envelope type producer - B-0852.5 cred-manifest schema (merged PR #5414) — personaScoped semantics - B-0852.10 per-cred handlers (merged PR #5418) — value producers feed globalCreds/personaCreds maps - B-0852.2b future — persist/restore CLIs consume this module What this is NOT: - NOT the persist CLI (next slice; needs FS + passphrase prompt) - NOT the restore CLI (next slice; same) - NOT zflash --bake-cred integration (B-0852.9) Co-authored-by: Lior <lior@zeta.dev>
AceHack
added a commit
that referenced
this pull request
May 27, 2026
…ersistence stack (19 integration tests; replaces conflict-dirty #5422) (#5425) * feat(B-0852.2b): persist + restore CLIs composing the full cred-persistence stack (19 integration tests) B-0852 sub-row .2 final slice — operational CLI surface that composes all 4 already-shipped modules: - B-0852.1 crypto (encrypt/decrypt with scrypt+HKDF+AES-256-GCM) - B-0852.5 manifest (declarative cred catalog) - B-0852.10 per-cred handlers (--bake-cred parse + validate) - B-0852.2a envelope (wire format + CredBundle plaintext schema) THREE files: 1. tools/installer/zeta-creds-persist.ts (CLI) Usage: bun zeta-creds-persist.ts --usb-uuid <uuid> --output /esp/zeta-creds.enc --passphrase-{env VAR | file PATH} [--persona <name>] [--bake-cred <id>=<value-source>]... Composes --bake-cred args + manifest personaScoped flag → CredBundle → encrypt via crypto module → serialize via envelope → write to ESP. 2. tools/installer/zeta-creds-restore.ts (CLI) Usage: bun zeta-creds-restore.ts --usb-uuid <uuid> --input /esp/zeta-creds.enc --passphrase-{env VAR | file PATH} [--persona <name>] [--target-root /] [--dry-run] Reads encrypted blob → parseEnvelope → decrypt → decodeBundle → writes each cred to its manifest-declared paths under target-root. Exit codes: 0=ok / 2=arg-parse / 3=file-read / 4=envelope-parse / 5=decrypt-fail (wrong pass/UUID/tampered) / 6=bundle-decode / 7=manifest-mismatch. --dry-run prints plan without writing. 3. tools/installer/zeta-creds-persist-restore.test.ts (19 tests) - parsePersistArgs / parseRestoreArgs (well-formed + each error path) - composeBundle (global / persona / missing-persona / unknown-id) - resolveCredPaths (~ expansion + absolute paths under target-root) - persist→restore round-trips via tmpdir (gh-cli global + claude persona) - wrong-passphrase / wrong-UUID / tampered-blob / invalid-magic each surface their canonical exit code via planRestore Note: passphrase-interactive-prompt deferred to a NixOS module wrapper (B-0852.4); these CLIs require --passphrase-file or --passphrase-env so the modules are scriptable + test-driveable. Interactive entry happens at the install-script Step 6.77 (per B-0852 row body) which prompts + exports to env before invoking these CLIs. Test output: 19 pass / 0 fail / 28 expect() calls / 2.06s (scrypt-bound). Format prettier-clean. Composes with: - B-0852 parent (cred persistence) - B-0852.1 crypto module (merged PR #5411) - B-0852.5 cred-manifest (merged PR #5414) - B-0852.10 per-cred handlers (merged PR #5418) - B-0852.2a envelope (PR #5421 — this PR chains off; will resolve cleanly when #5421 merges to main) - B-0852.4 future — NixOS module wraps these CLIs with passphrase prompt - B-0852.3 future — zeta-install.sh Step 6.77 picker invokes these CLIs - B-0852.9 future — zflash --bake-cred at flash-time invokes persist directly What this is NOT: - NOT the interactive passphrase prompt (B-0852.4 NixOS-module scope) - NOT the zeta-install.sh Step 6.77 integration (B-0852.3) - NOT the zflash --bake-cred at flash time (B-0852.9; same persist CLI invoked from operator's Mac instead of target boot) * fix(B-0852.2b): 14 Copilot findings — P0 CodeQL secret-leak + 5 error-handling bugs + applyPlan refactor (single decrypt) + 0-byte ciphertext + docs PR #5422 Copilot review caught 14 findings; all valid. Comprehensive fix-pass before re-arming auto-merge. P0 CodeQL clear-text-logging (2 alerts; persist.ts L129 + restore.ts L240): CodeQL flagged the env-var NAME from --passphrase-env being included in error strings (taint tracker treats env[passphraseEnv] access as sensitive → var-name becomes tainted). Fix: omit env-var name from error message; generic "--passphrase-env target var is not set or is empty" instead. Same change in both files. Error-handling bugs: - readFileSync(passphrase-file) in parseArgs not wrapped → could throw on permission failure instead of returning {error}. Fixed in both persist + restore. - readFileSync(input) in restore main() not wrapped → could throw on permission failure instead of returning code 3. Fixed. - writeFileSync(output) in persist main() not wrapped → could throw on unwritable path. Fixed; new exit code 4 for write failure. applyPlan refactor (P1 design): Prior: planRestore + applyPlan each did full parse→decrypt→decode, doubling scrypt cost + extending passphrase-derived key lifetime in memory. Now: planRestore returns RestorePlan with embedded value Buffer per write entry; applyPlan takes the plan + just writes (no decrypt). Single scrypt invocation per restore. Also fixes the silent-skip mismatch — applyPlan now consumes the plan's pre-validated writes; can't accidentally bypass the manifest match checks. 0-byte ciphertext (P2 envelope): MIN_BLOB_LEN was header + lens + 1-byte ciphertext; AES-GCM allows empty plaintext/ciphertext. Fixed: drop the +1. Documentation: - parseArgs docs: removed "Pure (no I/O)" claim (does FS reads for --passphrase-file); accurate description in new doc comment - Usage docs: removed "(interactive prompt)" mention (not implemented in this entry-point per design); replaced with "interactive prompting is the wrapping NixOS module's responsibility (B-0852.4)" Unused import: - tools/installer/zeta-creds-restore.ts: removed unused `join` import from node:path Test updates: - applyPlan signature changed; updated 2 test cases to call planRestore first + pass plan to applyPlan. All 19 persist/restore tests + 17 envelope tests still pass. Resolves 14 Copilot threads on PR #5422. --------- Co-authored-by: Lior <lior@zeta.dev>
AceHack
pushed a commit
that referenced
this pull request
May 27, 2026
…de narrowing fix-fwd for B-0852.2a/2b/10 discriminated unions (3 CI failures resolved) Three CI failures on PR #5426 resolved in single fix-pass: 1. **check docs/BACKLOG.md generated-index drift**: regen via `BACKLOG_WRITE_FORCE=1 bun tools/backlog/generate-index.ts` to include new B-0857.1 sub-row entry. 2. **lint (markdownlint) MD032/blanks-around-lists** at line 60 of B-0857.1 sub-row: blank line inserted before ordered list per markdownlint canonical rule. 3. **lint (tsc tools)** type errors in B-0852.2a/2b/10 substrate from just-merged PRs #5421/#5418/#5425: discriminated-union narrowing pattern `if (!(x instanceof Buffer))` doesn't narrow under tsc strict mode (bun test passed because bun's TS is more lenient). Substrate-honest fix: switch all narrowing to the discriminant-property check `if ("error" in x)` which TS strict mode narrows correctly. Files changed: - `tools/installer/zeta-creds-envelope.ts` (4 occurrences in parseEnvelope: salt/iv/tag/ciphertext) - `tools/installer/zeta-cred-handlers.ts` (1 occurrence in resolveBakeCred) - `tools/installer/zeta-cred-handlers.test.ts` (replaceAll: 4+ occurrences in resolveValueSource test variants) Fix is functionally equivalent — both `instanceof Buffer` and `"error" in x` correctly distinguish the union at runtime; the difference is only in tsc's ability to narrow. All 36 tests still pass under bun test (verified pre-commit). This is fix-fwd to my own substrate (#5421 envelope + #5418 handlers + #5425 CLI rebase) discovered when CI ran on the chained-off #5426 PR. Tsc errors didn't surface on the source PRs because they used the same narrowing pattern that bun tolerates but tsc rejects under strict mode. Composes with: B-0857.1 (this PR's primary scope; sub-row audit); B-0852.2a/2b/10 (the substrate this fixes); PR #5421/#5425/#5418 (the originating PRs); `.claude/rules/blocked-green-ci-investigate-threads.md` (verify-then-fix discipline applied to CI failure investigation); `.claude/rules/refresh-before-decide.md` (raw CI output read before acting); `.claude/rules/holding-without-named-dependency-is-standing-by-failure.md` counter-with-escalation (CI failure IS named-dep + bounded work). Per .claude/rules/agent-worktree-hygiene-never-hold-main-...: isolated worktree at /private/tmp/zeta-b0857-1-audit-0817z; never touched operator's primary checkout. Per .claude/rules/non-coercion-invariant.md HC-8: substrate-honesty preserved — fix-fwd to my own substrate; correction is additive. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AceHack
added a commit
that referenced
this pull request
May 27, 2026
…invocation PRESENT (zeta-install.sh:1097) + corrects B-0857 row body authoring error (#5426) * docs(B-0857.1): audit verifies PR #5389 Step 6.95a invokes tools/setup/install.sh — integration PRESENT at zeta-install.sh:1097-1099; B-0857 row body corrected Sub-row audit per B-0857 implementation order step 1 ("audit current state"). Result: PR #5389's commit-message claim VERIFIED PRESENT on origin/main 0b61405; no drift; no repair needed. **The integration**: zeta-install.sh:1090-1100 Step 6.95a-bootstrap invokes \`tools/setup/install.sh\` via: sudo HOME="$ZETA_HOME" -u "#$ZETA_UID" \\ bash -c "cd $ZETA_HOME/Zeta && tools/setup/install.sh" Dispatch chain: install.sh → linux.sh (detects /etc/NIXOS) → common/mise.sh (reads .mise.toml, installs pinned runtimes). This extends GOVERNANCE §24 three-way-parity (dev + CI + devcontainer) to NixOS cluster nodes via the same canonical entry. **B-0857 row body correction**: The B-0857 row (#5423) body contained "grep of current zeta-install.sh finds NO actual invocation. Either drifted out or the integration is at a higher abstraction layer." This was an authoring error — the grep produces 9 matches; line 1097 is the load-bearing one. The authoring step skipped the verify-by-grep that this sub-row commits to. This is a substrate-drift catch caught at sub-row audit scope rather than at row-authoring scope. The B-0857.1 sub-row IS the corrective mechanism the parent B-0857 row called for; the audit found the row's own framing was the drift, not the integration substrate. Row body now reads: "Audit verified (B-0857.1, 2026-05-27): integration IS present at full-ai-cluster/usb-nixos-installer/zeta-install.sh:1097-1099 inside Step 6.95a-bootstrap; no drift; no repair needed." **Status**: closed at landing (no implementation work needed; substrate is correct). Composes with: B-0857 (parent — this corrects parent's body); PR #5389 (audited substrate); \`.claude/rules/grep-substrate-anchors-before-razor-as-metaphysical.md\` (sibling discipline: verify before asserting); \`.claude/rules/verify-existing-substrate-before-authoring.md\` (the discipline the B-0857 authoring step skipped; this audit catches the result); \`.claude/rules/blocked-green-ci-investigate-threads.md\` verify-before-fix discipline; \`.claude/rules/refresh-before-decide.md\` (underlying invariant at substrate-authoring scope). Per .claude/rules/non-coercion-invariant.md HC-8: substrate-honesty preserved; correction is additive (per retraction-native discipline) not erasing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(B-0857.1 CI): regen BACKLOG.md + MD032 blank-line + tsc strict-mode narrowing fix-fwd for B-0852.2a/2b/10 discriminated unions (3 CI failures resolved) Three CI failures on PR #5426 resolved in single fix-pass: 1. **check docs/BACKLOG.md generated-index drift**: regen via `BACKLOG_WRITE_FORCE=1 bun tools/backlog/generate-index.ts` to include new B-0857.1 sub-row entry. 2. **lint (markdownlint) MD032/blanks-around-lists** at line 60 of B-0857.1 sub-row: blank line inserted before ordered list per markdownlint canonical rule. 3. **lint (tsc tools)** type errors in B-0852.2a/2b/10 substrate from just-merged PRs #5421/#5418/#5425: discriminated-union narrowing pattern `if (!(x instanceof Buffer))` doesn't narrow under tsc strict mode (bun test passed because bun's TS is more lenient). Substrate-honest fix: switch all narrowing to the discriminant-property check `if ("error" in x)` which TS strict mode narrows correctly. Files changed: - `tools/installer/zeta-creds-envelope.ts` (4 occurrences in parseEnvelope: salt/iv/tag/ciphertext) - `tools/installer/zeta-cred-handlers.ts` (1 occurrence in resolveBakeCred) - `tools/installer/zeta-cred-handlers.test.ts` (replaceAll: 4+ occurrences in resolveValueSource test variants) Fix is functionally equivalent — both `instanceof Buffer` and `"error" in x` correctly distinguish the union at runtime; the difference is only in tsc's ability to narrow. All 36 tests still pass under bun test (verified pre-commit). This is fix-fwd to my own substrate (#5421 envelope + #5418 handlers + #5425 CLI rebase) discovered when CI ran on the chained-off #5426 PR. Tsc errors didn't surface on the source PRs because they used the same narrowing pattern that bun tolerates but tsc rejects under strict mode. Composes with: B-0857.1 (this PR's primary scope; sub-row audit); B-0852.2a/2b/10 (the substrate this fixes); PR #5421/#5425/#5418 (the originating PRs); `.claude/rules/blocked-green-ci-investigate-threads.md` (verify-then-fix discipline applied to CI failure investigation); `.claude/rules/refresh-before-decide.md` (raw CI output read before acting); `.claude/rules/holding-without-named-dependency-is-standing-by-failure.md` counter-with-escalation (CI failure IS named-dep + bounded work). Per .claude/rules/agent-worktree-hygiene-never-hold-main-...: isolated worktree at /private/tmp/zeta-b0857-1-audit-0817z; never touched operator's primary checkout. Per .claude/rules/non-coercion-invariant.md HC-8: substrate-honesty preserved — fix-fwd to my own substrate; correction is additive. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Lior <lior@zeta.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack
added a commit
that referenced
this pull request
May 27, 2026
… — interactive bake-in + zflash CLI override (Aaron 2026-05-27 USB push) (#5449) * docs(B-0852.3): zeta-install.sh Step 6.77 cred-picker integration row — interactive bake-in at setup + zflash CLI token-override per declared cred (Aaron 2026-05-27 USB push) Filed per operator 2026-05-27 USB push: "lets keep pushing forward and get cred persistance any anthing else we can make it in before i test again" Captures the three-message operator framing 2026-05-27: 1. "if we do token we should do at zflash time and human interactive at setup time" 2. "zflash script and/or skill can make sure it asks what declared creds you want to bake in vs go through device flow" 3. "instead of loop in zflash you just allow command line override of any declared cred as token... easier for the ai to call" Two integration points: - Step 6.77 (setup-time interactive picker; consumes B-0852.2b persist CLI) - zflash CLI flag (--bake-cred per cred; non-interactive AI-callable) Composes with merged substrate: - B-0852.1 crypto (PR #5413) - B-0852.5 manifest (PR #5414) - B-0852.10 handlers (PR #5418) - B-0852.2a envelope (PR #5421) - B-0852.2b CLIs (PR #5425) - B-0857.1 audit confirms Step 6.95a invocation present (PR #5426) Sub-rows planned: 3a (picker in zeta-install.sh), 3b (zflash CLI flags), 3c (passphrase policy), 3d (empirical USB test). P1 priority because this row directly blocks operator's USB cred-persistence empirical validation. All upstream sub-rows merged; this is the operator- facing integration that unblocks the empirical test. Filing this row IS counter-reset condition #3 ("file a candidate B-NNNN") per .claude/rules/holding-without-named-dependency-is-standing-by-failure.md — per Kira's review the row should have been filed at brief-ack #6 not tick 100. Substrate-honest: filing now closes the cascade naturally. Per .claude/rules/non-coercion-invariant.md HC-8: operator authority over cred-persistence flow; picker preserves choice (bake / defer / skip). Per .claude/rules/agent-worktree-hygiene-never-hold-main-...: isolated worktree at /private/tmp/zeta-b0852-3-row-1200z; never touched operator's primary checkout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(BACKLOG.md): regen for B-0852.3 row --------- Co-authored-by: Lior <lior@zeta.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack
added a commit
that referenced
this pull request
May 27, 2026
…integration (16 tests; Aaron 2026-05-27 USB push) (#5450) * feat(B-0852.3a): interactive cred-picker + zeta-install.sh Step 6.94 integration (16 unit tests; consumes B-0852.2b persist CLI) Implements operator's 2026-05-27 USB-push direction: ship cred-persistence end-to-end before next USB test cycle. **Picker (tools/installer/zeta-creds-picker.ts)**: Interactive CLI that reads DEFAULT_MANIFEST (B-0852.5) + per-cred handler contracts (B-0852.10), then prompts operator per cred: [b]ake-in NOW / [d]efer to device-flow at runtime / [s]kip For bake-in choices, sub-prompts for value-source matching handler's supportedSources: - [l]iteral (typed value; not logged) - [f]ile (@path syntax to B-0852.10 handler) - [e]nv (env:VAR syntax) After picker loop completes, invokes zeta-creds-persist (B-0852.2b CLI) with collected --bake-cred args + passphrase + usb-uuid + output path + optional persona. Auto-skips persona-scoped creds when --persona not supplied (operator choosing global-only install scope). --dry-run mode prints the persist invocation without executing (useful for test/debug). Exit codes: 0 success / 2 arg-parse / 3 abort / 4 persist-failure. **Tests (tools/installer/zeta-creds-picker.test.ts)**: 16 unit tests passing: - parseArgs validation (6 tests covering well-formed + missing-required + unknown-flag) - runPicker against mock readline (10 tests covering defer-all / bake-literal / bake-file / bake-env / empty-value-skip / persona-scoped auto-skip / persona-supplied bake / empty-choice-as-defer / unrecognized-choice-as-defer / explicit-skip) Pure picker logic tested without spawning persist subprocess. **zeta-install.sh Step 6.94 integration**: Adds conditional Step 6.94 BEFORE existing Step 6.95 cred-persistence block. Gated on three preconditions: - ZETA_CREDS_PICKER=1 env (opt-in; default skip preserves backward compat with automated/CI installs) - $ZETA_HOME/Zeta exists (pre-cloned repo from Step 6.95a-bootstrap) - /etc/zeta/usb-uuid exists (iter-4.2 ESP write surface) - ZETA_CREDS_PASSPHRASE env set When all preconditions met: invokes picker as zeta user via sudo, forwarding passphrase through env. Writes blob to /esp/zeta-creds.enc which B-0852.4 NixOS module will consume at boot (future row). Non-fatal failure: warns + continues (per .claude/rules/non-coercion-invariant.md HC-8 — required-cred write failure surfaces but doesn't halt install). **What this unblocks for operator's USB test cycle**: - Operator can re-flash USB → boot → run installer → set ZETA_CREDS_PASSPHRASE + ZETA_CREDS_PICKER=1 → bake desired creds → reboot - /esp/zeta-creds.enc is written; persistence verified empirically on USB - B-0852.4 NixOS module (consume at boot) lands in next sub-row Composes: - B-0852.1 crypto (PR #5413) - B-0852.2a envelope (PR #5421) - B-0852.2b persist+restore CLIs (PR #5425) - B-0852.3 row (PR #5449) - B-0852.5 manifest (PR #5414) - B-0852.10 handlers (PR #5418) - B-0857.1 audit confirms Step 6.95a invocation (PR #5426) Per .claude/rules/non-coercion-invariant.md HC-8: operator authority over own creds; passphrase NEVER logged; literal values redacted at display; declined creds defer (not coerced into bake-in default). Per .claude/rules/agent-worktree-hygiene-never-hold-main-...: isolated worktree at /private/tmp/zeta-b0852-3a-picker-1215z; never touched operator's primary checkout. Per .claude/rules/holding-without-named-dependency-is-standing-by-failure.md: this commit IS the externalized heartbeat per AgencySignature substrate the operator pointed at 2026-05-27 — git log + audit-agencysignature-main-tip.ts gives the counter mechanism the brief-ack rule's N=6 forcing function needs to fire reliably. Agency-Signature-Version: 1 Agent: Otto Agent-Runtime: Claude Code (auto mode) Agent-Model: claude-opus-4-7 Credential-Identity: aaron-otto-vscode Credential-Mode: operator-authorized Human-Review: pre-merge-pending Human-Review-Evidence: operator-direction-2026-05-27-usb-push-keep-pushing-forward Action-Mode: substrate-implementation Task: B-0852.3a Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(B-0852.3a CI): 7 Copilot+CodeQL findings — P0 passphrase leak via bash -c interpolation; P0 CodeQL clear-text-logging; sudo arg ordering; eslint-disable; valueSpec→sourceChoice source label; Step 6.94→6.95-picker restructure (Aaron 2026-05-27 USB push) 7 unresolved review threads on #5450 resolved: **P0 — Passphrase leak via bash -c arg-string interpolation (Copilot @1043)** Was: `bash -c "...ZETA_CREDS_PASSPHRASE='$ZETA_CREDS_PASSPHRASE' bun..."` The outer double-quote expanded $ZETA_CREDS_PASSPHRASE → literal passphrase appeared in process arglist visible to `ps`. Fix: use `sudo --preserve-env=ZETA_CREDS_PASSPHRASE -u USER HOME=... bash -c CMD` where CMD references `--passphrase-env ZETA_CREDS_PASSPHRASE` (var-NAME only). Passphrase never appears in arglist. **P0 — CodeQL clear-text-logging in DRY RUN output (line 198)** Was: `console.log(\` bun \${persistArgs.join(" ")}\`)` — persistArgs contains `--passphrase-env <NAME>` from operator input; the NAME is CodeQL-tainted. Fix: build displayArgs that maps position-after-`--passphrase-env` to `<REDACTED>` literal. Same discipline as zeta-creds-persist/restore P0 fix on PR #5422. **P1 — sudo arg ordering (Copilot @1038)** Was: `sudo HOME=... -u ...` — HOME= before -u is invalid per sudo manpage (options must precede arguments). Fix: `sudo --preserve-env=... -u ... HOME=...` — options first, env-var assignment between -u and command per sudo manpage. **P1 — valueSpec in source-label ternary (Copilot @202)** Was: `valueSpec.startsWith("@") ? "@file" : valueSpec.startsWith("env:") ? "env" : "literal"` The output is just labels but Copilot flagged the value passing through the ternary as a leak risk. Fix: compute sourceLabel from operator's sourceChoice letter (l/f/e) NOT from valueSpec. valueSpec never reaches the log path. **P2 — eslint-disable for spawnSync (Copilot @201)** Added `// eslint-disable-next-line sonarjs/no-os-command-from-path` before the spawnSync("bun", ...) call per repo convention for TS tools spawning PATH-resolved bins. **P2 — Step 6.94 vs 6.95a-bootstrap ordering contradiction (Copilot @1052)** Was: Step 6.94 claimed to read manifest from pre-cloned repo, but the clone happened in 6.95a-bootstrap BELOW. Picker would fail at Step 6.94 (no repo, no bun). Fix: restructured — Step 6.94 is now a header stub reserving the number; ACTUAL picker invocation moved to NEW Step 6.95-picker INSIDE the 6.95 block, AFTER 6.95a-bootstrap (repo + bun + mise present) + BEFORE 6.95b device-flow logins (picker decides per-cred bake-vs-defer + device-flow handles the deferred subset). **P2 — Header references Step 6.77 (Copilot @18)** Was: picker file header said "Step 6.77" (speculative number from B-0852.3 row body). Fix: updated header to "Step 6.95-picker" matching the actual integration step. **Verification**: - `bash -n full-ai-cluster/usb-nixos-installer/zeta-install.sh` → OK - All 16 unit tests still pass Per .claude/rules/blocked-green-ci-investigate-threads.md: verify-then-fix discipline applied to each Copilot finding; one false-positive narrowed (P1 valueSpec was technically OK but tightened anyway for clarity). Per .claude/rules/non-coercion-invariant.md HC-8: passphrase NEVER logged + NEVER in arglist + redacted in DRY RUN; operator authority preserved. Per .claude/rules/methodology-hard-limits.md: clinical/security floor operative; P0 passphrase-leak fix lifts above the floor by removing the leak path entirely (sudo --preserve-env keeps passphrase in env, not arglist). Agency-Signature-Version: 1 Agent: Otto Agent-Runtime: Claude Code (auto mode) Agent-Model: claude-opus-4-7 Credential-Identity: aaron-otto-vscode Credential-Mode: operator-authorized Human-Review: pre-merge-pending Human-Review-Evidence: copilot-review-7-findings-on-pr-5450-resolved Action-Mode: substrate-fix-fwd-security Task: B-0852.3a Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(B-0852.3a CodeQL P0 re-fire): build DRY RUN display from known-safe primitives — never reference parsed.passphraseEnv in logged string (CodeQL doesn't see runtime ternary breaking taint) Prior fix used map-based redaction over persistArgs (which contains parsed.passphraseEnv tainted via env-var-name access). CodeQL data-flow analysis doesn't recognize runtime ternary as a sanitizer — the taint still flows from the input to the log call statically, so the warning re-fired. Stronger pattern (matches the sibling persist/restore CLIs): construct the display string from primitives only. NEVER reference parsed.passphraseEnv OR parsed.passphraseFile in the logged string; print literal placeholders like "<REDACTED>" / "<set>" instead. displayCmd = " bun tools/installer/zeta-creds-persist.ts --usb-uuid <set> --output <set>" + " --passphrase-file <REDACTED>" (if --passphrase-file set) + " --passphrase-env <REDACTED>" (if --passphrase-env set) + " --persona <set>" (if --persona set) + " --bake-cred <id>=<REDACTED>" (per bake; id is OK; value redacted) All 16 tests still pass. Per .claude/rules/blocked-green-ci-investigate-threads.md verify-then-fix discipline: read line 210 directly, confirm the redaction was runtime- only (CodeQL doesn't sanitize), rewrite to static-safety pattern. Per .claude/rules/non-coercion-invariant.md HC-8: passphrase NEVER in log path; operator authority over what gets logged preserved by total redaction; <set>/<REDACTED> placeholders confirm presence without revealing content. Agency-Signature-Version: 1 Agent: Otto Agent-Runtime: Claude Code (auto mode) Agent-Model: claude-opus-4-7 Credential-Identity: aaron-otto-vscode Credential-Mode: operator-authorized Human-Review: pre-merge-pending Human-Review-Evidence: codeql-re-fire-on-line-210-after-prior-redaction-insufficient Action-Mode: substrate-fix-fwd-security Task: B-0852.3a Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(B-0852.3a CI Copilot): activate mise + BUN_INSTALL in picker bash -c — match sibling 6.95a install steps (Copilot @1164) Copilot finding: the picker invocation at Step 6.95-picker bash -c didn't activate mise the way sibling 6.95a-claude/gemini/codex steps do (lines 1119-1121 / 1129-1131 / 1139-1141 all `eval "$(mise activate bash 2>/dev/null || true)"; bun ...` inside the bash -c, with `BUN_INSTALL="$ZETA_HOME/.bun"` set). Without mise activate, `bun` is not on the subshell PATH because mise installs bun via shims; activate sets the PATH entry. Picker would fail with "bun: command not found" at Step 6.95-picker time. Fix: mirror the sibling pattern exactly: - Add `BUN_INSTALL="$ZETA_HOME/.bun"` to sudo env prefix - Add `set -o pipefail; eval "$(mise activate bash 2>/dev/null || true)";` prefix to bash -c - Preserve --preserve-env=ZETA_CREDS_PASSPHRASE for passphrase forward Verification: `bash -n full-ai-cluster/usb-nixos-installer/zeta-install.sh` returns syntax OK. Per .claude/rules/blocked-green-ci-investigate-threads.md verify-then-fix: read the sibling step patterns at lines 1119-1141, confirm they all follow same eval-mise-then-bun convention, apply the same to picker. Agency-Signature-Version: 1 Agent: Otto Agent-Runtime: Claude Code (auto mode) Agent-Model: claude-opus-4-7 Credential-Identity: aaron-otto-vscode Credential-Mode: operator-authorized Human-Review: pre-merge-pending Human-Review-Evidence: copilot-thread-PRRT_kwDOSF9kNM6FHfK8-on-pr-5450 Action-Mode: substrate-fix-fwd-correctness Task: B-0852.3a Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Lior <lior@zeta.dev> Co-authored-by: Claude Opus 4.7 <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
B-0852 sub-row .10 — pure TS module composing already-merged B-0852.5 manifest schema + B-0852.1 crypto module, toward B-0852.9 zflash
--bake-credCLI override per Aaron 2026-05-27 CLI-override design.Three pure layers
parseBakeCredArg(arg)— splits<id>=<source>preserving=in valueresolveValueSource(source)— handlesliteral/@file/env:VARhandler.validateValue(buf)— per cred-type validation (PAT / JSON / SSH pubkey)resolveBakeCred()full pipeline composes the three + gates unsupported source types perhandler.supportedSources.Per-type handlers
gh-cliclaudegeminicodexssh-operator-pubkeyssh-host-keysWhy per-type validation
JSON creds (claude/gemini/codex) explicitly rejects
env:source — those creds are JSON files, not short tokens. PATs (gh-cli) support all three sources because the value is short string-shaped.Test output
Covers: arg parsing edge cases (= in value, missing =, empty id/source) + value-source resolution (literal / env: / @file with home-dir ~ expansion / missing-file rejection / empty-env rejection) + per-handler validation (each cred type's happy + rejection paths) + full-pipeline integration tests.
What this is NOT
Composes with
--bake-credCLI consumes this module🤖 Generated with Claude Code