fix(security): stop ANTHROPIC_BASE_URL settings overrides redirecting the agent off the PostHog gateway#703
fix(security): stop ANTHROPIC_BASE_URL settings overrides redirecting the agent off the PostHog gateway#703gewenyu99 wants to merge 6 commits into
Conversation
…cts the gateway LoggingUI.showSettingsOverride was a no-op (`return Promise.resolve()`), so a non-interactive `--ci` run that detected a Claude Code settings conflict ignored it and launched the agent anyway. If that settings file carried `env.ANTHROPIC_BASE_URL` (e.g. a third-party relay like api.code-relay.com), every model call was silently redirected off the PostHog LLM Gateway — the TUI screens (SettingsOverride / ManagedSettings) refuse, but CI leaked. Now CI enforces the same guarantee: remove the writable (project) override via backupAndFix; reject (so the runner aborts before the agent starts) on any override we can't remove (managed / global / project-local). Regression test in src/ui/__tests__/logging-ui-settings-guard.test.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🧙 Wizard CIRun the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands: Test all apps:
Test all apps in a directory:
Test an individual app:
Show more apps
Results will be posted here when complete. |
…tforms The interactive (TUI) leak: detection hardcoded the macOS managed-settings path (/Library/Application Support/ClaudeCode/managed-settings.json), so a managed (org/MDM) `env.ANTHROPIC_BASE_URL` on Linux (/etc/claude-code/...) or Windows (C:\ProgramData\...) went undetected. Claude Code applies managed settings regardless of settingSources, so every model call was redirected off the PostHog gateway (e.g. to a relay like api.code-relay.com) — even in a normal interactive run, with no conflict screen shown. Check all three platform managed paths (a non-current-platform path simply won't exist, so this is safe). Now the existing ManagedSettingsScreen / CI guard fire on every OS. Cross-platform regression test added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ts the spawn env scripts/precedence.no-jest.ts — two local listeners, a project .claude/settings.json that sets ANTHROPIC_BASE_URL, and a real claude-code query with the GATEWAY in the spawn env (exactly as the wizard passes it). Result: the /v1/messages call goes to the SETTINGS host, not the spawn-env host — confirming env-override alone is insufficient and the wizard must detect/remove the settings file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Repro + verification on real production code (
|
prod runAgent |
/v1/messages → |
|
|---|---|---|
| BEFORE (no-op) | real integration agent | RELAY 🔴 leak |
| AFTER (fix) | real integration agent | gateway 🟢 no leak |
Cause of "override not removed" in the wild
Claude Code settings-env beats the spawn env (confirmed directly: scripts/precedence.no-jest.ts — /v1/messages hits the settings host). It goes undetected when it lives in a managed file on a platform the old detection didn't check — MANAGED_SETTINGS_PATHS was macOS-only, so a Linux /etc/claude-code/managed-settings.json returned checkAllSettingsConflicts → []. Fixed here to check all three platform paths.
Reproduce
scripts/relay-prod.no-jest.ts (in this PR):
env -u CLAUDECODE -u CLAUDE_CODE_SDK_HAS_OAUTH_REFRESH -u CLAUDE_CODE_SDK_HAS_HOST_AUTH_REFRESH \
POSTHOG_PERSONAL_API_KEY=<phx> npx tsx scripts/relay-prod.no-jest.ts
Run on origin/main → LEAK; on this branch → NO LEAK.
🤖 Generated with Claude Code
scripts/relay-prod.no-jest.ts — runs the wizard's REAL runAgent against a project with a Claude Code settings ANTHROPIC_BASE_URL override and a localhost relay listener. On origin/main the integration agent's /v1/messages hits the relay (leak); with the fix it hits the gateway (no leak). scripts/precedence.no-jest.ts isolates the mechanism (settings env beats spawn env). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nt behavior - README: add "Explore with an agent" under Running locally → Testing (was wrongly placed in the workbench README). - scripts/README: drop the cross-PR pointer to the #703 repro scripts. - Trim header/inline comments across the harness + scripts to concise descriptions of what the code does now — no history, no change-rationale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What the user saw
A prod, interactive
npx @posthog/wizardrun whose agent went tohttps://api.code-relay.com. The error string isn't in our source — it came from claude-code/the relay, i.e. the spawned agent was actually pointed there.Investigation (measured, not assumed)
ANTHROPIC_BASE_URLis NOT the vector. The wizard overrides it atagent-interface.ts:564. Measured: withANTHROPIC_BASE_URL=https://api.code-relay.comexported, the agent subprocess receiveshttps://gateway.us.posthog.com/wizard. So the "inherited shell env var" theory doesn't hold against current code.ANTHROPIC_AUTH_TOKEN/ANTHROPIC_API_KEYchange auth, not the host → they 401, they don't redirect.ANTHROPIC_BASE_URLin a Claude Code settings file (envblock). claude-code applies settings-envover the process env, which is why the wizard removes/blocks the file rather than just env-overriding.The interactive path already blocks the settings files it detects (
SettingsOverrideScreenbacks the project file up;ManagedSettingsScreenexits). So the leak is a detection gap, and there are two:Bug 1 (the interactive/prod leak): managed-settings detection was macOS-only
MANAGED_SETTINGS_PATHShardcoded only/Library/Application Support/ClaudeCode/managed-settings.json. On Linux (/etc/claude-code/...) or Windows (C:\ProgramData\...), an org/MDM-managedenv.ANTHROPIC_BASE_URL(a corporate relay) was never detected → no conflict screen → agent launches → claude-code applies the managed override → every call redirected, even interactively. Fix: check all three platform paths.Bug 2 (the
--cileak):LoggingUI.showSettingsOverridewas a no-opA
--cirun detected the conflict and thenreturn Promise.resolve()'d — launching the agent with the override in place. Fix: remove the writable (project) override; reject (abort before launch) on anything non-removable.Before / after — driven through the new e2e control-plane harness
The AFTER panel is the real Ink screen, reconstructed from store state and rendered offline by the wizard-ci-tools control plane (
renderFrame) — no agent, no network. (Same harness renders the--cirefusal for Bug 2.)Tests
src/lib/agent/__tests__/managed-settings-crossplatform.test.ts— BEFORE (macOS-only) misses the Linux managed file; AFTER detects it (managed, non-writable); default paths cover Linux+Windows.src/ui/__tests__/logging-ui-settings-guard.test.ts— CI refuses non-removable overrides, removes the writable one, resolves when clean.🤖 Generated with Claude Code