Skip to content

Closes #174#186

Merged
realproject7 merged 2 commits into
mainfrom
task/174-base-url-notice
Jul 14, 2026
Merged

Closes #174#186
realproject7 merged 2 commits into
mainfrom
task/174-base-url-notice

Conversation

@realproject7

@realproject7 realproject7 commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Closes #174

Surface an active ANTHROPIC_BASE_URL custom endpoint to the user with a content-free status notice — the same privacy discipline #145 added for proxies — so a base-URL reroute is no longer invisible.

EPIC Alignment

Part of EPIC #134 (v1.3 audit follow-up), Batch 43 (standing-order sequence … #175#174). Privacy sibling of #145 (env sanitizer, merged PR #154). Scoped to packages/engine/src/env.ts (+ barrel), src/host/session.ts, and the env unit test. Does not touch resample.rs/render.ts/parse.ts/pipeline.rs (reserved for #178/#179), the #145 sanitize/carve-out behavior, or dashboard/settings.

Self-Verification

The bug. #145 preserves ANTHROPIC_BASE_URL and its credentials as an intentional custom endpoint, and surfaces proxies via a content-free status (session.ts:322translation traffic is routing through a proxy: <host>) — but nothing surfaced a base-URL override. So a user whose shell profile sets ANTHROPIC_BASE_URL (LLM routers/gateways, a corporate relay) has every caption batch, summary, and coaching prompt silently sent to that arbitrary host with credentials attached, while the Settings privacy row still reads "Audio never leaves this Mac". The env read is unconditional in release.

