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`).
CopilotKit - Runtime
✨ Why CopilotKit?
- Minutes to integrate - Get started quickly with our CLI
- Framework agnostic - Works with React, Next.js, AGUI and more
- Production-ready UI - Use customizable components or build with headless UI
- Built-in security - Prompt injection protection
- Open source - Full transparency and community-driven
🧑💻 Real life use cases
Deploy deeply-integrated AI assistants & agents that work alongside your users inside your applications.
🏆 Featured Examples
Documentation
To get started with CopilotKit, please check out the documentation.
Analytics & Privacy
CopilotKit uses Scarf for anonymous usage analytics to help improve the product. Scarf handles all privacy compliance and does not store raw IP addresses. This helps us understand how CopilotKit is being used and prioritize improvements.
Opting Out
To disable analytics, set the environment variable:
export COPILOTKIT_TELEMETRY_DISABLED=true
Or use the DO_NOT_TRACK standard:
export DO_NOT_TRACK=1