instar 1.3.379 → 1.3.381

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ # Side-effects review — Vault-backed GH_TOKEN at spawn (Phase-3 increment P3b, option C)
2
+
3
+ ## What this change does
4
+ Per-agent GitHub credential isolation for spawned sessions (CMT-1125 gap 1,
5
+ operator-selected design option C). At spawn time, `SessionManager` resolves
6
+ the agent's GitHub token from its encrypted per-agent SecretStore
7
+ (`github_token`, then `github.token`) and injects `GH_TOKEN=<token>` into the
8
+ tmux session environment. The `gh` CLI prefers `GH_TOKEN` over the
9
+ machine-global `~/.config/gh/hosts.yml` seat, so a session's GitHub actions
10
+ authenticate as THIS agent — the shared-machine cross-principal exposure
11
+ behind the 2026-06-05 identity-bleed incident.
12
+
13
+ Three pieces:
14
+ - `src/core/ghToken.ts` — `resolveGhTokenFromVault(stateDir)`: pure fs/crypto
15
+ vault read; trims; returns null on absent vault / absent key / non-string /
16
+ whitespace / any read error. Never throws. No subprocess (the P3a
17
+ GitSync mock-sequence lesson: spawn-path helpers must not consume scripted
18
+ child_process mock values).
19
+ - `SessionManager.ghTokenEnvFlags()` — private helper used by THREE spawn
20
+ sites (headless, rerouted-interactive, warm interactive). Returns
21
+ `['-e', 'GH_TOKEN=…']` or `[]`.
22
+ - `vaultStateDir` — same derivation as the pending-inject ledger
23
+ (StateManager.baseDir, else `<projectDir>/.instar`).
24
+
25
+ ## Blast radius
26
+ - **Additive + dark-by-default-shaped.** No config flag, no migration, no new
27
+ route: with no vault token the resolver returns null and the spawn argv is
28
+ unchanged byte-for-byte (tested: env has no `GH_TOKEN=` entry at all).
29
+ - **Fail-soft end to end.** A corrupt vault logs one console.warn and the
30
+ spawn proceeds tokenless (tested with a deliberately garbaged vault file).
31
+ - **The hardened triage spawn is EXCLUDED on purpose.** That site scrubs
32
+ credentials (`ANTHROPIC_API_KEY=`, `DATABASE_URL=` …); handing it a GitHub
33
+ token would invert its security posture. Reviewed and deliberately skipped.
34
+ - **Token visibility (same posture as existing art).** The token rides the
35
+ tmux `new-session -e` argv — momentarily visible in the process list and
36
+ in tmux's session environment, exactly like the existing
37
+ `INSTAR_AUTH_TOKEN` and `ANTHROPIC_API_KEY`/`CLAUDE_CODE_OAUTH_TOKEN`
38
+ injections at the same sites. This increment deliberately matches that
39
+ established posture rather than inventing a second transport; tightening
40
+ all of them together is its own future increment if wanted.
41
+ - **Per-spawn vault read.** One decrypt per spawn (and on macOS possibly one
42
+ read-only keychain probe via the dual-key candidates). Spawns are seconds-
43
+ long, rare operations; no caching added (a fresh read also means a newly
44
+ dropped token is picked up by the very next spawn with no restart).
45
+
46
+ ## Why vault-only (no hosts.yml fallback parsing)
47
+ Option C's value is the agent using ITS OWN seat. Parsing the machine-global
48
+ hosts.yml and injecting THAT token would re-create the exposure this exists
49
+ to close — so when the vault has no token we inject nothing and let `gh`
50
+ behave exactly as today. Phase-2 candidates (server-side `gh` calls in
51
+ routes.ts/CiFailurePoller, git-credential-helper for raw https pushes) are
52
+ catalogued in the increment ladder, not smuggled in here.
53
+
54
+ ## Deflake that rode along (test-only)
55
+ Both reroute suites — `tests/unit/headless-spawn-reroute.test.ts` and
56
+ `tests/e2e/june15-headless-spawn-reroute.test.ts` — read the REAL host memory
57
+ pressure through the reroute gate: on a loaded dev machine they failed on a
58
+ PRISTINE tree (verified by stashing this change and re-running; unit 8 fails,
59
+ e2e 3 fails). Both now stub `currentMemoryPressure` to 'normal' — they assert
60
+ reroute logic, not host health. No production code touched by the deflake; no
61
+ test in either file asserts the pressure refusal.
62
+
63
+ ## Test evidence
64
+ - `tests/unit/gh-token-vault.test.ts` (10): both key paths, precedence,
65
+ trimming, absent vault, wrong-key, non-string, whitespace, corrupt-vault
66
+ never-throws, production-path dual-key file-candidate read.
67
+ - `tests/unit/session-spawn-gh-token.test.ts` (5): REAL StateManager + REAL
68
+ vault + REAL resolver, argv-capturing tmux mock — token present in headless
69
+ env + INSTAR canaries intact; no vault → no flag; corrupt vault → spawn
70
+ succeeds, no flag; whitespace token → no flag; rerouted-interactive lane
71
+ carries the token too.
72
+ - Canaries green: headless-spawn-reroute (23), session-manager behavioral /
73
+ terminate / injection, SecretStore, GitSync, no-silent-fallbacks — 119/119.
74
+ - `tsc --noEmit` clean; `docs-coverage --check` floors hold (no new routes,
75
+ no new PascalCase core class file — `ghToken.ts` is a lowercase module by
76
+ design).