Skip to content

feat(proxy): wire the CCR upstream proxy (C9, env-gated no-op by default) - #666

Merged
ericleepi314 merged 3 commits into
mainfrom
ccr/bridge-orchestration
Jul 6, 2026
Merged

feat(proxy): wire the CCR upstream proxy (C9, env-gated no-op by default)#666
ericleepi314 merged 3 commits into
mainfrom
ccr/bridge-orchestration

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

Summary

C9 — wire the CCR upstream proxy (env-gated, no-op by default). The port ported src/upstreamproxy/upstream_proxy.py (init_upstream_proxy + get_upstream_proxy_env, gated on CLAUDE_CODE_REMOTE) but left it UNWIRED — zero live callers, and subprocess_env carried a documented TODO. This restores the genuine parity the open TS build has (init.ts:140-156 + subprocessEnv.ts:79-99; CLAUDE_CODE_REMOTE appears in 30 TS files).

  • src/utils/subprocess_env.py: register_upstream_proxy_env_fn(fn) + a provider slot (mirrors TS registerUpstreamProxyEnvFn — no static import of the asyncio/ssl/relay graph). subprocess_env merges the proxy recipe FIRST, then runs the scrub loop LAST so the anti-exfiltration scrub stays authoritative — exact TS subprocessEnv.ts:85-98 order (a provider can't re-introduce a scrubbed secret).
  • src/entrypoints/agent_server_cli.py: _maybe_init_upstream_proxy() — when CLAUDE_CODE_REMOTE is truthy, registerUpstreamProxyEnvFn then await init_upstream_proxy() (exact init.ts:148-149 order), fail-open. Called from BOTH _serve (HTTP) and _serve_stdio — matching TS's shared init.ts for all sessions.

Default behaviour unchanged: with CLAUDE_CODE_REMOTE unset (every local build), no provider is registered and subprocess_env is byte-for-byte the base; the entrypoint short-circuits before any import. Zero blast radius — the value is that a CCR-remote deployment's ported proxy actually engages.

Critic (2 rounds → APPROVE)

Corrected two real errors of mine: my "open build strips this" premise (a worktree-grep artifact — typescript/ is main-only; the open build HAS the wiring) and my pre-emptive init-first reorder (a parity regression on a false premise — register-first is already safe since a disabled proxy returns {}). Both MAJORs fixed to exact TS parity and verified line-by-line: "Both MAJORs correctly and faithfully resolved… Default no-op airtight. Ship it."

Verification

tests/test_c9_upstream_proxy_wiring.py (9): default no-op (env byte-for-byte unchanged / empty-provider no-op), proxy-var merge, scrub-authoritative (a provider returning ANTHROPIC_API_KEY is stripped while HTTPS_PROXY survives), fail-open, failed-init-injects-nothing, disabled-returns-empty, entrypoint-guard-noop. 49 subprocess/scrub/upstream_proxy tests green; prior-commit full suite at the 6-failure baseline.

🤖 Generated with Claude Code

ericleepi314 and others added 3 commits July 6, 2026 13:39
…fault)

The port ported upstream_proxy.py (init_upstream_proxy + get_upstream_proxy_env,
gated on CLAUDE_CODE_REMOTE) but left it UNWIRED — zero live callers, and
subprocess_env carried a TODO ("when it lands, merge get_upstream_proxy_env").
This wires it, mirroring TS's registerUpstreamProxyEnvFn indirection.

- src/utils/subprocess_env.py: register_upstream_proxy_env_fn(fn) + a module
  provider slot. subprocess_env merges the provider's recipe AFTER the scrub
  (so an injected HTTPS_PROXY / *_CA_BUNDLE isn't stripped by the
  anti-exfiltration pass), best-effort (a proxy-env failure can't break child
  spawning). The indirection keeps this module from statically importing the
  upstreamproxy package (asyncio/ssl/relay).
- src/entrypoints/agent_server_cli.py: _maybe_init_upstream_proxy() at _serve
  start — when CLAUDE_CODE_REMOTE is truthy, register the provider + await
  init_upstream_proxy(); FAIL-OPEN (a remote session degrades to direct rather
  than the server dying). No-op otherwise.

