mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
7897be4a95
## Problem — the leak The v2 runtime's `shouldForwardHeader` forwarded `authorization` **and any header whose name starts with `x-`** onto the outgoing agent call. In a real deployment the inbound request has already traversed a browser, CDN/edge, load balancer, and hosting platform — each stamping its own `x-*` headers — so the wide `x-*` wildcard silently forwarded: - **Hop-by-hop / topology:** `x-forwarded-for`, `x-real-ip`, `x-forwarded-proto/host/port` - **Cloud / CDN tracing:** `x-amzn-trace-id`, `x-amz-cf-id`, `x-cloud-trace-context`, `x-azure-*`, `x-fastly-*`, `x-request-id` - **Platform-injected:** `x-vercel-*`, `x-middleware-*` - **CopilotKit Cloud platform credential:** `x-copilotcloud-public-api-key` The last item is a real credential-exfiltration concern: a platform key scoped to Copilot Cloud reaching a third-party agent URL. This is the **breadth** half of #5712 (option 3); the **precedence** half was fixed in #5782. ## Design — denylist default + config knob, both paths - **Default denylist (safe default).** Keep the `authorization` + `x-*` base eligibility, but strip a curated, greppable set of known infra/proxy/platform headers (exact names + prefix families) before forwarding. Legitimate custom `x-*` application headers (`x-tenant-id`, `x-api-key`, …) keep flowing untouched. The authoritative list is a single exported constant in `header-utils.ts`. - **Configurable policy (`forwardHeaders` runtime option).** - `useDefaultDenylist?: boolean` (default **true**) — `false` restores the previous wide-open behavior. - `deny?` / `denyPrefixes?` — extend the default denylist. - `allow?` — opt into strict allowlist mode (only listed headers forward). - **Resolve once.** The constructor resolves `forwardHeaders` into a `forwardHeadersPolicy: ResolvedForwardHeadersPolicy` field (mirroring the existing `debug` → `ResolvedDebugConfig` resolve-once), exposed on `CopilotRuntimeLike` / `BaseCopilotRuntime` with a passthrough getter on the `CopilotRuntime` shim. - **Both paths.** The resolved policy is read at **/run** (`configureAgentForRequest`) and **/connect** (`handleSseConnect`) via `mergeForwardableHeaders`, so the two can never diverge. Server-wins precedence and server-self case-dedup from #5782 are untouched. ## Semver **Minor with an opt-out.** Removing a leak is a fix, not a contract change, and we ship a documented escape hatch: `new CopilotRuntime({ agents, forwardHeaders: { useDefaultDenylist: false } })` restores the prior behavior. Custom-header forwarders (the common case) are unaffected. ## Red-green proof (real surface, both paths) RED — with the predicate reverted to the old wide-open `authorization || x-*` (policy ignored), the new behavior assertions fail; the leak reproduces (`x-forwarded-for: 203.0.113.7` forwards on both /run and /connect): ``` ❯ header-utils.test.ts (19 tests | 8 failed) × strips known infra/proxy/platform headers by exact name → expected true to be false × strips known infra/platform header families by prefix → expected true to be false × strips denylisted headers case-insensitively → expected true to be false × deny extends the default set → expected true to be false × denyPrefixes extends the default set → expected true to be false × allow switches to allowlist mode → expected true to be false × extractForwardableHeaders drops denylisted x-* infra → expected {…4} to deeply equal {…1} ❯ agent-utils-header-forwarding.test.ts (/run) (10 tests | 1 failed) × strips denylisted infra/platform headers (#5712 breadth) → expected '203.0.113.7' to be undefined ❯ sse-connect-agent-id.test.ts (/connect) (5 tests | 1 failed) × strips denylisted infra/platform headers → expected '203.0.113.7' to be undefined ``` GREEN — with the real policy in place: ``` ✓ header-utils.test.ts (19 tests) ✓ agent-utils-header-forwarding.test.ts (10 tests) # /run path ✓ sse-connect-agent-id.test.ts (5 tests) # /connect path ✓ agent-header-precedence.test.ts (2 tests) Test Files 4 passed (4) Tests 36 passed (36) ``` Full `@copilotkit/runtime` suite: **113 files / 1593 tests passed.** Typecheck, oxlint (0 errors), oxfmt, and build all green. ## Builds on #5782 This branches off #5782's head (`636bcad05`) and reuses that PR's `mergeForwardableHeaders` (server-wins precedence + server-self case-dedup). It should land **after #5782**. It addresses the **forwarding-breadth half of #5712** — #5712's precedence core is fixed by #5782; this is the breadth follow-up (not `Fixes #5712`).