MODULO 6.4

🔀 Upstream Proxy

7
Topicos
~60
Minutos
Deep
Nivel
Source
Tipo
1

🏗️ Problem & Architecture

CCR containers need outbound HTTP traffic to carry injected credentials and be auditable. Solution: HTTPS CONNECT proxy on 127.0.0.1, tunnelled via WebSocket to Anthropic's gateway. Gateway MITMs TLS, injects org-configured headers, forwards to upstream.

💡 Fails Open by Design

Every step in initUpstreamProxy is wrapped so that any error logs a warning and returns { enabled: false }. A broken proxy must never break a working session.

2

🔧 Initialization Sequence

Step 1: Guard env vars

Returns early if CLAUDE_CODE_REMOTE or CCR_UPSTREAM_PROXY_ENABLED is not truthy.

Step 2: Read session token

Reads from /run/ccr/session_token, written by container orchestrator.

Step 3: Set non-dumpable

prctl(PR_SET_DUMPABLE, 0) via Bun FFI blocks same-UID ptrace/gdb.

Step 4: Download CA bundle

Fetches MITM proxy CA cert, concatenates with system CA bundle.

Step 5: Start relay

TCP server on ephemeral port. Returns port number.

Step 6: Unlink token file

Deleted AFTER relay confirms listening. Token lives in heap only.

3

🌐 CONNECT-over-WebSocket Relay

Clients (curl, gh, npm) connect and send HTTP CONNECT requests. Relay upgrades WebSocket to gateway and tunnels bytes bidirectionally. Two-phase connection: accumulate until CRLF CRLF terminates CONNECT header, then pump bytes.

💡 Race Condition Solved

TCP can coalesce CONNECT header and TLS ClientHello into one packet. Bytes after CRLF CRLF go into st.pending[] and flush in ws.onopen. Without this, ClientHello would be silently dropped.

4

📦 Protobuf Encoding

Bytes wrapped in UpstreamProxyChunk protobuf message. Hand-rolled encoding: tag byte 0x0a (field 1, wire type 2), varint length, payload. Content-Type: application/proto tells server to use binary proto deserialization.

💡 Why Not Plain Bytes

CCR gateway uses NewWebSocketStreamAdapter which expects this framing. Without Content-Type: application/proto, server silently fails with EOF.

5

🔄 Bun vs Node Runtime

Runtime dispatch: Bun (developer) vs Node (production CCR). Key difference: Node buffers writes unconditionally; Bun's sock.write() does partial kernel write and silently drops remainder. Bun relay tracks unwritten bytes in per-socket writeBuf[].

💡 WebSocket Proxy Agent

CCR containers behind egress gateway. WS upgrade itself must go through HTTP CONNECT proxy. Node uses ws package with explicit agent; Bun's native WebSocket accepts proxy URL directly.

6

🛡️ Security Model

Prompt Injection Defense

prctl(PR_SET_DUMPABLE, 0) blocks same-UID ptrace, defeating gdb -p $PPID attacks.

Dual Authentication

WS upgrade: Authorization Bearer token. CONNECT tunnel: Proxy-Authorization Basic (inside first protobuf chunk).

TLS Corruption Guard

After 200 Connection Established, plaintext 502 errors are never written - would corrupt active TLS cipher state.

7

⚡ Env Var Propagation

VariablePurpose
HTTPS_PROXY / https_proxyRoute HTTPS through relay
NO_PROXY / no_proxyBypass for loopback, RFC1918, Anthropic API
SSL_CERT_FILEOpenSSL / curl CA trust
NODE_EXTRA_CA_CERTSNode.js TLS trust
REQUESTS_CA_BUNDLEPython requests/httpx trust
CURL_CA_BUNDLEcurl CA trust

💡 HTTPS Only

Only HTTPS_PROXY is set, never HTTP_PROXY. Relay only handles CONNECT - plain HTTP returns 405.

📋 Resumo do Modulo

Upstream proxy is container-only: two env var guards ensure it never activates on developer machines.
Session token deleted from disk AFTER relay confirms listening - allows supervisor restarts on partial failure.
prctl(PR_SET_DUMPABLE, 0) is kernel-level defense against prompt-injection-driven ptrace attacks.
Hand-rolled protobuf framing: one tag byte + varint length + payload satisfies gateway's protocol.
Bun and Node relay implementations differ in write backpressure handling.
All eight proxy env vars injected into every subprocess via getUpstreamProxyEnv().
Voltar Proximo