Skip to content

fix(vite-plugin): preserve the request authority under HTTP/2 - #15533

Open
vahidshaik1901 wants to merge 1 commit into
cloudflare:mainfrom
vahidshaik1901:fix/http2-authority-host-resolution
Open

fix(vite-plugin): preserve the request authority under HTTP/2#15533
vahidshaik1901 wants to merge 1 commit into
cloudflare:mainfrom
vahidshaik1901:fix/http2-authority-host-resolution

Conversation

@vahidshaik1901

@vahidshaik1901 vahidshaik1901 commented Sep 6, 2026

Copy link
Copy Markdown

Fixes #14931.

Browsers negotiate HTTP/2 whenever server.https is enabled, and HTTP/2 carries the authority in the :authority pseudo-header rather than in Host. createHeaders() from @remix-run/node-fetch-server skips every :-prefixed pseudo-header:

for (let [key, value] of Object.entries(req.headers)) {
  if (key.startsWith(':') || value == null) continue;

So Host was absent from the parsed headers and createRequestForIncomingMessage() fell through to the literal "localhost", dropping both the host and the port. A Worker served from vite dev --https on port 5173 saw https://localhost/ instead of https://localhost:5173/.

toMiniflareRequest() read that same missing Host header, so X-Forwarded-Host was never set either. Auth libraries that rebuild redirect URLs from request.url or the forwarded headers — Clerk's handshake flow in the linked issue — redirected to the wrong origin and looped.

Changes

  • Added getRequestHost(), which resolves the authority from Host first and :authority second, mirroring the existing getForwardedProto() helper. It returns undefined when neither is present so the caller keeps its own "localhost" fallback.
  • createRequestForIncomingMessage() now resolves the host through that helper. HTTP/1.1 behaviour is unchanged, since Host is still preferred.
  • toMiniflareRequest() falls back to the host of the already-resolved request URL when there is no Host header, so X-Forwarded-Host is set under HTTP/2.

Testing

Added two tests driving a cleartext (h2c) HTTP/2 server, which exercises the same code path without requiring TLS certificates, following the existing createRequestHandler test pattern. Before the change they fail with:

expected 'http://localhost/path' to be 'http://127.0.0.1:61392/path'
expected null to be '127.0.0.1:61388'

All 199 tests across the 21 unit test files in packages/vite-plugin-cloudflare pass with the change. check:type and oxlint --deny-warnings are clean.


  • Tests
    • Tests included/updated
  • Public documentation
    • Documentation not necessary because: this restores the documented behaviour of request.url and X-Forwarded-Host under HTTPS; no public API changes.

Devin Review

Browsers negotiate HTTP/2 whenever `server.https` is enabled, and HTTP/2
carries the authority in the `:authority` pseudo-header rather than in `Host`.
`createHeaders()` from `@remix-run/node-fetch-server` skips every `:`-prefixed
pseudo-header, so `Host` was absent from the parsed headers and
`createRequestForIncomingMessage()` fell through to the literal `"localhost"`,
dropping both the host and the port.

A Worker served from `vite dev --https` on port 5173 therefore saw
`https://localhost/` rather than `https://localhost:5173/`. `toMiniflareRequest()`
read the same missing `Host` header, so `X-Forwarded-Host` was never set either.
Auth libraries that rebuild redirect URLs from `request.url` or the forwarded
headers, such as Clerk's handshake flow, redirected to the wrong origin and
looped.

Resolve the authority from `Host` first and `:authority` second via a new
`getRequestHost()` helper, mirroring the existing `getForwardedProto()`, and
fall back to the host of the already-resolved request URL when setting
`X-Forwarded-Host`.

Covered by tests against a cleartext HTTP/2 server, which exercises the same
code path without requiring TLS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018W34ieE3NNGaRQBxj5CKkH
@changeset-bot

changeset-bot Bot commented Sep 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b520dbb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/vite-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/http2-authority-host-resolution.md: [@cloudflare/wrangler]
  • packages/vite-plugin-cloudflare/src/tests/utils.spec.ts: [@cloudflare/wrangler]
  • packages/vite-plugin-cloudflare/src/utils.ts: [@cloudflare/wrangler]

@devin-ai-integration devin-ai-integration Bot 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@pkg-pr-new

pkg-pr-new Bot commented Sep 6, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@15533

@cloudflare/build-output-utils

npm i https://pkg.pr.new/@cloudflare/build-output-utils@15533

@cloudflare/codemods

npm i https://pkg.pr.new/@cloudflare/codemods@15533

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@15533

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@15533

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@15533

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@15533

miniflare

npm i https://pkg.pr.new/miniflare@15533

@cloudflare/pages-functions

npm i https://pkg.pr.new/@cloudflare/pages-functions@15533

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@15533

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@15533

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@15533

@cloudflare/vitest-plugin

npm i https://pkg.pr.new/@cloudflare/vitest-plugin@15533

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@15533

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@15533

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@15533

wrangler

npm i https://pkg.pr.new/wrangler@15533

commit: b520dbb

@vahidshaik1901

Copy link
Copy Markdown
Author

Three checks are red and I do not believe any of them relate to this change — details below so a reviewer does not have to dig. Happy to rebase if you would rather just see a clean run.

This PR touches three files, all within packages/vite-plugin-cloudflare:

  • packages/vite-plugin-cloudflare/src/utils.ts
  • packages/vite-plugin-cloudflare/src/__tests__/utils.spec.ts
  • .changeset/http2-authority-host-resolution.md

Vite Plugin Playground (ubuntu-latest, vite-8) — the suite never ran. The beforeAll in vitest-setup.ts:91 timed out starting the dev server:

Error: Hook timed out in 50000ms.
 ❯ vitest-setup.ts:91:1
Test Files  1 failed | 95 passed | 5 skipped (101)

No assertion failed; react-spa/__tests__/experimental-headers-and-redirects was reported as a failed suite, not a failed test.

Tests (Windows, packages-and-tools) — a miniflare test, in a package this PR does not touch:

FAIL test/dev-registry.spec.ts > DevRegistry ...
AssertionError:
- [dev-registry] Failed to forward tail events to "remote-worker"
+ tail me
Test Files  1 failed | 85 passed (86)

That looks like a tail-event forwarding race rather than anything host-related.

Tests (Windows, fixtures)@fixture/start-worker-node exits with ERR_TEST_FAILURE, again outside the changed package.

Locally, all 199 tests across the 21 unit test files in packages/vite-plugin-cloudflare pass, and check:type plus oxlint --deny-warnings are clean.

@vahidshaik1901

Copy link
Copy Markdown
Author

Flagging an overlap I should have caught before opening this: #15519 fixes the same issue
(#14931) and was opened a day earlier. Reviewers should not have to discover that
themselves.

The two take different routes. #15519 extracts getAuthority and getScheme as exported
helpers and unit-tests them directly, and also carries the :scheme handling. This PR
leaves the helpers private and instead drives a real node:http2 server through
createRequestHandler, asserting on request.url and X-Forwarded-Host as the Worker
actually sees them — so it covers the whole path rather than the extracted pieces.

I have no attachment to which one lands. #15519 got there first and is the broader change;
happy to close this in its favour, and to re-send the HTTP/2 server test as a follow-up on
top of it if that end-to-end coverage looks useful. Just say which you prefer.

@vahidshaik1901

Copy link
Copy Markdown
Author

Correction to the note above: #15519 was not the first PR for #14931. #14933 (@intrdx) has been open since 2026-07-30 and has had a maintainer approval since 2026-08-03, and #15237 is open for the same issue as well. I missed both when I checked, which is on me.

@intrdx has offered on #14933 to fold the getRequestHost() helper and the h2c tests from this PR into that branch. That is the right outcome. I will close this PR as soon as #14933 carries the change.

intrdx added a commit to intrdx/workers-sdk that referenced this pull request Sep 6, 2026
Adopt createRequestForIncomingMessage host resolution and toMiniflareRequest URL-host fallback from cloudflare#15533, replacing the outer Host force-set. Cover the :authority path with cleartext HTTP/2 tests.

Co-authored-by: SHAIK VAHID <38548782+vahidshaik1901@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

@cloudflare/vite-plugin: HTTPS/HTTP2 Host/:authority handling drops non-default port from request.url (breaks Clerk handshake redirects)

2 participants