fix(agents): detect and warn on ANTHROPIC_BASE_URL override in ~/.claude/settings.json - #425
fix(agents): detect and warn on ANTHROPIC_BASE_URL override in ~/.claude/settings.json#425alex-budanov wants to merge 20 commits into
Conversation
yanaSelin
left a comment
There was a problem hiding this comment.
Code review — EPMCDME-10988 (3 findings: 1 critical, 2 major)
All three review lenses ran (blind, edge-case, acceptance). Two findings are straight patches; one needs a scope decision from the product owner before it can be resolved.
See inline comments for details.
|
Second-pass fixes (CR-001 / CR-002 / CR-003 from tech lead review) All three items addressed via TDD (test written RED, confirmed fail, then implementation to GREEN):
19 tests passing, no lint/typecheck warnings. |
Security hardening follow-up (commit 8317768)Three terminal-injection gaps identified during code review were patched in a follow-up commit: CR-001 — OSC C1-form CR-002 — CR-003 — Three regression tests added to |
…ection Design for detecting ANTHROPIC_BASE_URL in ~/.claude/settings.json that would silently override the active profile URL. Proposes a new settings-conflict.ts helper called from claude.plugin.ts beforeRun with chalk-formatted warning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3-task TDD plan: settings-conflict.ts helper (6 unit tests), beforeRun wiring (3 integration tests), and project-config.md documentation update. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire detectSettingsConflict into lifecycle.beforeRun: emits a chalk.yellow multi-line warning to stderr when ~/.claude/settings.json contains an ANTHROPIC_BASE_URL that differs from (or is absent from) the profile env. Uses a dynamic import of settings-conflict.js (matching the existing statusline-installer pattern) to avoid auto-mock initialisation slowness in the WSL2 test environment. hookTimeout is raised from 10 s to 30 s in vitest.config.ts to accommodate the Windows-filesystem cold-start latency; this also resolves pre-existing statusline test timeouts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap detectSettingsConflict import+call in try/catch so a thrown exception (e.g. os.homedir() failing in containers) degrades gracefully with logger.warn instead of aborting session startup - Strip C0/C1 control characters and ANSI CSI sequences from URL values before chalk interpolation to prevent terminal injection via a crafted ~/.claude/settings.json - Replace ?? with || for profileUrl fallback so an explicit empty-string profile URL also shows "(not set)" in the warning Extends the integration test suite from 3 to 6 tests to cover: - try/catch degradation path (logger.warn, no throw) - empty-string profileUrl fallback - C0/ANSI stripping in URL values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update spec to reflect the implementation decisions made during SDD: - displayWarningMessage replaced by console.error(chalk.yellow(...)) - Integration tests go in dedicated conflict.test.ts file - Clarify resolveHomeDir import path pattern Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cover empty-string ANTHROPIC_BASE_URL (CR-004) and readFile EACCES rejection (CR-005) identified in tech lead code review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add git clone from fork as the primary path for users without the repo - Replace hardcoded WSL path in smoke.mjs with $REPO_DIR shell variable - Clarify that origin/fork remote layout is for existing clones only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docs/superpowers/runs/ is gitignored — this file was accidentally force-added. It is a local pipeline artifact, not part of the PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CR-001 [CRITICAL]: read ANTHROPIC_BASE_URL from settings.env block
Claude Code stores env vars under a nested `env` object in settings.json,
not at the root. `settings.ANTHROPIC_BASE_URL` was always undefined so
detection never fired. Fixed to read `settings.env.ANTHROPIC_BASE_URL`.
Updated all test mocks to use `{ env: { ... } }` schema so they validate
the spec rather than the broken implementation.
CR-002 [MAJOR]: fix CSI regex alternation order to prevent bracket residue
The C0 catch-all `[\x00-\x1f]` was consuming `\x1b` before the full CSI
pattern could match, leaving `[31mFORGED[0m` visible in the terminal.
Moved the CSI alternative first so the entire escape sequence is consumed
atomically. Added a failing test for the injected-residue attack vector.
Also: add `vi.mock('../../../core/BaseAgentAdapter.js')` to the conflict
integration test to prevent SSO provider auto-registration from triggering
network I/O during test setup (caused 30s timeouts in WSL/CI environments).
CR-001: update project-config.md to document env block schema for ANTHROPIC_BASE_URL; add regression test asserting root-level key returns null. CR-002: replace hand-rolled CSI regex in safeUrl with strip-ansi + C1 control-range blocklist ([\x00-\x1f\x7f-\x9f]); add test for single-byte C1 CSI form (\x9b) that the old regex missed. CR-003: guard against JSON.parse returning null or a non-object value in detectSettingsConflict; add tests for JSON null and JSON array inputs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oofing Switch from a blocklist to a layered defence: 1. DCS pre-strip — remove full \x1bP/\x1bX/\x1b^/\x1b_ sequences including payload before strip-ansi; ansi-regex only strips the 2-byte introducer, leaving any ASCII payload intact. 2. ASCII allowlist [^\x20-\x7e] — replaces the old C0/C1 blocklist; also covers Bidi override chars, soft hyphen (U+00AD), zero-width chars, combining marks, and all other non-ASCII Unicode vectors in one rule. 3. URL userinfo guard — new URL() parse detects credentials in the URL (the @-trick: https://trusted@evil.com routes to evil.com); userinfo is stripped and [credentials removed] is prepended so the real host is visible. Two new TDD tests added (10 total in the conflict test suite). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tests CR-NEW-001: the hardcoded '(not set — direct Anthropic API)' fallback was passed through safeUrl, whose ASCII allowlist strips U+2014 (em dash) and collapses the surrounding spaces to a double space. Fixed by bypassing safeUrl for the known-safe constant — only user-controlled values need sanitising. Four tests added (14 total): - em dash preserved in hardcoded fallback (regression guard for CR-NEW-001) - DCS with no terminator — validates the |$ end-of-string fallback path - C1 DCS form (\x90...\x07) — single-byte introducer path in pre-strip regex - URL userinfo with password field (user:pass@host) — covers url.password branch Also adds an inline comment on the DCS regex identifying the ESC-form and C1-form character groups (\x90 \x98 \x9e \x9f) for future maintainers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expand credentials-removal branch to also clear url.search and trigger it when query params are present. Prevents api_key= style secrets leaking into console.error and CI log aggregators. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ay model in conflict warning
Extends ConflictInfo with optional settingsModel/profileModel fields.
detectSettingsConflict now checks settings.env.ANTHROPIC_MODEL alongside
ANTHROPIC_BASE_URL. The CLI warning block conditionally shows a model row
when a model conflict is present, satisfying AC-3 ('display actual
endpoint/model being used').
16 unit tests passing (5 new for model-conflict paths).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vectors - CR-001: add \x9d to DCS pre-strip regex to cover C1-form OSC introducer - CR-002: clear url.hash in safeUrl to prevent fragment-payload leakage - CR-003: wrap conflict.profileModel in safeUrl() before terminal output - Add three regression tests covering each injection vector All 17 tests in claude.plugin.conflict.test.ts pass.
8317768 to
bd3655e
Compare
Hardcoded POSIX string caused Test (Windows) to fail: path.join on Windows converts '/home/testuser/claude' to '\home\testuser\claude', so the assertion never matched. Computing SETTINGS_PATH with join() makes the expected value platform-correct on both Ubuntu and Windows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Fixes EPMCDME-10988. `codemie-code` injects `ANTHROPIC_BASE_URL` via the active profile, but Claude Code silently overrides that value if `~/.claude/settings.json` also contains `ANTHROPIC_BASE_URL`. The startup banner showed the profile URL while the session used a different endpoint — invisible to the user.
This change detects the override at startup and prints a visible warning showing both URLs before the session starts. It also detects `ANTHROPIC_MODEL` overrides (scope expanded from the original spec — approved).
Changes
Testing
Automated (33 tests):
Manual E2E smoke test:
Since `codemie-code` is not yet in production and the full CLI is interactive, the warning was verified by importing `ClaudePluginMetadata.lifecycle.beforeRun` directly from the built `dist/` in a minimal Node.js script:
Checklist
Closes EPMCDME-10988
🤖 Generated with Claude Code