chore(deps): bump undici from 7.28.0 to 8.9.0 - #1287
Conversation
Bumps [undici](https://github.com/nodejs/undici) from 7.28.0 to 8.9.0. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](nodejs/undici@v7.28.0...v8.9.0) --- updated-dependencies: - dependency-name: undici dependency-version: 8.9.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
|
Found 126 test failures on Blacksmith runners: Failures
|
There was a problem hiding this comment.
Deferring — the inline finding here is significant (a major-version dependency bump that appears to break global fetch() dispatcher usage at three call sites: CDN purge, webhook delivery, and imgproxy rendering), so this should not be shadow-approved despite being a routine-looking dependency bump.
Extended reasoning...
Overview
This PR is an automated Dependabot bump of undici from ^7.24.0 to ^8.9.0, touching only package.json and package-lock.json. No application code is modified in the diff itself.
Security risks
The undici 8.9.0 release includes several genuine security fixes (Cache-Control parsing, cookie validation, CRLF header injection hardening) that would be beneficial. However, the bug hunting system identified a serious compatibility regression: this repo has three call sites (src/storage/renderer/image.ts, src/storage/cdn/cdn-cache-manager.ts, src/storage/events/lifecycle/webhook.ts) that construct an undici.Agent from the npm package and pass it via the dispatcher option to Node's global fetch(), rather than using undici.fetch/undici.request directly. I confirmed this pattern exists in the current codebase at all three locations. Node's global fetch is backed by its own internally bundled (older) undici, and v8's stricter internal handler-interface assertions are reported to reject the handler objects Node's fetch constructs — which would break CDN purges and webhook delivery outright and cause imgproxy requests to hang until timeout.
Level of scrutiny
Although this class of change (a dependency version bump) is often mechanical and safe to shadow-approve, a major-version bump of a dependency with this kind of runtime-behavior-changing implication needs a human to confirm/test before merging, since it risks breaking production request paths that don't have direct unit test coverage for this cross-package interaction.
Other factors
This is a fresh, single-commit dependabot PR with no prior review activity from me. Given the potential production-impacting nature of the finding, I'm deferring rather than approving, even though the diff itself is textually trivial.
| "pprof-format": "^2.2.1", | ||
| "safe-stable-stringify": "^2.3.1", | ||
| "undici": "^7.24.0", | ||
| "undici": "^8.9.0", |
There was a problem hiding this comment.
🔴 Bumping undici from ^7.24.0 to ^8.9.0 breaks Node's global fetch() at all three call sites that pass an undici Agent/Dispatcher via the dispatcher option: src/storage/renderer/image.ts, src/storage/cdn/cdn-cache-manager.ts, and src/storage/events/lifecycle/webhook.ts. This is because Node's built-in global fetch uses its own internally-bundled (older) undici, which is unaffected by this npm bump, and undici v8's stricter internal handler assertions reject the older-interface handler objects Node's fetch constructs — causing CDN purges and webhook delivery to fail immediately with \x27fetch failed\x27, and imgproxy requests to hang for the full timeout before failing.
Extended reasoning...
This PR bumps undici from ^7.24.0 to ^8.9.0. Three call sites in this repo construct a Dispatcher using the npm undici package's Agent, and pass that dispatcher into Node's global fetch() via the dispatcher option:
src/storage/renderer/image.ts:134-166,235-240— buildsnew Agent(...).compose(interceptors.retry(...))and passes it tofetch()for every imgproxy image-transform request.src/storage/cdn/cdn-cache-manager.ts:10,106-114— builds a plainnew Agent(...)for CDN purge requests.src/storage/events/lifecycle/webhook.ts:48-54,83-91— builds a plainnew Agent(...)for webhook delivery.
Node's global fetch() is implemented on top of Node's own internally-bundled copy of undici — a fixed version compiled into the Node binary — not the version installed in node_modules. Bumping the npm undici dependency has no effect on that bundled copy. On Node 24 (this repo's minimum required version per engines.node >= 24.0.0), the bundled fetch implementation corresponds to undici ~7.29.0; on Node 22 it corresponds to ~6.23.0.
undici v8 tightened its internal request-handler protocol: assertRequestHandler() in lib/core/util.js now requires the new handler interface (onRequestStart, onResponseStart, onResponseData, onResponseEnd, onResponseError). Node's bundled fetch, built against the older undici 7.x/6.x protocol, constructs handler objects using the old interface (onConnect, onHeaders, onData, onComplete). When that old-style handler is dispatched through a v8 Agent (the dispatcher option), assertRequestHandler rejects it.
This was empirically verified by multiple independent reproductions on both Node 22.22.0 and Node 24.18.1 (the repo's required runtime): with undici@7.28.0 installed (pre-PR), both the plain-Agent pattern (CDN/webhook) and the Agent.compose(interceptors.retry(...)) pattern (image.ts) work correctly and return 200 OK. With undici@8.9.0 installed (this PR), the plain-Agent pattern makes global fetch() throw TypeError: fetch failed with cause invalid onRequestStart method on every request, while the composed-with-retry pattern used in image.ts never throws at all — the promise simply never settles, and the request hangs until the caller's own AbortSignal (IMGPROXY_TOTAL_TIMEOUT_MS) eventually fires and aborts it.
Concrete walkthrough (CDN/webhook path):
cdn-cache-manager.tsconstructsdispatcher = new Agent({...})from the npmundici@8.9.0package.- It calls global
fetch(url, { dispatcher, ... })(notundici.fetch/undici.request). - Node's built-in fetch machinery (backed by its bundled undici ~7.29) builds a request handler object with
onConnect/onHeaders/onData/onCompleteand callsdispatcher.dispatch(opts, handler). - The v8
Agent.dispatch()callsassertRequestHandler(handler, ...), which requiresonRequestStartetc. — absent on this handler — and throws. - The thrown error surfaces as
fetch failed, so every CDN purge and every webhook delivery fails immediately once this PR ships.
For image.ts, the same mismatch occurs, but because the dispatcher is Agent.compose(interceptors.retry(...)), the failure manifests as a hang instead of an immediate throw — the request never completes until the external AbortSignal timeout fires, meaning every imgproxy transform would stall for the full configured timeout before failing.
Nothing in the existing code or type system catches this: dispatcher is a duck-typed option on fetch(), and TypeScript's Dispatcher type from undici's own .d.ts is what's used for both the npm package and (implicitly) Node's built-in fetch, so there's no compile-time signal that the two are now protocol-incompatible across major versions. The safe fix is to either pin undici to a 7.x release (e.g. ^7.29.0) to pick up the security fixes without changing the major version consumed by these dispatcher-based call sites, or to migrate the three call sites to call undici.fetch/undici.request directly (using undici's own fetch implementation, which is guaranteed to be protocol-compatible with its own Agent) instead of passing the v8 dispatcher into Node's global fetch.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light.png)
Bumps undici from 7.28.0 to 8.9.0.
Release notes
Sourced from undici's releases.
... (truncated)
Commits
21a8e1eBumped v8.9.0 (#5589)7d3cf92fix: validate blob body content typec601ffffix(cache): harden cache directive parsing2b3f749test(retry): correct broken content-range fixtures in retry-handler.jse11a68efix(retry): reject partial content length mismatch9f09b49test: cover crash on mixed unqualified and qualified private cache directives4fe5bc5fix: handle empty qualified private cache directive10d93fcfix: harden cookie domain, path, and unparsed attribute validationa17e301Ignore auto-generated .npmrc on Windows (#5583)a0922b0fix: handle frozen globalThis in setGlobalDispatcher (#5574)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)