Skip to content

Proxy forwards hop-by-hop request headers to upstream (RFC 7230 §6.1) #791

@felixgabler

Description

@felixgabler

🐛 The bug

proxy-handler.ts forwards client request headers to the upstream target but never strips hop-by-hop headers. The request loop only skips host, SENSITIVE_HEADERS, and (conditionally) content-length:

// packages/script/src/runtime/server/proxy-handler.ts (main @ 90aced6ae)
const lowerKey = key.toLowerCase()
// host header — fetch derives it from URL
if (lowerKey === 'host')
  continue
// SENSITIVE_HEADERS always stripped regardless of privacy flags
if (SENSITIVE_HEADERS.includes(lowerKey))
  continue
if (lowerKey === 'content-length') { /* ... */ }

Hop-by-hop headers (connection, keep-alive, proxy-authenticate, proxy-authorization, te, trailer, transfer-encoding, upgrade) are connection-specific. Per RFC 7230 §6.1 a proxy must not forward them. Forwarding them onto the upstream fetch() can corrupt the upstream exchange (mis-framed bodies when a stale transfer-encoding/content-length rides along, broken keep-alive negotiation, dropped requests), and the failure mode varies by upstream and by the runtime's HTTP stack.

The response side already handles this via SKIP_RESPONSE_HEADERS (line 28), so the request side is just missing the symmetric filter.

I might be missing a reason these are intentionally forwarded. Happy to be corrected.

🛠️ To reproduce

Proxy any registry script (firstParty / proxy) from a client that sends hop-by-hop request headers. Browsers and in-app webviews routinely send Connection: keep-alive, and some clients send TE / Transfer-Encoding. Those headers reach the proxied fetch() and get forwarded upstream. Whether it surfaces depends on the upstream and the Nitro HTTP stack. In our case it disrupted Sentry proxying and a native (iOS webview) network signal until we stripped them.

🌈 Expected behavior

The proxy strips the standard hop-by-hop set on the request side, mirroring SKIP_RESPONSE_HEADERS:

const SKIP_REQUEST_HEADERS = new Set(['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer', 'transfer-encoding', 'upgrade'])

// in the request loop, right after the host check:
if (SKIP_REQUEST_HEADERS.has(lowerKey))
  continue

ℹ️ Additional context

We hit this proxying analytics and Sentry through the first-party proxy. Forwarding connection / transfer-encoding regressed Sentry proxying and a native network signal, and we are carrying exactly the filter above as a local patch today.

Happy to send a PR, since it mirrors the existing SKIP_RESPONSE_HEADERS pattern.

Environment: @nuxt/scripts@1.1.1 (also present on main @ 90aced6), Nitro on Vercel (Node 22).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions