Files
cloudflare__vinext/tests/http2-pseudo-headers.test.ts
T
Xplod13 2b67523e9e fix(server): strip HTTP/2 pseudo-headers before building Headers (#2021)
## Summary
- Drop RFC 7540 §8.1.2.1 pseudo-headers (`:method`, `:authority`, `:path`,
  `:scheme`) before constructing WHATWG `Headers` from Node's `req.headers`.
- Fixed at every `Headers`-from-`req.headers` site: the Pages Router
  middleware snapshot (index.ts), the edge API request builder
  (api-handler.ts), and the shared `appendWebHeader` helper used by
  `nodeToWebRequest` (prod-server.ts).
- Also excluded pseudo-headers from the `reportRequestError` header metadata
  in both api-handler.ts and dev-server.ts so error reports reflect real
  request headers only.
- Added a regression unit test driving `nodeToWebRequest` with a fake HTTP/2
  request.

## Root Cause
Over HTTP/2, Node's `http2` server exposes the request pseudo-headers as
`:`-prefixed keys on `req.headers`. The WHATWG `Headers`
constructor/`append`/`set` reject any header name containing `:`
(`TypeError: Headers.set: ":method" is an invalid header name`), so building a
`Headers` object directly from `req.headers` threw and returned a 500 on every
HTTP/2 request. Per RFC 7540 §8.1.2.1, pseudo-headers must not be treated as
regular header fields, so they are stripped at each construction site.

## References
- Issue: cloudflare/vinext#2013
- RFC 7540 §8.1.2.1 (HTTP/2 pseudo-header fields)
- Node `http2` request `:`-prefixed headers; WHATWG Fetch `Headers` name validation

## Verification
- `node -e "new Headers({':method':'GET'})"` → throws (repro).
- `CI=true pnpm test tests/http2-pseudo-headers.test.ts` → red before fix,
  green after.
- `CI=true npx vp check` on all changed files → pass (format + lint + types).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 23:34:22 +01:00

48 lines
1.6 KiB
TypeScript

import type { IncomingMessage } from "node:http";
import { describe, expect, it } from "vitest";
import { nodeToWebRequest } from "../packages/vinext/src/server/prod-server.js";
/**
* Regression test for #2013.
*
* When a request arrives over HTTP/2, Node populates `req.headers` with
* RFC 7540 §8.1.2.1 pseudo-headers (`:method`, `:authority`, `:path`,
* `:scheme`). The WHATWG `Headers` constructor/append/set rejects any header
* name containing `:`, so building a `Headers` object directly from
* `req.headers` threw `TypeError: ... is an invalid header name` and returned
* a 500 on every HTTP/2 request. Pseudo-headers must be stripped before a
* `Headers` object is constructed.
*/
describe("HTTP/2 pseudo-header stripping (#2013)", () => {
function fakeHttp2Request(): IncomingMessage {
return {
method: "GET",
url: "/",
headers: {
":method": "GET",
":authority": "example.com",
":path": "/",
":scheme": "https",
host: "example.com",
"user-agent": "vitest",
},
} as unknown as IncomingMessage;
}
it("does not throw and strips pseudo-headers while keeping real headers", () => {
const req = fakeHttp2Request();
let request!: Request;
expect(() => {
request = nodeToWebRequest(req, "/");
}).not.toThrow();
// Pseudo-headers must be absent. (Note: `Headers.has(":method")` would
// itself throw, so enumerate the names instead.)
const names = [...request.headers.keys()];
expect(names.some((n) => n.startsWith(":"))).toBe(false);
// Real headers must be preserved.
expect(request.headers.get("user-agent")).toBe("vitest");
});
});