Skip to content

[#145] Env sanitizer: strip ANTHROPIC_CUSTOM_HEADERS + detect/surface HTTP(S)_PROXY#154

Merged
realproject7 merged 1 commit into
mainfrom
task/145-env-sanitizer
Jul 8, 2026
Merged

[#145] Env sanitizer: strip ANTHROPIC_CUSTOM_HEADERS + detect/surface HTTP(S)_PROXY#154
realproject7 merged 1 commit into
mainfrom
task/145-env-sanitizer

Conversation

@realproject7

Copy link
Copy Markdown
Owner

Fixes #145

EPIC Alignment

Parent EPIC: #134 — LiveCap v1.3 latency, reliability & STT-core performance (Fable audit). This ticket's role: close the env-sanitizer security gap so Anthropic custom-header credentials can't leak and proxy routing is never silent — without breaking the "your audio only goes to the model you chose" promise. Depends on: none. Enables: #148 (archive hardening — not touched here). Out of scope (per ticket): #148 archive hardening, #146 release-build debug env gating — not implemented.

Ran the EPIC Alignment Check against #134 and the merged Cluster A siblings (135/137/139/142/136): this batch is disjoint. The one contract to respect — sanitizeChildEnv stays the child-env boundary with ANTHROPIC_BASE_URL as the explicit intentional-custom-endpoint exception — is preserved; #142's second CLI engine is honored (the sanitizer runs per spawn, and the proxy notice is emitted once per session, not per engine).

Self-Verification

Adversarial diff re-read + kill-list scan (clean): no console.*; the proxy notice is content-free and carries only a safe host[:port] label — proxyHost() strips any embedded user:pass@ credentials and path, and an unparseable value degrades to (set) rather than echoing the raw string; no full proxy/secret value is ever logged or returned. No new runtime dependencies (URL is a Node global). No stub/TODO. Never logs caption content.

Levers

  1. Strip ANTHROPIC_CUSTOM_HEADERS — added to the credential regex so it strips alongside ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN (+ the existing Bedrock/Vertex set) when no ANTHROPIC_BASE_URL, keeping the intentional-custom-endpoint exception. Custom headers can smuggle Authorization/x-api-key → silent per-token API billing (the [follow-up] sanitizeChildEnv misses CLAUDE_CODE_USE_BEDROCK/VERTEX → silent per-token billing #25 class).
  2. Detect + surface proxies — new detectProxy() finds an active HTTPS_PROXY/ALL_PROXY/HTTP_PROXY (+ lowercase), most-relevant-first (the HTTPS var that carries the transcript wins), and returns a safe host label only. Proxies are detected, never stripped (a user may legitimately need one), but session.ts emits one content-free notice per session so the routing to Anthropic is a conscious choice.

Acceptance criteria (1:1)

Evidence @ latest commit

  • pnpm --filter @livecap/engine test261 passed (env.test.ts 22, +12: 3 custom-headers + 9 detectProxy).
  • pnpm test:app134 passed. pnpm --filter @livecap/archive test76 passed.
  • pnpm typecheck (app+host) ✓, @livecap/engine typecheck ✓, pnpm lint ✓, pnpm no-stub ✓, ./scripts/color-guard.sh ✓.

Design Fidelity

N/A — not a UI PR (engine sanitizer + host status emit; no visual surface).

Deviations

  • Scope-1 "strip unconditionally" vs. the Review-pass amendment. Scope item 1 says strip ANTHROPIC_CUSTOM_HEADERS "unconditionally," but the later (2026-07-06) Review-pass amendment lists it among the vars stripped "when no ANTHROPIC_BASE_URL" and directs "keep the ANTHROPIC_BASE_URL exception." I implemented per the amendment (exception-gated): with a custom base URL the user's endpoint/headers are intentional (the existing credential model), and the ticket's own threat framing — "with no base-URL set the existing exception never engages" — confirms the danger is exactly the no-base-URL case, which is closed. So custom headers strip when no base URL, and are kept when one is set (tested both ways).
  • Proxy notice on the CLI path only. The notice lives in the if (cli) branch because only the CLI child egresses the transcript to Anthropic (through any inherited proxy); the on-device local tier never routes externally, so emitting there would be a false alarm. Detection logic itself (detectProxy) is engine-level and fully unit-tested regardless of path.
  • Kept detectProxy as a sibling detector rather than changing sanitizeChildEnv's signature (the ticket offered either) — smaller blast radius, and detection is orthogonal to stripping.

… proxy

- Strip ANTHROPIC_CUSTOM_HEADERS with the credential set (can smuggle
  Authorization/x-api-key -> silent per-token billing, the #25 class),
  under the same intentional-custom-endpoint (ANTHROPIC_BASE_URL) exception.
- Add detectProxy(): find an active HTTP(S)/ALL_PROXY (+ lowercase), return
  a SAFE host[:port] label only (never the full value, which may embed
  credentials). Proxies are detected, never stripped.
- session.ts emits ONE content-free proxy notice per session on the CLI
  path (the transcript egresses only there; the on-device local tier never
  routes externally). Emitted once, not per engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Epic Alignment: PASS

#145 closes the #134 security/privacy env-sanitizer gap while preserving the sanitizeChildEnv child-process boundary and the ANTHROPIC_BASE_URL custom-endpoint exception from the ticket's EPIC Context.

Checked (evidence)

  • Structural gate: gh pr view 154 at d10217385155cb4750086ab2d61f436b4781fa67 → filled ## EPIC Alignment, ## Self-Verification, and non-UI ## Design Fidelity N/A.
  • Context: gh issue view 145 → contracts require stripping named auth-bearing Anthropic vars when no base URL, detecting proxy env without logging full values, and one content-free host-session notice; gh api repos/realproject7/livecap/issues/134 checked security/privacy goal and #145 routing.
  • Sanitizer: packages/engine/src/env.ts:8, :34-37 strip ANTHROPIC_CUSTOM_HEADERS with existing credentials under the base-URL exception; packages/engine/test/env.test.ts:89-110 covers default strip, case-insensitive strip, and base-URL preservation.
  • Proxy detection: packages/engine/src/env.ts:45-65 detects HTTPS/ALL/HTTP proxy vars plus lowercase; :69-75 reduces to host[:port] or (set), not the raw value; tests at packages/engine/test/env.test.ts:113-158 cover precedence, lowercase, creds, schemeless, empty, and unparseable values.
  • Host notice: src/host/session.ts:231-233 makes start once-per-session; src/host/session.ts:315-325 emits the content-free proxy notice in the CLI path outside per-engine wiring, so #142's two CLI engines do not duplicate it.
  • Riskiest part of this diff: the notice is not directly host-e2e tested; acceptable here because existing host tests document no headless start harness, and the pure detector plus guarded session-level call site make the once-per-session behavior directly inspectable.
  • Kill-list: scanned all items — clean; no mock/stub runtime path, TODO/FIXME/HACK, console/debugger, new deps, caption logging, or scope creep beyond env.ts, export, tests, and session.ts.
  • CI: gh pr checks 154 → app-macos, packages-linux, no-stub-gate, color-guard all pass.

Findings

None.

Decision

Approve. The implementation matches #145's amendment and #134's privacy contract: credentials that can redirect billing are stripped at the child-env boundary, proxy routing is surfaced without leaking values, and the two-lane CLI setup only emits one session-level notice.

@realproject7

Copy link
Copy Markdown
Owner Author

RE2 — APPROVE (PR #154, Fixes #145 — env sanitizer: strip ANTHROPIC_CUSTOM_HEADERS + detect/surface HTTP(S)/ALL proxy, N-1/H-2) @ d10217385155cb4750086ab2d61f436b4781fa67.

Reviewed vs main c42c5a7; scope confined to packages/engine/src/{env,index}.ts + packages/engine/test/env.test.ts + src/host/session.ts. Non-UI (Design Fidelity N/A, stated). PR body carries filled ## EPIC Alignment + ## Self-Verification + ## Deviations.

Checked (evidence)

Layer 1 — EPIC #134 alignment (matches @Head's Batch 26 directive exactly):

  • Credential strip broadenedANTHROPIC_CREDENTIAL = /^anthropic_(api_key|auth_token|custom_headers)$/i (env.ts:8), gated by the existing if (!env.ANTHROPIC_BASE_URL) exception (env.ts:34). ANTHROPIC_CUSTOM_HEADERS can smuggle Authorization/x-api-key → silent per-token billing ([follow-up] sanitizeChildEnv misses CLAUDE_CODE_USE_BEDROCK/VERTEX → silent per-token billing #25 class); stripping it under the same intentional-custom-endpoint exception is exactly @Head's re-scope ("when no ANTHROPIC_BASE_URL … keep the exception"). The scope-1 "unconditionally" wording is resolved to the amendment and disclosed in ## Deviations — aligned, not a drift.
  • Proxy detected, never strippeddetectProxy() (env.ts:52) scans HTTPS_PROXY/ALL_PROXY/HTTP_PROXY (+ lowercase), returns a safe host[:port] label; session.ts:319-328 surfaces ONE content-free status notice. Proxies pass through to the child (not stripped) — matches "detect + surface, do not silently honor."
  • Once per session, not per engine (Translation latency: dedicated CLI session (unblock live from summary) + idle-fast dispatch + defer first summary (audit H2/H1/N3) #142) — the emit sits in start() (guarded by if (this.started) return, session.ts:236), inside the CLI branch, outside the per-engine wiring. Two CLI children → one notice. Local-only path never emits (on-device tier never egresses) — correct, not a false alarm.

Layer 2 — kill-list clean:

Correctness (unit-verified): precedence HTTPS > ALL > HTTP, upper+lowercase, empty/whitespace ignored, scheme-less host:port accepted, bare host (no port) handled, unparseable → (set). 12 new tests (env.test.ts, 22 total) cover custom-headers strip ×3 incl. base-URL exception, detectProxy ×9.

Ran live at d1021738: @livecap/engine261 passed; pnpm typecheck (app+host) green; pnpm test:app134 passed. CI 4/4 green (app-macos / packages-linux / no-stub-gate / color-guard); MERGEABLE/CLEAN.

Non-blocking nits (no change required):

  1. NO_PROXY isn't considered — if it exempts the Anthropic host the notice still fires (a conservative false-positive, acceptable for a security warning).
  2. Same-var upper-vs-lower precedence and whether the CLI child actually honors these env vars are both immaterial to a detection notice.

No findings. RE2 APPROVE.

(Attribution: formal GitHub APPROVE blocked — gh is the PR-author identity — so this verdict is a PR comment with the body-marker + ### Checked (evidence), operative per convention.)

@realproject7
realproject7 merged commit 7f54917 into main Jul 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[security] Env sanitizer: strip ANTHROPIC_CUSTOM_HEADERS + detect/surface HTTP(S)_PROXY (N-1/H-2)

2 participants