feat(itmux): .env credential loading for itmux run (fixes the stale-token 401) - #254
feat(itmux): .env credential loading for itmux run (fixes the stale-token 401)#254NeuralEmpowerment wants to merge 10 commits into
Conversation
…view) Close the brief-world-readable window on host-side secret material: every file/dir holding secrets is now created with its restrictive mode at creation, not chmod'd afterward. Adds create_private_dir (mkdir 0700) and write_private_file (open O_CREAT|O_EXCL 0600). - Fix 1: secret env file - create_new 0600 instead of fs::write + chmod. - Fix 2: workspace throwaway dir - 0700 at creation. - Fix 3: codex auth.json temp file - create_new 0600 (same class, no carve-out); fresh_cred_dir now mkdir 0700 atomically too. - Fix 4: cfg(unix) mode-assertion tests driving the real creation helpers (dir 0700, file 0600, refuses to clobber). - Hygiene: unlink the host secret env file immediately after transfer; teardown remove_dir_all remains the fallback. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
Dual-review gate: PASS
171 tests, clippy -D warnings clean, fmt clean. Permanently fixes the stale-token 401. |
7fde8f0 to
a060abb
Compare
|
Retargeted to #250 and rebased. Reconciled the earlier env-file client with #250’s reviewed streaming/process-group client; credential values remain out of argv and are materialized only to a 0600 temporary env file. Fixed one post-rebase AuthContext fixture. Local Rust fmt/test/clippy plus Python Ruff and 22 focused tests pass. Fresh CI pending. |
…view) Close the brief-world-readable window on host-side secret material: every file/dir holding secrets is now created with its restrictive mode at creation, not chmod'd afterward. Adds create_private_dir (mkdir 0700) and write_private_file (open O_CREAT|O_EXCL 0600). - Fix 1: secret env file - create_new 0600 instead of fs::write + chmod. - Fix 2: workspace throwaway dir - 0700 at creation. - Fix 3: codex auth.json temp file - create_new 0600 (same class, no carve-out); fresh_cred_dir now mkdir 0700 atomically too. - Fix 4: cfg(unix) mode-assertion tests driving the real creation helpers (dir 0700, file 0600, refuses to clobber). - Hygiene: unlink the host secret env file immediately after transfer; teardown remove_dir_all remains the fallback. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
4c0211f to
bd7cde2
Compare
d6b048f to
e790602
Compare
…view) Close the brief-world-readable window on host-side secret material: every file/dir holding secrets is now created with its restrictive mode at creation, not chmod'd afterward. Adds create_private_dir (mkdir 0700) and write_private_file (open O_CREAT|O_EXCL 0600). - Fix 1: secret env file - create_new 0600 instead of fs::write + chmod. - Fix 2: workspace throwaway dir - 0700 at creation. - Fix 3: codex auth.json temp file - create_new 0600 (same class, no carve-out); fresh_cred_dir now mkdir 0700 atomically too. - Fix 4: cfg(unix) mode-assertion tests driving the real creation helpers (dir 0700, file 0600, refuses to clobber). - Hygiene: unlink the host secret env file immediately after transfer; teardown remove_dir_all remains the fallback. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
|
Rebased onto refreshed #250 head (current dependency base) and force-with-lease pushed. Validation: Rust format + compile; (27 passed); F401 [*] F401 [*] F401 [*] F401 [*] Found 4 errors. |
bd7cde2 to
603640d
Compare
R2/R7/R8: add a defaulted, serde-stable generic secret_env map to AgentRunCredentials for harness-neutral env-var secrets, keeping the existing claude/codex fields for compat. Regenerate the schemars schema and add round-trip + default-empty tests. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
R4 narrow .env parser (KEY=VALUE, # comments, quotes; no expansion, multiline, or export; file:line errors) + load_credentials with precedence (--env-file > process env). R2 per-harness routing (resolve_agent_secrets) with confirmed preferred/fallback + R8 compat precedence. R3 fail-fast message. Renders the 0600 env-file body. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
R1/R6: stage a per-agent 0600 env file over the existing base64-over-stdin transfer, then launch the harness pane with 'set -a; . <file>; set +a; exec <cmd>' so the CLI inherits CLAUDE_CODE_OAUTH_TOKEN / API keys from the child env. No secret ever reaches argv (no docker exec -e, no tmux set-environment). StartOptions/Workspace carry per-agent secret_env and the staged file path; an agent is enabled by secret_env alone. R1/R8: claude OAuth mode stages .claude.json only (AuthContext.claude_omit_ credentials), never a synthesized .credentials.json. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
R3: replace the empty 'credentials: Default::default()' with loader output and route the recipe agent's secrets in the executor. Default is fail-fast (actionable error naming the missing var); --allow-host-auth- fallback re-enables the legacy $HOME/.<agent> path with a PATH-only warning. Kills the silent stale-file 401 fallback. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
Seed sentinel secrets and assert they never appear in any docker exec argv, tmux label, fail-fast/error string, or serialized AgentRunEvent - only in the in-memory spec and the base64 stdin payload. Includes a base64 decode to prove the stdin payload is the sole argv-free carrier. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
…rough Typed run_client forwarding --env-file to itmux run (or materializing an in-memory credential mapping to a private 0600 temp file whose PATH - never contents - is forwarded then removed). Tests assert forwarding and that a secret value never reaches argv or logs. mypy --strict clean. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
Document the recognized keys, precedence, CLAUDE_CODE_OAUTH_TOKEN preference, fail-fast + opt-in host fallback, the sourced-0600-env-file no-argv-leak mechanism, the stale-file 401 fix (Keychain-scrape workaround retired), and 'credentials live in the run/workspace layer, never in the recipe'. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
…view) Close the brief-world-readable window on host-side secret material: every file/dir holding secrets is now created with its restrictive mode at creation, not chmod'd afterward. Adds create_private_dir (mkdir 0700) and write_private_file (open O_CREAT|O_EXCL 0600). - Fix 1: secret env file - create_new 0600 instead of fs::write + chmod. - Fix 2: workspace throwaway dir - 0700 at creation. - Fix 3: codex auth.json temp file - create_new 0600 (same class, no carve-out); fresh_cred_dir now mkdir 0700 atomically too. - Fix 4: cfg(unix) mode-assertion tests driving the real creation helpers (dir 0700, file 0600, refuses to clobber). - Hygiene: unlink the host secret env file immediately after transfer; teardown remove_dir_all remains the fallback. Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
603640d to
8853659
Compare
|
Fixed the required Rust clippy gate: handle_run now has the targeted too_many_arguments allowance used for CLI boundary functions. Verified locally with cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test. |
Populates
AgentRunSpec.credentialsfrom a.env/process env soitmux runinjects subscription (OAuth / auth.json) or API-key credentials via the secure DooD path - instead ofcredentials: Default::default()silently falling back to the stale host$HOME/.claudefile (the recurring 401 root cause, confirmed live in EXP-08: the on-disk creds expire in hours; the live token is in the macOS Keychain). Codex plan-reviewed (8 revisions R1-R8 folded in). Stacked on #247.Secrets contract (consumer-supplied; NEVER in the recipe)
itmux run --env-file <path>(else process env). Precedence: env-file > process env > (opt-in)--allow-host-auth-fallback.Injection - no secret ever touches argv (R1/R5)
CLAUDE_CODE_OAUTH_TOKEN, NOT a synthesized.credentials.json.0600env-file staged over the existing base64-over-stdindocker exectransfer; pane launched viaset -a; . '/home/agent/.itmux-secret-env-<agent>'; set +a; exec <harness>(only the file PATH + harness name on the command line).docker exec -e VAR=value, notmux set-environment(both leak to argv).claude_omit_credentials: in OAuth mode, stage ONLY the.claude.jsontrust marker, never a.credentials.json- so a stale host file can't re-cause the 401.Hardening
tests/secret_redaction.rs, load-bearing): 3 sentinels asserted absent from every docker argv, the launch wrapper, all error strings, and serializedAgentRunEvents; positively confirms the two sanctioned carriers (rendered env-file body + base64 stdin, decoded in-test).--allow-host-auth-fallbackre-enables the legacy path and warns with the source PATH only (never contents).secret_env: BTreeMap(defaulted, serde-stable, allowlisted names); legacyclaude.oauth_token/codex.auth_jsonkept (compat, R8); schema regenerated.--env-filepassthrough.Gates: cargo test 169 pass, clippy -D warnings clean, fmt clean, R8 neutrality guard holds; python pytest 6 / ruff / mypy --strict clean. Fixes the stale-token 401 permanently; retires the Keychain-scrape workaround. Tracks okrs-51p.7.