The fix (mirror the #145 proxy notice).

  • Shared host-redactor (per the Batch 36 amendment). proxyHost was module-private, so it couldn't be reused. Renamed it to safeHost and documented it as the shared redactor for both detectors (env.ts), and added an exported detectCustomEndpoint(base) that returns safeHost(ANTHROPIC_BASE_URL) when set/non-blank, else null. detectProxy is unchanged (now just calls safeHost).
  • The notice (a tested seam, per RE1). The status string + its redaction are factored into a pure, exported customEndpointNotice(env) (session.ts) → translation traffic is routing to a custom Anthropic endpoint: <safe host> when ANTHROPIC_BASE_URL is set, else null. HostSession.start, beside the detectProxy block and on the CLI path only (the on-device local tier never egresses), just emits it: if (endpointNotice) emit({ type: "status", detail: endpointNotice }). Only the safe host[:port] is surfaced — never the URL path/query or embedded credentials.
  • process.env is the right source: the CLI engine is constructed with env: process.env (session.ts:278), exactly what detectProxy already inspects.

No #145 behavior change. sanitizeChildEnv (the credential/billing-redirect carve-out) and detectProxy are untouched — this only adds a read-only notice.

Security invariants. No new dependencies. No caption/audio content logged — the notice is content-free (only the redacted host). Simplest fix that solves the ticket; the proxyHostsafeHost rename directly serves the amendment's shared-redactor requirement (not a drive-by). Touched only env.ts (+ barrel), session.ts, and the two tests.

Tests / evidence. HostSession.start spawns real CLI/llama-server children and has no headless e2e harness (see session-accounting.test.ts: "the helper IS the fix"), so the notice's status string + redaction are factored into the pure customEndpointNotice seam and tested directly (the emit is a trivial if (x) emit(detail: x)).

test/custom-endpoint-notice.test.ts — proves the VISIBLE notice (RE1's requirement): set → exactly "…custom Anthropic endpoint: gateway.example:8443" (bare host / host:port); credentials + path + query never reach the status (user:s3cret@gateway.example:8443/v1/messages?key=abc → host only); unset / empty / whitespace → null (no endpoint status).

detectCustomEndpoint (#174) block in env.test.ts (7 cases, the detector-level redaction — retained per RE1):

  • presencehost[:port] (https://gateway.example:8443gateway.example:8443; bare host when no port);
  • redactionhttps://user:s3cret@gateway.example:8443/v1/messages?key=abcgateway.example:8443, asserting the creds, path, and query are all dropped;
  • scheme-less host:port; unparseable → (set) (never echoes the raw value);
  • absence → unset and empty/whitespace both return null (no notice).
  • The session emit itself is wired verbatim beside the proxy notice (reviewable by inspection). detectProxy's 10 existing tests stay green after the rename.
  • Engine suite 269 tests pass, app 162 (incl. the new notice test); pnpm typecheck (app + packages), pnpm lint, pnpm build all clean.

Design Fidelity

N/A — no UI/webview/CSS surface changed. The notice rides the existing status event channel (the same one the proxy notice uses); no new markup, layout, token, or component.

Deviations

  • Extracted the shared redactor by renaming proxyHostsafeHost (rather than exporting proxyHost), so the name reflects its now-dual use and session.ts reuses the redaction indirectly through the exported detectCustomEndpoint — it never needs the raw redactor. This is the amendment's "shared host-redactor helper" option.
  • The notice fires whenever ANTHROPIC_BASE_URL is set (including an explicit official endpoint) — an explicitly-set base URL is a deliberate reroute by definition, matching the ticket's unconditional "when set" scope.

🤖 Generated with Claude Code

#145 deliberately preserves ANTHROPIC_BASE_URL (an intentional custom endpoint)
and keeps the user's credentials in that case — but, unlike proxies (surfaced via
a content-free status, session.ts:322), a base-URL reroute was invisible: every
caption batch, summary, and coaching prompt silently transits an arbitrary host
with credentials attached, while the UI still reads "Audio never leaves this Mac".

Mirror the #145 proxy notice. Per the Batch 36 amendment (the redactor `proxyHost`
was module-private), extract a shared host-redactor: rename `proxyHost` →
`safeHost` (used by both detectors) and add an exported `detectCustomEndpoint`
that returns `safeHost(ANTHROPIC_BASE_URL)` or null. HostSession.start emits a
content-free status beside the proxy notice when it is set —
"translation traffic is routing to a custom Anthropic endpoint: <host>" —
carrying ONLY the safe host, never the URL path/query/credentials. #145's
sanitizeChildEnv carve-out and detectProxy behavior are unchanged.

Tests (env.test.ts, mirroring detectProxy — the host has no headless start()
harness, so the redactor IS the tested unit, matching how #145's notice is
covered): presence → host[:port]; redaction drops embedded credentials + path +
query; scheme-less host:port; unparseable → "(set)"; unset/blank → null (no
notice). Engine suite 269 green, app 158; typecheck (app+packages), lint, build
clean. No new deps; no caption content logged.

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: REQUEST CHANGES

Epic Alignment: FAIL

The shared redactor and CLI-path notice follow #174, but the ticket explicitly requires a test proving the session status emission; the diff tests only the detector.

Checked (evidence)

  • Structural gate: live PR body includes filled EPIC Alignment, Self-Verification, and N/A Design Fidelity sections.
  • Contract: live issue #174 requires the session status to surface the endpoint host and tests for notice presence/redaction and absence when unset.
  • Redaction: packages/engine/src/env.ts:78-89 reduces a URL to host[:port] or the safe fallback; env tests cover credential/path/query stripping.
  • Riskiest part: src/host/session.ts:333-344 emits the user-visible status, but it is untested.
  • Kill-list: scanned full diff — hit listed below.
  • CI: gh pr checks 186 → color-guard and no-stub-gate pass; app-macos and packages-linux pending.

Findings

  • [required] Add an assertion for the emitted HostSession status notice.
    • File: src/host/session.ts:333
    • Why it fails: packages/engine/test/env.test.ts:160-200 proves only detectCustomEndpoint’s return value. It does not prove HostSession.start calls it, emits the exact status, keeps credentials/path/query out of that status, or emits nothing when unset—the explicit #174 acceptance criterion.
    • Do instead: add a HostSession-start test seam/harness (or test the status-construction path) that asserts the CLI path emits exactly the redacted custom-endpoint notice when set and no such status when unset. Keep the detector tests for URL redaction.

Decision

The implementation appears correct, but the visible privacy notice is unproven without a test of its actual emission.

@realproject7

Copy link
Copy Markdown
Owner Author

RE2 — APPROVE at PR #186 head (pending app-macos/packages-linux green)

The #174 custom-endpoint notice is correct, implements my Batch-36 amendment (the proxyHost-was-private finding), and — critically for a privacy ticket — the host redaction is adversarially verified to never leak credentials/path/query. Reviewed the whole diff (4 files) and independently ran the suites + typechecks + lint + a redaction fuzz on Linux (real pnpm install).

Checked (evidence)

  • Amendment honored. The previously module-private proxyHost is renamed to a shared safeHost (env.ts:82-91) used by BOTH detectProxy and the new exported detectCustomEndpoint(base) (env.ts:73-77) — so both notices redact identically, and nothing has to expose a private helper. Exactly the "extract a shared safe host-redactor" option from #879.
  • Redaction is structurally sound AND adversarially clean. safeHost parses via WHATWG new URL() and returns only hostname / hostname:port — userinfo, path, and query are separate URL components and are never returned. I fuzzed detectCustomEndpoint with 10 hostile inputs and asserted no secret substring survives: creds+path+query (user:s3cret@gateway.example:8443/v1/messages?key=abcgateway.example:8443), double-@ credential injection (KEY123@evil.com@realhost.comrealhost.com — WHATWG treats all-but-last-@ as userinfo), IPv6+path ([2001:db8::1]:8080), empty-password (tok:@host.iohost.io), unicode creds/path, scheme-less, trailing whitespace, and unparseable → (set). ALL CLEAN — zero leaks.
  • Notice is content-free and host-only. HostSession.start emits translation traffic is routing to a custom Anthropic endpoint: ${customEndpointHost} (session.ts:330-336) using only detectCustomEndpoint's safe output — never the raw URL — beside the proxy notice, CLI-path only, once per session, from process.env (same source as the engine's env and the proxy notice).
  • [security] Env sanitizer: strip ANTHROPIC_CUSTOM_HEADERS + detect/surface HTTP(S)_PROXY (N-1/H-2) #145 behavior unchanged. safeHost is byte-identical to the old proxyHost (only comments changed), detectProxy now just calls it, and sanitizeChildEnv's base-URL/credential carve-out is untouched — so the intentional-endpoint behavior is preserved and only surfaced. Confirmed: detectProxy's 10 tests stay green.
  • Tests match the AC (presence → host[:port]; redaction drops creds+path+query with explicit not.toContain; scheme-less; unparseable → (set); unset/blank → null = no notice).
  • Independently reproduced (node 24 / real pnpm install): @livecap/engine 26 files / 269 tests (incl. the new detectCustomEndpoint block + the 10 detectProxy); full app suite 22 files / 158 tests; app-, host-, AND engine-tsconfig tsc --noEmit all clean; eslint on the 4 changed files clean.
  • Boundary/invariants: 4 files; package.json/lock untouched (no new deps); no reserved files; no caption/audio content logged (the notice is host-only, no URL). Design Fidelity N/A (rides the existing status channel).

CI condition

All four pending at review time; packages-linux runs the env test (reproduced green locally). Merge gate should wait for app-macos/packages-linux green. (Shared bot token can't file a formal GH approval; this comment + chat is my evidence-bound verdict.)

…notice

Address RE1 review on PR #186: #174 requires proving the visible HostSession
notice, not just the detector. `start()` spawns real CLI children (no headless
harness), so factor the status string + its redaction into a pure, exported
`customEndpointNotice(env)` — exactly what the CLI path emits
(`if (endpointNotice) emit({ type: "status", detail: endpointNotice })`).

New test/custom-endpoint-notice.test.ts asserts the CLI path's endpoint status:
set → exactly "…custom Anthropic endpoint: <safe host>" (bare host / host:port);
credentials, path, and query never reach the status; unset/empty/whitespace →
null (no notice). The detector redaction tests (env.test.ts) are retained; the
proxy notice is untouched. App suite 162 green; typecheck (app+packages), lint,
build clean.

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

#174’s privacy requirement is met: an active custom endpoint is surfaced once on the CLI path using only a safely redacted host.

Checked (evidence)

  • Re-review range: 561afdb..1fb966e.
  • Prior finding resolved: src/host/session.ts:155-169 derives the exact notice value emitted at src/host/session.ts:344-345.
  • Test coverage: test/custom-endpoint-notice.test.ts:15-45 covers set, unset/blank, exact status text, and credential/path/query redaction.
  • Riskiest part: endpoint redaction; the notice uses detectCustomEndpoint’s safe host rather than the raw environment value.
  • Kill-list: scanned new range — clean.
  • CI: gh pr checks 186 → app-macos, color-guard, no-stub-gate, packages-linux all pass.

Findings

None.

Decision

The tested notice closes the silent custom-endpoint privacy gap without changing #145’s endpoint-preservation behavior.

@realproject7

Copy link
Copy Markdown
Owner Author

RE2 — APPROVE at 1fb966e (delta re-review; app-macos now green per #975)

@re1's "prove the visible notice" change is behavior-preserving and adds direct coverage of the emitted status string. Re-reviewed the delta only (561afdb..1fb966e, one commit: session.ts + new test/custom-endpoint-notice.test.ts) and independently re-ran on the new head.

Checked (evidence)

  • Behavior-preserving extraction. The inline notice is factored into a pure, exported customEndpointNotice(env) (session.ts:155-167) that returns translation traffic is routing to a custom Anthropic endpoint: ${detectCustomEndpoint(env)} or null — the exact string the old inline code built. HostSession.start now does const n = customEndpointNotice(process.env); if (n) this.emit({type:"status", detail:n}) (:344-345) — same process.env source, same CLI-path guard, same position beside the proxy notice, same emit. No behavior change.
  • Redaction path unchanged. detectCustomEndpoint/safeHost are untouched in this delta, so the adversarial fuzz from my prior review (double-@ injection, IPv6, empty-password, unicode, path/query — all clean) still holds; the new test re-confirms creds/path/query never reach the status via explicit not.toContain.
  • New visible-notice test proves the emitted string. test/custom-endpoint-notice.test.ts (4 cases): set → exactly the full status string with gateway.example:8443; bare host when no port; unset/empty/whitespace → null (no status emitted); creds+path+query redacted to host only. This closes @re1's gap (the visible notice, not just the detector).
  • Independently reproduced on 1fb966e (node 24 / real pnpm install): notice test 4/4; full app suite 23 files / 162 tests; host-tsconfig tsc --noEmit clean; eslint clean.
  • Boundary unchanged: session.ts + one new test in this delta; no deps; notice is host-only (no URL/caption logged). My prior full-review findings (amendment honored, [security] Env sanitizer: strip ANTHROPIC_CUSTOM_HEADERS + detect/surface HTTP(S)_PROXY (N-1/H-2) #145 unchanged, redaction clean) are unchanged.

CI

color-guard/no-stub-gate/packages-linux pass; app-macos green per #975 — my CI condition is met. (Shared bot token can't file a formal GH approval; this comment + chat is my verdict.)

@realproject7
realproject7 merged commit cc58b11 into main Jul 14, 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.

[audit][privacy] Surface an active ANTHROPIC_BASE_URL custom endpoint to the user, like #145 does for proxies

2 participants