[wrangler] fix: propagate zone from unstable_getMiniflareWorkerOptions so vite dev outbound CF-Worker reflects dev.host#13919
Conversation
🦋 Changeset detectedLatest commit: 6bcea69 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
|
Codeowners approval required for this PR:
Show detailed file reviewers |
|
✅ All changesets look good |
|
Good — I've completed a thorough review. Let me summarize my findings: The fix is correct and well-tested. The There are no logic bugs, security issues, backward compatibility violations, or incorrect API behavior. LGTM |
create-cloudflare
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
commit: |
There was a problem hiding this comment.
We shouldn't be using the dev options from the Wrangler config in the Vite plugin. Can we reduce the scope of this PR so that it handles the inference from the routes without providing a way to override? We can then follow up with new options in the plugin config to handle the override.
jamesopstad
left a comment
There was a problem hiding this comment.
Looks good. Just a question over naming (zone vs hostname). The PR title and description also need updating.
| describe("unstable_getMiniflareWorkerOptions", () => { | ||
| runInTempDir(); | ||
|
|
||
| describe("zone derivation (used for the outbound CF-Worker header)", () => { |
There was a problem hiding this comment.
Is zone the right word in this block or should it be hostname? Couldn't a single zone have multiple possible hostnames because of subdomains?
There was a problem hiding this comment.
I had to go and look up exactly what CF-Worker is supposed to contain in production...
From our docs...
It is set to the name of the zone which owns the Worker making the subrequest. For example, a Worker script on route for foo.example.com/* from example.com will have all subrequests with the header:
CF-Worker: example.com
The intended purpose of this header is to provide a means for recipients (for example, origins, load balancers, other Workers) to recognize, filter, and route traffic generated by Workers on specific zones.
It appears this is explicitly not the hostname of the request but the name of the zone. So I am now not sure if the logic for the zone guessing is quite right. I need to go and think about this a bit more.
There was a problem hiding this comment.
I have reworked the logic for selecting the zone from the routes (and in wrangler dev the dev.host config).
As mentioned from the docs the zone is not always the hostname, so this approach fixes that (or at least doesn't make it any worse where it is ambiguous).
CF-Worker header behaviour matrix
Reference: Cloudflare HTTP headers — CF-Worker — production sets CF-Worker to the zone name that owns the Worker.
Legend
<wk>=<worker-name>.example.com(Miniflare's default when no zone is supplied)- ✅ matches production
⚠️ best-effort (correct value requires an API lookup, so it's not knowable locally without one)- 🔧 changed by this PR
- 💥 throws at startup (pre-existing behaviour from
getInferredHost)
Only the first configured route is consulted in both code paths.
wrangler dev --local
| Wrangler config | Before | After | Per docs |
|---|---|---|---|
dev.host: "x.example.com" only |
x.example.com |
x.example.com |
n/a (dev.host is a local override) |
route: "example.com/*" |
example.com ✅ |
example.com ✅ |
example.com |
route: "foo.example.com/*" |
foo.example.com |
foo.example.com |
example.com |
{ pattern: "foo.example.com/*", zone_name: "example.com" } |
foo.example.com |
example.com ✅ 🔧 |
example.com |
{ pattern: "foo.example.com/*", zone_id: "abc" } |
foo.example.com |
foo.example.com |
example.com |
{ pattern: "*/*", zone_name: "example.com" } |
example.com ✅ |
example.com ✅ |
example.com |
{ pattern: "*/*", zone_id: "abc" } (no dev.host) |
💥 | 💥 | example.com |
dev.host + { pattern: "foo.example.com/*", zone_name: "example.com" } |
dev.host |
example.com ✅ 🔧 |
example.com |
dev.host + { pattern: "foo.example.com/*", zone_id: "abc" } |
dev.host |
dev.host |
example.com |
| nothing configured | <wk> |
<wk> |
n/a |
@cloudflare/vite-plugin · @cloudflare/vitest-pool-workers · getPlatformProxy
These paths intentionally ignore dev.host — the dev config block is specific to wrangler dev. Users who need a custom CF-Worker host in these environments should configure a route instead.
| Wrangler config | Before | After | Per docs |
|---|---|---|---|
dev.host: "x.example.com" only |
<wk> |
<wk> |
n/a (dev.host not consulted) |
route: "example.com/*" |
<wk> |
example.com ✅ 🔧 |
example.com |
route: "foo.example.com/*" |
<wk> |
foo.example.com |
example.com |
{ pattern: "foo.example.com/*", zone_name: "example.com" } |
<wk> |
example.com ✅ 🔧 |
example.com |
{ pattern: "foo.example.com/*", zone_id: "abc" } |
<wk> |
foo.example.com |
example.com |
{ pattern: "*/*", zone_name: "example.com" } |
<wk> |
example.com ✅ 🔧 |
example.com |
{ pattern: "*/*", zone_id: "abc" } |
<wk> |
<wk> |
example.com |
| nothing configured | <wk> |
<wk> |
n/a |
Notes on the ⚠️ rows
When a route uses zone_id, a custom_domain, or a plain-string SimpleRoute on a subdomain (e.g. foo.example.com/*), determining the parent zone (example.com) requires resolving it server-side. We don't do that at dev time, so we fall back to the pattern's hostname as a best-effort approximation. To get the production-correct header locally, declare the zone explicitly via zone_name.
workers-devprod
left a comment
There was a problem hiding this comment.
Codeowners reviews satisfied
…ions` so `vite dev` outbound `CF-Worker` reflects `dev.host` Under `vite dev` and `vitest-pool-workers`, outbound subrequests sent the `CF-Worker` header as `<worker-name>.example.com` even when `dev.host` or `routes` were configured, because the worker options returned by `unstable_getMiniflareWorkerOptions` (and the equivalent `getPlatformProxy` path) never set `zone`. Miniflare then fell back to its default. `wrangler dev --local` was unaffected because it builds worker options through a separate path that already sets `zone`. Derive `zone` from the normalized config the same way `wrangler dev` does — prefer `dev.host`, otherwise fall back to the hostname of the first configured route via `getHostFromRoute`. Downstream consumers pick this up automatically with no API changes. Fixes #13791.
The dev config block is intended for wrangler dev. Drop the dev.host branch from getZoneFromConfig so getPlatformProxy and unstable_getMiniflareWorkerOptions only derive the outbound CF-Worker zone from configured routes. Update tests, playground fixture, and the changeset to match the reduced scope.
Co-authored-by: James Opstad <13586373+jamesopstad@users.noreply.github.com>
…orker zone Per https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-worker, production sets the outbound `CF-Worker` header to the *zone name* that owns the Worker — for a route on `foo.example.com/*` from zone `example.com`, the header is `example.com`, not `foo.example.com`. Previously, both `wrangler dev --local` and the new `unstable_getMiniflareWorkerOptions` / `getPlatformProxy` paths derived the zone via `getHostFromRoute`, which returns the pattern hostname. That's correct when the pattern hostname is the zone apex, but wrong when the user has explicitly configured a route with `zone_name: "example.com"` for a subdomain pattern. Introduce a shared `getZoneFromRoute` helper that prefers the route's `zone_name` field when present and falls back to `getHostFromRoute` otherwise (still a best-effort approximation when only `zone_id`, a string pattern, or a `custom_domain` route is configured — resolving the parent zone would require an API lookup). Use it in both the new `getZoneFromConfig` (vite-plugin / vitest-pool-workers / getPlatformProxy) and `wrangler dev`'s `resolveDev`, wiring the latter through a new `StartDevWorkerOptions["dev"].zone` field so the controller no longer aliases the CF-Worker zone to `origin.hostname` (which legitimately remains the pattern hostname for `request.url` purposes).
b606848 to
1bfd0c4
Compare
Compute the outbound `CF-Worker` zone inline in `LocalRuntimeController.convertToConfigBundle` rather than threading a new `dev.zone` field through `StartDevWorkerOptions`. The rule: prefer the first route's explicit `zone_name` (the user's unambiguous declaration of the parent zone, matching production), and otherwise fall back to `dev.origin.hostname` (which already collapses `dev.host` and the inferred-from-route-pattern fallback). This avoids adding a new public-ish API surface while still applying the zone_name fix to `wrangler dev --local` for consistency with the new `unstable_getMiniflareWorkerOptions` / `getPlatformProxy` behaviour.
…comment to JSDoc Adds a getZoneFromRoute test case covering when the route pattern can't be parsed as a hostname AND there's no zone_name to fall back on — the helper returns undefined, leaving Miniflare to apply its default <worker-name>.example.com header. Also moves the explanatory comment from the getZoneForCfWorkerHeader call site into a JSDoc on the function itself.
|
@jamesopstad - please can you take another look? |
Fixes #13791.
Under
vite devand@cloudflare/vitest-pool-workers, outbound subrequests sent theCF-Workerheader as<worker-name>.example.comeven whendev.hostorrouteswere configured. This broke local development against services that reject unknownCF-Workerhosts — the issue reporter hit it against Apple WeatherKit (403 Forbidden).wrangler dev --localwas unaffected because it builds Miniflare worker options through a separate path that already setszone.The root cause was that
unstable_getMiniflareWorkerOptions(and the equivalentgetPlatformProxyworker-options path inpackages/wrangler/src/api/integrations/platform/index.ts) never propagatedzone, so Miniflare fell back to its${workerName}.example.comdefault when constructing theCF-Workerheader on outbound fetches.The fix derives
zonefrom the normalized config the same waywrangler devdoes — preferdev.host, otherwise fall back to the hostname of the first configured route viagetHostFromRoute— and sets it on both worker options paths.@cloudflare/vite-pluginand@cloudflare/vitest-pool-workerspick this up automatically with no API changes.Tests added at two layers:
packages/wrangler/src/__tests__/unstable-get-miniflare-worker-options.test.ts) covering the derivation cases:dev.hostset, singleroutestring,routesarray, unparseable*/*falling back tozone_name, both set (dev.hostwins), and neither set (undefined).cron-triggersplayground that runs a side Node HTTP echo server, hits a new/cf-worker-headerendpoint in the Worker, and asserts the outboundCF-Workerheader matches the configureddev.host. Verified to fail with the exact pre-fix symptom (cron-trigger-worker.example.com) when the fix is reverted.wrangler dev --local, which already matches the production behaviour that is documented elsewhere.