## 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`).
Shell Docs
showcase/shell-docs is the Next.js app that builds and serves
docs.copilotkit.ai. Author CopilotKit product documentation here, not in the retired
top-level docs/ app.
Run Locally
Shell-docs is a standalone npm-based app. You do not need a root install just to run the docs app locally.
cd showcase/scripts
npm install
cd ../shell-docs
npm install
npm run dev
The local dev server runs on port 3003.
http://localhost:3003
The shell-docs npm lifecycle generates registry, demo-content, setup-content, and search
data before dev, build, and typecheck.
Validate Changes
Run these from showcase/shell-docs:
npm run build
npm run typecheck
npm run test
For repo-level CI parity, prefer Nx when a shell-docs target is available in the current checkout and root dependencies are installed. For normal shell-docs local development, the npm commands above are the canonical path.
Authoring Recipes
Showcase-Driven Framework Docs
Showcase-driven frameworks use docs_mode: generated. The docs are assembled from showcase
registry/generated data, demos, source regions, shared/root MDX, snippets, and sparse
framework overrides.
To update showcase-driven docs:
- Edit the showcase source of truth: manifests, demos, feature coverage, source regions, or registry inputs.
- Edit shared/root MDX only when the change applies across generated frameworks.
- Add sparse framework overrides only for real framework-specific differences.
- Do not hand-edit generated files under
src/data/frameworks/. - Validate routes, sidebar state, search results, snippets, and framework switching.
Authored Framework Docs
Authored frameworks use docs_mode: authored. The framework owns an MDX tree under
src/content/docs/integrations/<docsFolder>/ with a meta.json sidebar.
To update authored docs:
- Check
getDocsFolder()insrc/lib/registry.ts; the URL slug and folder name may differ. - Edit the MDX page under
src/content/docs/integrations/<docsFolder>/. - Update that folder's
meta.jsonwhen adding, removing, or moving pages. - Reuse shared snippets from
src/content/snippets/when content should stay consistent across frameworks. - Validate the framework route, sidebar, search result, and any shared snippet render.
Reference Docs
Edit API reference pages under src/content/reference/.
The v2 reference does not use meta.json; navigation is generated by walking the tree and
reading each page's title and description frontmatter. Only the legacy reference/v1/
tree uses meta.json.
Snippets
Reusable snippets live under src/content/snippets/. Snippets may be rendered by root docs,
authored framework pages, and showcase-driven framework pages, so keep them general unless
the path is intentionally framework-specific.
Frontend Applicability
Frontend routes use page-level applicability metadata, independent from where the content is authored. A page can be authored MDX, showcase-generated content, mirrored protocol docs, or reference content and still be universal across frontends.
Use the frontend field in page frontmatter or meta.json when a root doc should appear in
non-React frontend docs:
universal— render the same page under/<frontend>/....frontend-variant— render only when a matching page exists undersrc/content/docs/frontends/<frontend>/....hide— omit the page from frontend-scoped docs.
Do not use "showcase-driven" as a proxy for frontend availability. Showcase derivation is an authoring/source detail; frontend applicability controls routing and sidebar inclusion.
AG-UI Mirrored Docs
AG-UI protocol docs are authored upstream in ag-ui-protocol/ag-ui. The
src/content/ag-ui/ tree is a downstream mirror rendered on the CopilotKit docs host.
Change AG-UI docs upstream first, then sync the mirror back into shell-docs.
Top-Level Docs Symlink
The repository's top-level docs/ path is a symlink to showcase/shell-docs/ for
contributor muscle memory. It is not a separate docs app. Do not recreate the old
docs/content/docs/ tree; author CopilotKit docs in showcase/shell-docs/src/content/.