DEFAULT BEHAVIOUR UNCHANGED: with CLAUDE_CODE_REMOTE unset (every local build,
and the open TS build strips this path entirely), the provider is never
registered and subprocess_env is byte-for-byte the base. Low blast radius —
the value is that a CCR-remote deployment's ported proxy actually engages
instead of being dead code.

Scope: the WS mTLS/CA-context and relay.stop()-cleanup pieces from the plan live
inside the dormant relay itself (no open-build reference, untestable without CCR
infra) — deferred; this lands the reachable, testable wiring.

tests/test_c9_upstream_proxy_wiring.py (6): default no-op (env unchanged /
empty-provider no-op), proxy-var merge, survives-the-scrub, fail-open, and the
entrypoint guard is a no-op without the env. 89 subprocess/proxy tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-addressing the c9-critic's highest-risk probe: _maybe_init_upstream_proxy
registered the env provider BEFORE await init_upstream_proxy(), so a failed init
(fail-open) could leave the provider registered. Reordered to init-first,
register-only-on-success — a failed init now leaves ZERO registration, so
subprocess_env stays a pure no-op. (Defense-in-depth: get_upstream_proxy_env
already returns {} when the proxy state is DISABLED with a clean env, verified.)

Tests: a failing init leaves _upstream_proxy_env_fn is None (no half-registered
provider); the disabled state returns an empty recipe. 8 C9 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…S parity)

The c9-critic corrected two real errors (and disproved my #4 premise: I'd
grepped typescript/ INSIDE the worktree where it doesn't exist — the open build
HAS this wiring at init.ts:148-149, 30 files reference CLAUDE_CODE_REMOTE. So C9
is genuine restored parity, not overreach — ship it).

MAJOR-A [subprocess_env.py] — scrub/merge order was INVERTED vs TS. TS
(subprocessEnv.ts:91-97) merges the proxy recipe THEN runs the scrub loop, so
the SCRUB is authoritative/last — no provider can re-introduce a scrubbed
secret. The port scrubbed first then merged, so a provider returning a scrub-set
key would SURVIVE into the child (a defense-in-depth hole in the exact
anti-exfiltration control), and the comment falsely claimed "TS order". Fixed:
merge proxy first, THEN scrub — exact TS order; proxy keys aren't in the scrub
set so they still survive. New test proves a provider returning ANTHROPIC_API_KEY
is scrubbed while HTTPS_PROXY survives.

MAJOR-B [agent_server_cli.py] — my earlier #3 "fix" (init-first) diverged from
TS register-first (init.ts:148-149) on a FALSE premise: the critic proved a
provider over a disabled/failed init returns {} on a clean env (safe in EITHER
order). Reverted to register-first for exact TS parity; dropped the false
"injects bad vars" rationale. Test updated to assert the real safe property (a
failed init injects nothing because the disabled state returns {}).

Also (critic notes): wired _serve_stdio too (TS inits in shared init.ts for ALL
sessions, not just HTTP — a CCR-remote stdio container would otherwise miss the
proxy); fixed the upstream_proxy docstring (9-var→8-var, dropped the nonexistent
lowercase `proxy` claim).

