Skip to content

fix(http): allow projected MCP headers in preflights - #3167

Open
SamMorrowDrums wants to merge 2 commits into
mainfrom
sammorrowdrums-allow-mcp-parameter-preflights
Open

fix(http): allow projected MCP headers in preflights#3167
SamMorrowDrums wants to merge 2 commits into
mainfrom
sammorrowdrums-allow-mcp-parameter-preflights

Conversation

@SamMorrowDrums

Copy link
Copy Markdown
Collaborator

Summary

  • extend the browser CORS allowlist from fix(http): preserve CORS across OAuth routes #3147 with MCP 2026-07-28 Mcp-Method and Mcp-Name request headers plus the current Mcp-Param-owner and Mcp-Param-repo projections
  • inspect every Access-Control-Request-Headers value on OPTIONS and reflect only valid, non-empty Mcp-Param-* field names
  • deduplicate projected names case-insensitively, sort them deterministically, and emit canonical header casing without exposing request parameters as response headers
  • preserve the existing wildcard-origin/no-credentials policy, fixed allowlist, exposed headers, downstream headers, and non-OPTIONS behavior

Exact projected-header contract

CORS has no prefix wildcard, so preflights validate each requested name with httpguts.ValidHeaderFieldName. Matching is case-insensitive against the exact Mcp-Param- prefix and requires a non-empty suffix. Fixed owner/repo entries remain first; future names are case-insensitively deduplicated, sorted, and emitted with http.CanonicalHeaderKey. Arbitrary headers, bare Mcp-Param-, malformed/control-character names, and lookalike prefixes are omitted.

Mcp-Param-* headers are request-only and are not added to Access-Control-Expose-Headers; Mcp-Session-Id and WWW-Authenticate remain exposed.

Cross-repository dependencies

  • Follow-up to merged fix(http): preserve CORS across OAuth routes #3147 and the v1.11.0 direct/self-hosted HTTP contract.
  • github/github-mcp-server-remote#955 must consume this commit or a release containing it for direct remote traffic; its stable dependency blocker remains until this change is merged and released.
  • github/copilot-api#37229 separately owns matching hosted-edge preflight behavior.

Validation

  • script/lint
  • script/test

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 27, 2026 09:55
@SamMorrowDrums
SamMorrowDrums requested a review from a team as a code owner August 27, 2026 09:55
Auto-generated by license-check workflow

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Extends browser CORS preflight support for MCP routing and projected parameter headers.

Changes:

  • Adds fixed MCP method, name, owner, and repo headers.
  • Validates, deduplicates, sorts, and reflects future Mcp-Param-* headers.
  • Expands middleware and router-level CORS tests.
Show a summary per file
File Description
pkg/http/server_test.go Tests projected headers through the full router.
pkg/http/middleware/cors.go Implements projected-header preflight handling.
pkg/http/middleware/cors_test.go Covers validation, ordering, deduplication, and preserved behavior.
pkg/http/headers/headers.go Defines MCP projection header constants.
go.mod Promotes x/net to a direct dependency.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 8/9 changed files
  • Comments generated: 0
  • Review effort level: Balanced

Comment on lines +66 to +84
for _, value := range requestHeaders.Values("Access-Control-Request-Headers") {
for header := range strings.SplitSeq(value, ",") {
header = strings.TrimSpace(header)
if !httpguts.ValidHeaderFieldName(header) {
continue
}

key := strings.ToLower(header)
if !strings.HasPrefix(key, prefix) || len(key) == len(prefix) {
continue
}
if _, ok := seen[key]; ok {
continue
}

seen[key] = struct{}{}
projected = append(projected, key)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no cap on how many projected names we reflect here. Within Go's 1MB request header limit, a client can send thousands of distinct Mcp-Param-x names on a preflight and get every one of them echoed back, growing both seen and the response header. That's a cheap request producing a large response — a small amplification vector.

Could we bound this? Stopping after something like 32–64 projected names would close it off at no practical cost, since real clients will only ever send a handful.

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.

3 participants