fix(miniflare): prevent fetch failed on POST 401 responses#13328
fix(miniflare): prevent fetch failed on POST 401 responses#13328assimelha wants to merge 2 commits intocloudflare:mainfrom
Conversation
When a Worker returns a 401 response to a POST request, undici Fetch spec implementation (step 14) triggers HTTP authentication retry logic. It attempts to re-extract the request body source for re-sending with credentials, but workerd uses streamed request bodies where body.source is null. This causes undici to throw a network error: "expected non-null body source", surfacing as "TypeError: fetch failed". Fix: Set credentials to "omit" in miniflare fetch wrapper when calling undici.fetch(). This bypasses the 401 authentication retry entirely. Miniflare dispatches to the workerd runtime directly - there is no HTTP authentication scheme to negotiate, so this code path should never trigger. Verified: the fix resolves POST 401 crashes while preserving correct behavior for POST 200, POST 400, POST 403, POST 500, and GET 401. Fixes cloudflare#13327
🦋 Changeset detectedLatest commit: 39dedbb The changes in this PR will be included in the next version bump. 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
|
|
/bonk review this PR |
There was a problem hiding this comment.
Review Summary
The PR correctly identifies the root cause of POST 401 failures in miniflare — undici's fetch spec implementation tries to re-extract the request body for credential re-authentication on 401 responses, but workerd's streamed bodies have body.source === null, causing a crash.
However, this bug is already fixed on main by the existing undici patch at patches/undici@7.24.4.patch, which sets isTraversableNavigable() to return false. This prevents the 401 credential-retry code path from ever executing, regardless of the credentials setting. The patch was introduced in commit 65e6684 (PR #13095) and references the same upstream issues (#12967, nodejs/undici#4910).
The credentials: "omit" changes in this PR would serve as defense-in-depth, which isn't a bad thing in principle. But there are a few concerns:
-
Redundancy between the two locations. The
credentials: "omit"set inindex.tsflows through tofetch.tsvia theRequestInit, and thenfetch.tsalso hardcodescredentials: "omit"in theundici.fetch()call. Either one alone would suffice — having both is confusing without an explanation of why both exist. -
The
fetch.tschange affects all callers, not justdispatchFetch. Thefetch()function fromhttp/fetch.tsis also used fromplugins/core/index.tsforfetchMockoutbound services. Settingcredentials: "omit"globally here changes behavior for all fetch calls going through miniflare's wrapper, not just the workerd dispatch path. While this is likely harmless (miniflare is not a browser and has no credential store), it deserves consideration. -
No tests. The PR has no test to verify the fix or prevent regression. A test in
test/http/fetch.spec.tsthat makes a POST request receiving a 401 response would be valuable, especially since this is a bug fix. The miniflare AGENTS.md notes test files use.spec.tsnaming. -
The changeset description uses a conventional commit prefix (
fix(miniflare): ...) which is acceptable per.changeset/README.md, but the body could be more concise.
I'd suggest either:
- Option A: Close this PR, since the undici patch already resolves the issue.
- Option B: If maintainers want defense-in-depth, pick only one of the two locations (I'd suggest
fetch.tssince it covers all call sites), and add a test.
cc @cloudflare/wrangler for guidance on whether the defense-in-depth approach is desired given the existing patch.
| // the 401 handling entirely (Fetch spec §4.4 step 14). This is safe | ||
| // because miniflare dispatches to workerd directly — there is no HTTP | ||
| // authentication to retry. | ||
| credentials: "omit", |
There was a problem hiding this comment.
This change makes credentials: "omit" apply to all callers of this fetch() wrapper — not just dispatchFetch(). For instance, plugins/core/index.ts uses it for fetchMock outbound services:
value.outboundService = (req) => fetch(req, { dispatcher: fetchMock });While likely harmless (miniflare has no credential store), this is a broader behavioral change than the PR description suggests. Worth noting explicitly.
Also, this fix is redundant with the existing undici patch at patches/undici@7.24.4.patch which already sets isTraversableNavigable() to return false, preventing the 401 credential retry code path from ever executing.
| // (body.source === null), this causes a "fetch failed" network error | ||
| // with "expected non-null body source". Setting credentials to "omit" | ||
| // bypasses the 401 handling entirely (see Fetch spec step 14). | ||
| forwardInit.credentials = "omit"; |
There was a problem hiding this comment.
This is redundant with the credentials: "omit" added in fetch.ts (line 94). The flow is:
dispatchFetchsetsforwardInit.credentials = "omit"herefetch()infetch.tscreatesnew Request(input, requestInit)— undici's constructor preservescredentials: "omit"from initundici.fetch(request, { dispatcher, credentials: "omit" })creates a new internal Request, overriding credentials again
Both set credentials: "omit" on the final internal request that undici's fetch algorithm reads, so either one alone suffices. If both are kept, a comment should explain why (e.g., "also set here in case fetch.ts wrapper is bypassed in the future").
|
|
||
| fix(miniflare): prevent fetch failed on POST 401 responses | ||
|
|
||
| Set `credentials: "omit"` in miniflare's fetch wrapper to bypass undici's HTTP 401 authentication retry logic. Workerd uses streamed request bodies where `body.source` is null, causing undici to throw "expected non-null body source" on POST 401 responses. |
There was a problem hiding this comment.
Minor: per .changeset/README.md, the body should explain "the reason for the change and anything notable about the approach" in a way useful for changelog readers. Consider simplifying to focus on user-facing impact:
| Set `credentials: "omit"` in miniflare's fetch wrapper to bypass undici's HTTP 401 authentication retry logic. Workerd uses streamed request bodies where `body.source` is null, causing undici to throw "expected non-null body source" on POST 401 responses. | |
| Set `credentials: "omit"` in miniflare's fetch wrapper to prevent POST requests returning HTTP 401 from crashing with `TypeError: fetch failed` during local development. |
|
Review submitted successfully. Here's a summary of what I found: Key finding: The bug this PR fixes (POST 401 crash in miniflare) is already resolved on The PR's
I suggested the maintainers either close the PR (since the patch already fixes it) or, if they want defense-in-depth, keep only one location and add a test. |
|
Has this issue been completely fixed? |
|
Thanks for offering this fix @assimelha. There is a fix coming in Undici... nodejs/undici#4941. I am concerned that this PR is a workaround that might impact other aspects of how Miniflare forwards requests to Workers - adding userland headers to requests can break user's expectations. |
Summary
POST requests returning HTTP 401 from Workers crash miniflare undici proxy with
TypeError: fetch failedin dev mode. GET 401, POST 400, POST 403, and POST 500 all work fine.Root cause
Undici Fetch spec implementation (step 14) has special handling for 401 responses: it tries to re-extract the request body source for credential re-authentication. But workerd uses streamed request bodies where
body.sourceisnull. This causes undici to throw:...which surfaces as
TypeError: fetch failedatprocessResponse.Fix
Set
credentials: "omit"in miniflarefetch()wrapper when callingundici.fetch(). This bypasses the 401 authentication retry logic entirely. Miniflare dispatches to the workerd runtime directly — there is no HTTP authentication scheme to negotiate, so this code path should never trigger.Testing
Verified locally by patching the compiled miniflare dist:
Impact
This affects any auth library (better-auth, lucia, etc.) running on Cloudflare Workers in dev mode where login with wrong credentials returns 401.
Fixes #13327