9 C9 tests green (default no-op, proxy-merge, scrub-authoritative, fail-open,
disabled-returns-empty, entrypoint-guard). subprocess/proxy suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ericleepi314
ericleepi314 merged commit 2222fc2 into main Jul 6, 2026
@ericleepi314
ericleepi314 deleted the ccr/bridge-orchestration branch July 6, 2026 20:58
ericleepi314 added a commit that referenced this pull request Jul 7, 2026
…ult) (#666)

* feat(proxy): wire the CCR upstream proxy (C9 — env-gated, no-op by default)

The port ported upstream_proxy.py (init_upstream_proxy + get_upstream_proxy_env,
gated on CLAUDE_CODE_REMOTE) but left it UNWIRED — zero live callers, and
subprocess_env carried a TODO ("when it lands, merge get_upstream_proxy_env").
This wires it, mirroring TS's registerUpstreamProxyEnvFn indirection.

- src/utils/subprocess_env.py: register_upstream_proxy_env_fn(fn) + a module
  provider slot. subprocess_env merges the provider's recipe AFTER the scrub
  (so an injected HTTPS_PROXY / *_CA_BUNDLE isn't stripped by the
  anti-exfiltration pass), best-effort (a proxy-env failure can't break child
  spawning). The indirection keeps this module from statically importing the
  upstreamproxy package (asyncio/ssl/relay).
- src/entrypoints/agent_server_cli.py: _maybe_init_upstream_proxy() at _serve
  start — when CLAUDE_CODE_REMOTE is truthy, register the provider + await
  init_upstream_proxy(); FAIL-OPEN (a remote session degrades to direct rather
  than the server dying). No-op otherwise.

DEFAULT BEHAVIOUR UNCHANGED: with CLAUDE_CODE_REMOTE unset (every local build,
and the open TS build strips this path entirely), the provider is never
registered and subprocess_env is byte-for-byte the base. Low blast radius —
the value is that a CCR-remote deployment's ported proxy actually engages
instead of being dead code.

Scope: the WS mTLS/CA-context and relay.stop()-cleanup pieces from the plan live
inside the dormant relay itself (no open-build reference, untestable without CCR
infra) — deferred; this lands the reachable, testable wiring.

tests/test_c9_upstream_proxy_wiring.py (6): default no-op (env unchanged /
empty-provider no-op), proxy-var merge, survives-the-scrub, fail-open, and the
entrypoint guard is a no-op without the env. 89 subprocess/proxy tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(proxy): C9 #3 — register the proxy provider only AFTER init succeeds

Pre-addressing the c9-critic's highest-risk probe: _maybe_init_upstream_proxy
registered the env provider BEFORE await init_upstream_proxy(), so a failed init
(fail-open) could leave the provider registered. Reordered to init-first,
register-only-on-success — a failed init now leaves ZERO registration, so
subprocess_env stays a pure no-op. (Defense-in-depth: get_upstream_proxy_env
already returns {} when the proxy state is DISABLED with a clean env, verified.)

Tests: a failing init leaves _upstream_proxy_env_fn is None (no half-registered
provider); the disabled state returns an empty recipe. 8 C9 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(proxy): C9 critic — scrub-authoritative order + register-first (TS parity)

The c9-critic corrected two real errors (and disproved my #4 premise: I'd
grepped typescript/ INSIDE the worktree where it doesn't exist — the open build
HAS this wiring at init.ts:148-149, 30 files reference CLAUDE_CODE_REMOTE. So C9
is genuine restored parity, not overreach — ship it).

MAJOR-A [subprocess_env.py] — scrub/merge order was INVERTED vs TS. TS
(subprocessEnv.ts:91-97) merges the proxy recipe THEN runs the scrub loop, so
the SCRUB is authoritative/last — no provider can re-introduce a scrubbed
secret. The port scrubbed first then merged, so a provider returning a scrub-set
key would SURVIVE into the child (a defense-in-depth hole in the exact
anti-exfiltration control), and the comment falsely claimed "TS order". Fixed:
merge proxy first, THEN scrub — exact TS order; proxy keys aren't in the scrub
set so they still survive. New test proves a provider returning ANTHROPIC_API_KEY
is scrubbed while HTTPS_PROXY survives.

MAJOR-B [agent_server_cli.py] — my earlier #3 "fix" (init-first) diverged from
TS register-first (init.ts:148-149) on a FALSE premise: the critic proved a
provider over a disabled/failed init returns {} on a clean env (safe in EITHER
order). Reverted to register-first for exact TS parity; dropped the false
"injects bad vars" rationale. Test updated to assert the real safe property (a
failed init injects nothing because the disabled state returns {}).

Also (critic notes): wired _serve_stdio too (TS inits in shared init.ts for ALL
sessions, not just HTTP — a CCR-remote stdio container would otherwise miss the
proxy); fixed the upstream_proxy docstring (9-var→8-var, dropped the nonexistent
lowercase `proxy` claim).

9 C9 tests green (default no-op, proxy-merge, scrub-authoritative, fail-open,
disabled-returns-empty, entrypoint-guard). subprocess/proxy suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
ericleepi314 added a commit that referenced this pull request Jul 21, 2026
…ult) (#666)

* feat(proxy): wire the CCR upstream proxy (C9 — env-gated, no-op by default)

The port ported upstream_proxy.py (init_upstream_proxy + get_upstream_proxy_env,
gated on CLAUDE_CODE_REMOTE) but left it UNWIRED — zero live callers, and
subprocess_env carried a TODO ("when it lands, merge get_upstream_proxy_env").
This wires it, mirroring TS's registerUpstreamProxyEnvFn indirection.

- src/utils/subprocess_env.py: register_upstream_proxy_env_fn(fn) + a module
  provider slot. subprocess_env merges the provider's recipe AFTER the scrub
  (so an injected HTTPS_PROXY / *_CA_BUNDLE isn't stripped by the
  anti-exfiltration pass), best-effort (a proxy-env failure can't break child
  spawning). The indirection keeps this module from statically importing the
  upstreamproxy package (asyncio/ssl/relay).
- src/entrypoints/agent_server_cli.py: _maybe_init_upstream_proxy() at _serve
  start — when CLAUDE_CODE_REMOTE is truthy, register the provider + await
  init_upstream_proxy(); FAIL-OPEN (a remote session degrades to direct rather
  than the server dying). No-op otherwise.

DEFAULT BEHAVIOUR UNCHANGED: with CLAUDE_CODE_REMOTE unset (every local build,
and the open TS build strips this path entirely), the provider is never
registered and subprocess_env is byte-for-byte the base. Low blast radius —
the value is that a CCR-remote deployment's ported proxy actually engages
instead of being dead code.

Scope: the WS mTLS/CA-context and relay.stop()-cleanup pieces from the plan live
inside the dormant relay itself (no open-build reference, untestable without CCR
infra) — deferred; this lands the reachable, testable wiring.

tests/test_c9_upstream_proxy_wiring.py (6): default no-op (env unchanged /
empty-provider no-op), proxy-var merge, survives-the-scrub, fail-open, and the
entrypoint guard is a no-op without the env. 89 subprocess/proxy tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(proxy): C9 #3 — register the proxy provider only AFTER init succeeds

Pre-addressing the c9-critic's highest-risk probe: _maybe_init_upstream_proxy
registered the env provider BEFORE await init_upstream_proxy(), so a failed init
(fail-open) could leave the provider registered. Reordered to init-first,
register-only-on-success — a failed init now leaves ZERO registration, so
subprocess_env stays a pure no-op. (Defense-in-depth: get_upstream_proxy_env
already returns {} when the proxy state is DISABLED with a clean env, verified.)

Tests: a failing init leaves _upstream_proxy_env_fn is None (no half-registered
provider); the disabled state returns an empty recipe. 8 C9 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(proxy): C9 critic — scrub-authoritative order + register-first (TS parity)

The c9-critic corrected two real errors (and disproved my #4 premise: I'd
grepped typescript/ INSIDE the worktree where it doesn't exist — the open build
HAS this wiring at init.ts:148-149, 30 files reference CLAUDE_CODE_REMOTE. So C9
is genuine restored parity, not overreach — ship it).

MAJOR-A [subprocess_env.py] — scrub/merge order was INVERTED vs TS. TS
(subprocessEnv.ts:91-97) merges the proxy recipe THEN runs the scrub loop, so
the SCRUB is authoritative/last — no provider can re-introduce a scrubbed
secret. The port scrubbed first then merged, so a provider returning a scrub-set
key would SURVIVE into the child (a defense-in-depth hole in the exact
anti-exfiltration control), and the comment falsely claimed "TS order". Fixed:
merge proxy first, THEN scrub — exact TS order; proxy keys aren't in the scrub
set so they still survive. New test proves a provider returning ANTHROPIC_API_KEY
is scrubbed while HTTPS_PROXY survives.

MAJOR-B [agent_server_cli.py] — my earlier #3 "fix" (init-first) diverged from
TS register-first (init.ts:148-149) on a FALSE premise: the critic proved a
provider over a disabled/failed init returns {} on a clean env (safe in EITHER
order). Reverted to register-first for exact TS parity; dropped the false
"injects bad vars" rationale. Test updated to assert the real safe property (a
failed init injects nothing because the disabled state returns {}).

Also (critic notes): wired _serve_stdio too (TS inits in shared init.ts for ALL
sessions, not just HTTP — a CCR-remote stdio container would otherwise miss the
proxy); fixed the upstream_proxy docstring (9-var→8-var, dropped the nonexistent
lowercase `proxy` claim).

9 C9 tests green (default no-op, proxy-merge, scrub-authoritative, fail-open,
disabled-returns-empty, entrypoint-guard). subprocess/proxy suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant