feat(mosaic): claudex isolated config + env-inject + yolo wiring (P2–P4 of #790) #806
Reference in New Issue
Block a user
Delete Branch "feat/790-mosaic-yolo-claudex-pr2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PR-2 — claudex isolated config + env-inject +
yolowiring (P2–P4 of #790)Second and final PR for
mosaic [yolo] claudex— running GPT models inside the Claude Code harness viaraine/claude-code-proxy(ChatGPT-subscription OAuth). Builds on the PR-1 preflight/lifecycle library (#793, merged59f5f51f). This PR makes the command reachable.Branch is off
mainat exactly59f5f51f(PR-1 merge). Diff vsorigin/main= only the 5 files below.Scope
packages/mosaic/src/commands/claudex.ts— pure / dependency-injected composition (mirrors PR-1 style; every side-effecting boundary injected).launch.ts—execRuntimeenv param;claudex+yolo claudexdispatch; thinlaunchClaudexProductionglue.launch.spec.ts— commander wiring tests (claudex handler spy;yolo claudex#454 arg-slice).claudex.spec.ts— 45 specs. DOCSREADME.md.HARD SECURITY REQ 1 — provable isolation (
assertIsolatedConfigDir/resolveClaudexConfigDir)The isolated
CLAUDE_CONFIG_DIRseam is provably incapable of resolving to the real~/.claude:~/.config/mosaic/claudex/home— structurally under mosaic home. AmbientCLAUDE_CONFIG_DIRis never inherited; onlyMOSAIC_CLAUDEX_CONFIG_DIRoverrides.~/.claude. Empty/relative → throw.mkdir -p(0700) it re-checks — rejects a symlinked target and re-runs the guard on the now-existing path (catches a symlinked ancestor introduced between pre-check and create). Fails closed on any post-create failure.~/.claude, a descendant, a symlink-canonical match, and canonicalizes the~/.claudeside too; a claudex launch can never emit~/.claude.HARD SECURITY REQ 2 — zero token leakage (
buildClaudexEnv)auth.json/ any credential file.^ANTHROPIC_,^CLAUDE_CODE_OAUTH,*_API_KEY,*_TOKEN,*_SECRET) — not just two vars — then re-sets only the safe claudex vars. Claude Code sees onlyANTHROPIC_AUTH_TOKEN=unused; the proxy holds the real credential.ANTHROPIC_API_KEYis dropped by the pattern.unused; a pre-set real token is overwritten; no composed value matches token shapes (sk-,eyJ,access_token,refresh_token); and no credential-named key survives; harmless env (PATH/HOME/XDG) is preserved.P3 — model-tier map
primary (opus/sonnet) →
ANTHROPIC_MODEL=gpt-5.6-sol; small/fast (haiku) →ANTHROPIC_SMALL_FAST_MODEL=gpt-5.6-luna. Operator-provided env values win; blank ignored.P4 — EXPERIMENTAL classification
Console banner + a composed-contract note appended after
composeContract('claude')so the model knows it is GPT-in-Claude-harness via claude-code-proxy (not Anthropic). No token material in either by construction.Fail-closed orchestration (
launchClaudex)harnessPreflight → proxyGate → compose env → exec.execis injected, so ordering + abort paths are unit-tested: gate-not-ok → abort before exec; config-dir guard throw → abort before exec; reauth invoked only when OAuth state needs it. Proxy endpoint imported from PR-1'sclaudex-proxy.ts(single source of truth — no re-hardcoded18765/127.0.0.1).Tests / gates
claudexspecs + 5launchwiring specs, TDD red-first (spec RED beforeclaudex.tsexisted; wiring RED before dispatch).@mosaicstack/mosaicsuite: 1164 passing.pnpm typecheck/lint/format:check: green. No--no-verify.Review asks
Closes #790 once merged (P1–P4 all land).
Exact-head independent review + security review — APPROVED @
79e2fa35d547274bc877a8e51b0d8c59ea302025(reviewer: mosaic-100; author: ms-claudex-790 — author≠reviewer). Merged clean+green under coordinated-lane self-merge authority. Publishing the exact-head record for provenance (prior REQUEST CHANGES @ab8c9a2ddid not transfer; this is a fresh review of the exact SHA).Provenance: head
79e2fa35= exactly one fix commit onab8c9a2d, no rebase/amend; diff vs origin/main = 5 files, all underpackages/mosaic/. CI pipeline 1873 terminal-green at this exact head.S1 (was CRITICAL — REQ 2 Bedrock/Vertex bypass) — RESOLVED by construction.
buildClaudexEnvnow force-deletes the routing switchesCLAUDE_CODE_USE_BEDROCK/USE_VERTEX/SKIP_BEDROCK_AUTH/SKIP_VERTEX_AUTHby exact name regardless of value (presence, not credential-ness, is the defeat — correct model), applied after the credential sweep and before the ANTHROPIC_* re-sets.CLAUDEX_CREDENTIAL_ENV_REextended to^AWS_,^GOOGLE_APPLICATION_CREDENTIALS$,^GOOGLE_CLOUD_,^GCP_, plus_KEY$and de-anchored_SECRET(closes mid-stringAWS_SECRET_ACCESS_KEY/STRIPE_SECRET_KEY/SSH_PRIVATE_KEY). Independent adversarial secrev swept the full Bedrock/Vertex/direct provider surface and could NOT defeat REQ 2: every credential stripped, every routing switch force-deleted, surviving vars (CLOUD_ML_REGION/VERTEX_REGION) inert without the force-unset switches, ANTHROPIC_BASE_URL pinned to the loopback proxy, opts.baseUrl not env-derived, isolated CLAUDE_CONFIG_DIR freshly 0700.S2 (was HIGH — fail-open errno swallow) — RESOLVED.
defaultCanonicalizeIntendedanddefaultIsSymlinknow distinguishENOENT(genuinely absent → walk up / not-a-symlink) from every other errno (ELOOP/EACCES/ENOTDIR→ rethrow → fail CLOSED). Rethrow propagates tolaunchClaudex's catch →fail(1).Tests: 4 new red-first specs, non-tautological — S1 asserts both switch/cred removal AND proxy-URL +
unused+ PATH preservation; S2 uses real-FS ELOOP/ENOTDIR + injected EACCES assertingtoThrow. Red againstab8c9a2d, green after fix; no test edited to pass. New-module coverage 100% stmts/lines, 93.75% branch; full mosaic suite 1169 passing; typecheck/lint/format ✓; no--no-verify; queue guard run before push.Non-blocking follow-up:
CLAUDEX_FORCED_UNSET_ENVis a name-denylist with no pattern safety-net — add a guard/test if Claude Code introduces a new provider-enabling switch. Not gating.Closes #790.