Skip to content

fix: retry refused HTTP/2 streams - #5598

Merged
mcollina merged 2 commits into
mainfrom
fix/http2-refused-stream-retry
Aug 3, 2026
Merged

fix: retry refused HTTP/2 streams#5598
mcollina merged 2 commits into
mainfrom
fix/http2-refused-stream-retry

Conversation

@mcollina

Copy link
Copy Markdown
Member

This relates to...

HTTP/2 peers can reject an unprocessed stream with RST_STREAM(REFUSED_STREAM). Node.js surfaces this as the generic ERR_HTTP2_STREAM_ERROR, and Undici previously failed the request without considering the retry-safe HTTP/2 reset code.

Rationale

RFC 9113 section 8.7 permits clients to retry a request rejected with REFUSED_STREAM, including non-idempotent requests, because the peer asserts that no application processing occurred. It also says clients should not automatically retry the same request more than once.

Changes

Features

N/A

Bug Fixes

  • Detect NGHTTP2_REFUSED_STREAM using stream.rstCode before response headers are exposed.
  • Retry the request once on the existing HTTP/2 session.
  • Restrict transparent retries to replayable bodies: no body, Buffer, or Blob.
  • Preserve the numeric reset code as error.http2ErrorCode when an error is ultimately exposed.
  • Add regression coverage for concurrent streams, buffered POST bodies, the one-retry limit, streaming bodies, and other reset codes.

Breaking Changes and Deprecations

None.

Status

Validation:

  • npm run test:h2:core — 96 passed
  • npm run test:h2:fetch — 11 passed
  • npx eslint lib/dispatcher/client-h2.js test/http2-refused-stream.js
  • Commit hook npm run lint

@mcollina
mcollina requested a review from trivikr July 27, 2026 16:15
@codecov-commenter

codecov-commenter commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.50%. Comparing base (c76fe46) to head (5f9a959).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5598   +/-   ##
=======================================
  Coverage   93.49%   93.50%           
=======================================
  Files         110      110           
  Lines       38429    38476   +47     
=======================================
+ Hits        35931    35976   +45     
- Misses       2498     2500    +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mcollina
mcollina requested a review from metcoder95 July 28, 2026 06:41
Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina
mcollina force-pushed the fix/http2-refused-stream-retry branch from b87e26f to 34476c2 Compare July 29, 2026 13:05
@mcollina

Copy link
Copy Markdown
Member Author

Rebased onto main (was conflicting since #5603 landed).

The conflict was not purely textual, so flagging the resolution for review:

#5603 turned canRetryRequestAfterGoAway() into a stateful function — it increments a per-request GOAWAY retry budget (kRefusedAttempts, capped at MAX_REFUSED_ATTEMPTS) as a side effect of being called. This PR renames that same function to canReplayRequest() and calls it from retryRefusedStream() as a pure "is this body replayable?" predicate.

Merged naively, asking whether a REFUSED_STREAM retry is possible would silently consume one of the request's GOAWAY reconnect attempts. So I split them:

function canReplayRequest (request) {        // pure predicate
  const { body } = request
  return body == null || util.isBuffer(body) || util.isBlobLike(body)
}

function registerGoAwayRefusal (request) {   // the budget, GOAWAY path only
  const attempts = (request[kRefusedAttempts] ?? 0) + 1
  request[kRefusedAttempts] = attempts
  return attempts <= MAX_REFUSED_ATTEMPTS
}

with the GOAWAY site now reading canReplayRequest(request) && registerGoAwayRefusal(request). Both limits are preserved and independent:

peer behaviour connections opened outcome
GOAWAY(lastStreamID=0) on every connection 4 (1 + 3 retries) fails with the GOAWAY error
RST_STREAM(REFUSED_STREAM) on every request 1 retried once on the same session, then fails

One thing worth deciding separately: RFC 9113 §8.7 says clients SHOULD NOT automatically retry more than once, which this PR follows for REFUSED_STREAM (kRefusedStreamRetry), while the GOAWAY path from #5603 allows 3. Different signals, so not necessarily wrong, but the asymmetry is now visible in one file.

Verification on the rebased branch: test/http2-refused-stream.js 6/6, the four h2 suites from #5603 all pass, test/+(http2|h2)*.js 105 pass, full unit suite 1456 pass, 0 fail.

Two follow-ups to the rebase onto main.

RFC 9113 section 8.7 says a client SHOULD NOT automatically retry a request
more than once. The REFUSED_STREAM path already followed that; the GOAWAY
replay budget introduced in #5603 allowed three. Lower it to one so both
refusal signals behave the same, and rename the budget after the signal it
belongs to now that REFUSED_STREAM retries live in this file too:

  MAX_REFUSED_ATTEMPTS -> MAX_GOAWAY_REPLAY_ATTEMPTS
  kRefusedAttempts     -> kGoAwayReplayAttempts

A peer that refuses every connection now opens two connections rather than
four before the request fails, so tighten the regression bound accordingly.

retryRefusedStream() also open-coded the stream detach that
detachRequestStreamForClose() already performs. Reuse the helper: it drops
the 'close' listener rather than relying only on the nulled state, and it
guards the stream-count decrement, so the abandoned attempt cannot touch
the retried request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A49JamgF2TkZHu5h58ChUM
Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina

Copy link
Copy Markdown
Member Author

Pushed the refactor and the spec alignment — but please read the second half of this comment first, it blocks the PR.

Changes in 5f9a9598

RFC 9113 §8.7 alignment. The REFUSED_STREAM path already retried at most once; the GOAWAY replay budget from #5603 allowed three. Lowered to one so both refusal signals agree, and renamed the budget now that two refusal mechanisms share this file: MAX_REFUSED_ATTEMPTSMAX_GOAWAY_REPLAY_ATTEMPTS, kRefusedAttemptskGoAwayReplayAttempts. A peer refusing every connection now opens 2 connections instead of 4, and test/http2-goaway-refusal-storm.js asserts <= 4 rather than the now-meaningless <= 20.

Detach refactor. retryRefusedStream() open-coded what detachRequestStreamForClose() already does. Reusing the helper also drops the 'close' listener instead of relying only on the nulled state, and guards the stream-count decrement.

test/http2-refused-stream.js 6/6, storm test 3/3, test/+(http2|h2)*.js 105 pass.

⚠️ This branch segfaults

While measuring the above I found that this PR crashes the process under HTTP/2 connection churn — SIGSEGV, exit 139, a native crash rather than a JS error.

Reproduce with the churn harness already in the tree (test/http2-request-never-settles.js, from #5603), run directly so the exit code is visible:

for i in $(seq 1 8); do
  node --expose-gc test/http2-request-never-settles.js >/dev/null 2>&1
  echo "exit=$?"
done
build non-zero exits
main (c76fe46) 0/8
this branch at 34476c29 (rebase only, before today's commit) 3/8
this branch at 5f9a9598 (with the changes above) 1/12

So it is not the rebase resolution and not the two changes in this commit — the crash tracks the PR's own retry path. The refactor seems to reduce the rate rather than fix it, which is consistent with it removing one stale reference to the abandoned stream, but that is a guess.

The most likely area is retryRefusedStream() splicing the request back into the pending queue while the reset stream is still being torn down by nghttp2 — the request gets re-dispatched onto the same session while native state for the old stream is still in flight. Under borp this surfaces as a bare 'test failed' with one subtest missing and no assertion, which is easy to mistake for flakiness; running the file directly is what exposes the signal handler.

I did not chase it further — a native use-after-free needs a debug build and a gdb backtrace to pin down, and it may well be a Node bug that this access pattern triggers rather than something to fix here. Happy to dig in if useful.

@mcollina

Copy link
Copy Markdown
Member Author

Correction to my earlier comment above — the part blaming this PR for the crash was wrong, twice over.

What I said: that the process crash "tracks the PR's own retry path", based on 3/8 crashes on this branch versus 0/8 on main.

What is actually going on: there were two unrelated crashes behind one signature, and neither is caused by this PR. Under borp both appear as a bare 'test failed' with a subtest silently missing and no assertion text, which is what let me conflate them.

1. Assertion failed: onread->IsFunction() (SIGABRT). Node main only. Bisected to 46de80de88c (http2: avoid uaf while receiving and sending rst_stream, #64166) — the deferred session close it introduces lets nghttp2 deliver DATA to streams whose JS wrappers are gone. Reproduces from a dependency-free node:http2 script with no undici involved, and reproduces on undici main as well as this branch. Filed as nodejs/node#64850.

2. V8 maglev SIGSEGV. This is what the CI failures on this PR actually are, on released v24.18.0:

#1 v8::internal::Heap::UnregisterStrongRoots(...)
#3 v8::internal::maglev::MaglevConcurrentDispatcher::FinalizeFinishedJobs()
   (V8Worker) #3 v8::internal::maglev::MaglevCompiler::Compile(...)
build crash rate
this branch, v24.18.0 ~24% (12/50)
main, v24.18.0, without this PR ~4% (2/50)
this branch, --no-maglev 0/45

It reproduces on main too, just less often. The PR's added code plausibly raises maglev compilation pressure; there is no semantic defect here. Only Node 24 / ubuntu was affected in CI — 22, 25, 26, macOS and Windows were green, matching local rates.

Net: no undici bug in this PR from either crash. The rebase and the RFC 9113 §8.7 changes still stand as described. CI on this branch will keep flaking on Node 24 until the V8 issue is fixed or test/http2-request-never-settles.js is made less maglev-intensive — worth deciding separately from this PR.

Apologies for the noise from the earlier comment.

@mcollina
mcollina merged commit 3926499 into main Aug 3, 2026
36 of 38 checks passed
@mcollina
mcollina deleted the fix/http2-refused-stream-retry branch August 3, 2026 11:20
@github-actions github-actions Bot mentioned this pull request Aug 3, 2026
mergify Bot added a commit to ArcadeData/arcadedb that referenced this pull request Aug 5, 2026
Bumps [undici](https://github.com/nodejs/undici) from 8.5.0 to 8.10.0.
Release notes

*Sourced from [undici's releases](https://github.com/nodejs/undici/releases).*

> v8.10.0
> -------
>
> What's Changed
> --------------
>
> * feat: namespace h2 options by [`@​metcoder95`](https://github.com/metcoder95) in [nodejs/undici#5498](https://redirect.github.com/nodejs/undici/pull/5498)
> * test: update WPT expectations by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5587](https://redirect.github.com/nodejs/undici/pull/5587)
> * test: add cache/dedupe + dns re-dispatch integration tests by [`@​GiHoon1123`](https://github.com/GiHoon1123) in [nodejs/undici#5535](https://redirect.github.com/nodejs/undici/pull/5535)
> * fix(websocket): support process.unref by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5578](https://redirect.github.com/nodejs/undici/pull/5578)
> * fix(h2): ensure every request settles by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5603](https://redirect.github.com/nodejs/undici/pull/5603)
> * fix(readable): consume a body whose end has already been emitted by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5617](https://redirect.github.com/nodejs/undici/pull/5617)
> * fix(retry): skip the content-length checkpoint for HEAD and for a 206 without content-range by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5610](https://redirect.github.com/nodejs/undici/pull/5610)
> * fix: revert idle socket validation to setTimeout(0) to prevent stall on idle event loop by [`@​marceli1404`](https://github.com/marceli1404) in [nodejs/undici#5606](https://redirect.github.com/nodejs/undici/pull/5606)
> * fix(env-http-proxy-agent): match bare IPv6 addresses in no\_proxy by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5623](https://redirect.github.com/nodejs/undici/pull/5623)
> * test: handle aggregate balanced pool errors by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5377](https://redirect.github.com/nodejs/undici/pull/5377)
> * fix(readable): keep body bytes that arrive after setEncoding() by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5620](https://redirect.github.com/nodejs/undici/pull/5620)
> * fix(socks5): evict unused origin pools by [`@​Kkartik14`](https://github.com/Kkartik14) in [nodejs/undici#5595](https://redirect.github.com/nodejs/undici/pull/5595)
> * fix: skip deduplication for upgrade requests by [`@​Ram-blip`](https://github.com/Ram-blip) in [nodejs/undici#5593](https://redirect.github.com/nodejs/undici/pull/5593)
> * fix(retry): forward informational responses by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5625](https://redirect.github.com/nodejs/undici/pull/5625)
> * fix(mock): non-string path matchers under ignoreTrailingSlash, and DataView reply bodies by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5619](https://redirect.github.com/nodejs/undici/pull/5619)
> * fix(interceptors): cache() and deduplicate() silently inert on Client/Pool without opts.origin by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5628](https://redirect.github.com/nodejs/undici/pull/5628)
> * build(deps): bump ossf/scorecard-action from 2.4.3 to 2.4.4 by [`@​dependabot`](https://github.com/dependabot)[bot] in [nodejs/undici#5633](https://redirect.github.com/nodejs/undici/pull/5633)
> * build(deps): bump github/codeql-action/init from 4.36.2 to 4.37.3 by [`@​dependabot`](https://github.com/dependabot)[bot] in [nodejs/undici#5634](https://redirect.github.com/nodejs/undici/pull/5634)
> * build(deps): bump actions/setup-node from 6.4.0 to 7.0.0 by [`@​dependabot`](https://github.com/dependabot)[bot] in [nodejs/undici#5636](https://redirect.github.com/nodejs/undici/pull/5636)
> * fix(mock): emit request body lifecycle hooks by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5367](https://redirect.github.com/nodejs/undici/pull/5367)
> * fix(h2): detach upgrade close handler after GOAWAY by [`@​pacocartones`](https://github.com/pacocartones) in [nodejs/undici#5641](https://redirect.github.com/nodejs/undici/pull/5641)
> * fix: retry refused HTTP/2 streams by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5598](https://redirect.github.com/nodejs/undici/pull/5598)
> * fix: preserve DNS origin hostname on sockets by [`@​cyphercodes`](https://github.com/cyphercodes) in [nodejs/undici#5577](https://redirect.github.com/nodejs/undici/pull/5577)
>
> New Contributors
> ----------------
>
> * [`@​marceli1404`](https://github.com/marceli1404) made their first contribution in [nodejs/undici#5606](https://redirect.github.com/nodejs/undici/pull/5606)
> * [`@​Kkartik14`](https://github.com/Kkartik14) made their first contribution in [nodejs/undici#5595](https://redirect.github.com/nodejs/undici/pull/5595)
> * [`@​pacocartones`](https://github.com/pacocartones) made their first contribution in [nodejs/undici#5641](https://redirect.github.com/nodejs/undici/pull/5641)
> * [`@​cyphercodes`](https://github.com/cyphercodes) made their first contribution in [nodejs/undici#5577](https://redirect.github.com/nodejs/undici/pull/5577)
>
> **Full Changelog**: <nodejs/undici@v8.9.0...v8.10.0>
>
> v8.9.0
> ------
>
> ⚠️ Security fixes
> -----------------
>
> ### High severity
>
> * [GHSA-4cwx-7wf7-3272](GHSA-4cwx-7wf7-3272): malformed qualified `private` Cache-Control directives could cause cross-user information disclosure in shared caches or a parse-time crash. The cache parser now treats empty qualified directives conservatively and safely handles mixed qualified and unqualified directives. Fixed by [4fe5bc5f](nodejs/undici@4fe5bc5) with regression coverage in [9f09b49a](nodejs/undici@9f09b49).
>
> ### Medium severity
>
> * [GHSA-m8rv-5g2x-5cg5](GHSA-m8rv-5g2x-5cg5): a malicious `type` property on a duck-typed blob-like HTTP/1.1 request body could inject CRLF sequences into the generated `content-type` header. Undici now coerces and validates the value before adding it to the request. Fixed by [7d3cf924](nodejs/undici@7d3cf92).
> * [GHSA-jr45-8vmc-qm54](GHSA-jr45-8vmc-qm54): optional whitespace around `=` in qualified `no-cache` and `private` directives could bypass shared-cache restrictions and disclose authenticated data across users. Cache-Control parsing now normalizes these forms and applies conservative cache decisions. Fixed by [c601fff1](nodejs/undici@c601fff).
> * [GHSA-8xcm-r25x-g524](GHSA-8xcm-r25x-g524): the retry interceptor could expose a stale `Content-Length` after resuming a partial response, potentially causing downstream response desynchronization, hangs, or corruption. Undici now rejects partial responses whose `Content-Length` is inconsistent with `Content-Range`. Fixed by [e11a68ed](nodejs/undici@e11a68e), with corrected fixtures in [2b3f7493](nodejs/undici@2b3f749).
> * [GHSA-v3r7-h72x-cjcm](GHSA-v3r7-h72x-cjcm): unsanitized `domain` and `unparsed` values passed to `setCookie()` could inject cookie attributes. Undici now validates cookie domains, paths, and unparsed attributes more strictly. Fixed by [10d93fc3](nodejs/undici@10d93fc).
>
> Additional hardening
> --------------------

... (truncated)


Commits

* [`c8d80e6`](nodejs/undici@c8d80e6) Bumped v8.10.0 ([#5644](https://redirect.github.com/nodejs/undici/issues/5644))
* [`66923b4`](nodejs/undici@66923b4) fix: preserve DNS origin hostname on sockets ([#5577](https://redirect.github.com/nodejs/undici/issues/5577))
* [`3926499`](nodejs/undici@3926499) fix: retry refused HTTP/2 streams ([#5598](https://redirect.github.com/nodejs/undici/issues/5598))
* [`73d6e9e`](nodejs/undici@73d6e9e) fix(h2): detach upgrade close handler after GOAWAY ([#5641](https://redirect.github.com/nodejs/undici/issues/5641))
* [`b111adb`](nodejs/undici@b111adb) fix(mock): emit request body lifecycle hooks ([#5367](https://redirect.github.com/nodejs/undici/issues/5367))
* [`ae4a3e3`](nodejs/undici@ae4a3e3) build(deps): bump actions/setup-node from 6.4.0 to 7.0.0 ([#5636](https://redirect.github.com/nodejs/undici/issues/5636))
* [`ec3fbf1`](nodejs/undici@ec3fbf1) build(deps): bump github/codeql-action/init from 4.36.2 to 4.37.3 ([#5634](https://redirect.github.com/nodejs/undici/issues/5634))
* [`2151720`](nodejs/undici@2151720) build(deps): bump ossf/scorecard-action from 2.4.3 to 2.4.4 ([#5633](https://redirect.github.com/nodejs/undici/issues/5633))
* [`b96a116`](nodejs/undici@b96a116) fix(interceptors): allow interceptors without opts.origin ([#5628](https://redirect.github.com/nodejs/undici/issues/5628))
* [`a18ef2d`](nodejs/undici@a18ef2d) fix(mock): non-string path matchers under ignoreTrailingSlash, and DataView r...
* Additional commits viewable in [compare view](nodejs/undici@v8.5.0...v8.10.0)
  
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility\_score?dependency-name=undici&package-manager=npm\_and\_yarn&previous-version=8.5.0&new-version=8.10.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
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-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
  
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show  ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will 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 version` will 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 dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ArcadeData/arcadedb/network/alerts).
mergify Bot added a commit to ArcadeData/arcadedb that referenced this pull request Aug 5, 2026
…p ci]

Bumps [undici](https://github.com/nodejs/undici) from 8.5.0 to 8.10.0.
Release notes

*Sourced from [undici's releases](https://github.com/nodejs/undici/releases).*

> v8.10.0
> -------
>
> What's Changed
> --------------
>
> * feat: namespace h2 options by [`@​metcoder95`](https://github.com/metcoder95) in [nodejs/undici#5498](https://redirect.github.com/nodejs/undici/pull/5498)
> * test: update WPT expectations by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5587](https://redirect.github.com/nodejs/undici/pull/5587)
> * test: add cache/dedupe + dns re-dispatch integration tests by [`@​GiHoon1123`](https://github.com/GiHoon1123) in [nodejs/undici#5535](https://redirect.github.com/nodejs/undici/pull/5535)
> * fix(websocket): support process.unref by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5578](https://redirect.github.com/nodejs/undici/pull/5578)
> * fix(h2): ensure every request settles by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5603](https://redirect.github.com/nodejs/undici/pull/5603)
> * fix(readable): consume a body whose end has already been emitted by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5617](https://redirect.github.com/nodejs/undici/pull/5617)
> * fix(retry): skip the content-length checkpoint for HEAD and for a 206 without content-range by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5610](https://redirect.github.com/nodejs/undici/pull/5610)
> * fix: revert idle socket validation to setTimeout(0) to prevent stall on idle event loop by [`@​marceli1404`](https://github.com/marceli1404) in [nodejs/undici#5606](https://redirect.github.com/nodejs/undici/pull/5606)
> * fix(env-http-proxy-agent): match bare IPv6 addresses in no\_proxy by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5623](https://redirect.github.com/nodejs/undici/pull/5623)
> * test: handle aggregate balanced pool errors by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5377](https://redirect.github.com/nodejs/undici/pull/5377)
> * fix(readable): keep body bytes that arrive after setEncoding() by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5620](https://redirect.github.com/nodejs/undici/pull/5620)
> * fix(socks5): evict unused origin pools by [`@​Kkartik14`](https://github.com/Kkartik14) in [nodejs/undici#5595](https://redirect.github.com/nodejs/undici/pull/5595)
> * fix: skip deduplication for upgrade requests by [`@​Ram-blip`](https://github.com/Ram-blip) in [nodejs/undici#5593](https://redirect.github.com/nodejs/undici/pull/5593)
> * fix(retry): forward informational responses by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5625](https://redirect.github.com/nodejs/undici/pull/5625)
> * fix(mock): non-string path matchers under ignoreTrailingSlash, and DataView reply bodies by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5619](https://redirect.github.com/nodejs/undici/pull/5619)
> * fix(interceptors): cache() and deduplicate() silently inert on Client/Pool without opts.origin by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5628](https://redirect.github.com/nodejs/undici/pull/5628)
> * build(deps): bump ossf/scorecard-action from 2.4.3 to 2.4.4 by [`@​dependabot`](https://github.com/dependabot)[bot] in [nodejs/undici#5633](https://redirect.github.com/nodejs/undici/pull/5633)
> * build(deps): bump github/codeql-action/init from 4.36.2 to 4.37.3 by [`@​dependabot`](https://github.com/dependabot)[bot] in [nodejs/undici#5634](https://redirect.github.com/nodejs/undici/pull/5634)
> * build(deps): bump actions/setup-node from 6.4.0 to 7.0.0 by [`@​dependabot`](https://github.com/dependabot)[bot] in [nodejs/undici#5636](https://redirect.github.com/nodejs/undici/pull/5636)
> * fix(mock): emit request body lifecycle hooks by [`@​marko1olo`](https://github.com/marko1olo) in [nodejs/undici#5367](https://redirect.github.com/nodejs/undici/pull/5367)
> * fix(h2): detach upgrade close handler after GOAWAY by [`@​pacocartones`](https://github.com/pacocartones) in [nodejs/undici#5641](https://redirect.github.com/nodejs/undici/pull/5641)
> * fix: retry refused HTTP/2 streams by [`@​mcollina`](https://github.com/mcollina) in [nodejs/undici#5598](https://redirect.github.com/nodejs/undici/pull/5598)
> * fix: preserve DNS origin hostname on sockets by [`@​cyphercodes`](https://github.com/cyphercodes) in [nodejs/undici#5577](https://redirect.github.com/nodejs/undici/pull/5577)
>
> New Contributors
> ----------------
>
> * [`@​marceli1404`](https://github.com/marceli1404) made their first contribution in [nodejs/undici#5606](https://redirect.github.com/nodejs/undici/pull/5606)
> * [`@​Kkartik14`](https://github.com/Kkartik14) made their first contribution in [nodejs/undici#5595](https://redirect.github.com/nodejs/undici/pull/5595)
> * [`@​pacocartones`](https://github.com/pacocartones) made their first contribution in [nodejs/undici#5641](https://redirect.github.com/nodejs/undici/pull/5641)
> * [`@​cyphercodes`](https://github.com/cyphercodes) made their first contribution in [nodejs/undici#5577](https://redirect.github.com/nodejs/undici/pull/5577)
>
> **Full Changelog**: <nodejs/undici@v8.9.0...v8.10.0>
>
> v8.9.0
> ------
>
> ⚠️ Security fixes
> -----------------
>
> ### High severity
>
> * [GHSA-4cwx-7wf7-3272](GHSA-4cwx-7wf7-3272): malformed qualified `private` Cache-Control directives could cause cross-user information disclosure in shared caches or a parse-time crash. The cache parser now treats empty qualified directives conservatively and safely handles mixed qualified and unqualified directives. Fixed by [4fe5bc5f](nodejs/undici@4fe5bc5) with regression coverage in [9f09b49a](nodejs/undici@9f09b49).
>
> ### Medium severity
>
> * [GHSA-m8rv-5g2x-5cg5](GHSA-m8rv-5g2x-5cg5): a malicious `type` property on a duck-typed blob-like HTTP/1.1 request body could inject CRLF sequences into the generated `content-type` header. Undici now coerces and validates the value before adding it to the request. Fixed by [7d3cf924](nodejs/undici@7d3cf92).
> * [GHSA-jr45-8vmc-qm54](GHSA-jr45-8vmc-qm54): optional whitespace around `=` in qualified `no-cache` and `private` directives could bypass shared-cache restrictions and disclose authenticated data across users. Cache-Control parsing now normalizes these forms and applies conservative cache decisions. Fixed by [c601fff1](nodejs/undici@c601fff).
> * [GHSA-8xcm-r25x-g524](GHSA-8xcm-r25x-g524): the retry interceptor could expose a stale `Content-Length` after resuming a partial response, potentially causing downstream response desynchronization, hangs, or corruption. Undici now rejects partial responses whose `Content-Length` is inconsistent with `Content-Range`. Fixed by [e11a68ed](nodejs/undici@e11a68e), with corrected fixtures in [2b3f7493](nodejs/undici@2b3f749).
> * [GHSA-v3r7-h72x-cjcm](GHSA-v3r7-h72x-cjcm): unsanitized `domain` and `unparsed` values passed to `setCookie()` could inject cookie attributes. Undici now validates cookie domains, paths, and unparsed attributes more strictly. Fixed by [10d93fc3](nodejs/undici@10d93fc).
>
> Additional hardening
> --------------------

... (truncated)


Commits

* [`c8d80e6`](nodejs/undici@c8d80e6) Bumped v8.10.0 ([#5644](https://redirect.github.com/nodejs/undici/issues/5644))
* [`66923b4`](nodejs/undici@66923b4) fix: preserve DNS origin hostname on sockets ([#5577](https://redirect.github.com/nodejs/undici/issues/5577))
* [`3926499`](nodejs/undici@3926499) fix: retry refused HTTP/2 streams ([#5598](https://redirect.github.com/nodejs/undici/issues/5598))
* [`73d6e9e`](nodejs/undici@73d6e9e) fix(h2): detach upgrade close handler after GOAWAY ([#5641](https://redirect.github.com/nodejs/undici/issues/5641))
* [`b111adb`](nodejs/undici@b111adb) fix(mock): emit request body lifecycle hooks ([#5367](https://redirect.github.com/nodejs/undici/issues/5367))
* [`ae4a3e3`](nodejs/undici@ae4a3e3) build(deps): bump actions/setup-node from 6.4.0 to 7.0.0 ([#5636](https://redirect.github.com/nodejs/undici/issues/5636))
* [`ec3fbf1`](nodejs/undici@ec3fbf1) build(deps): bump github/codeql-action/init from 4.36.2 to 4.37.3 ([#5634](https://redirect.github.com/nodejs/undici/issues/5634))
* [`2151720`](nodejs/undici@2151720) build(deps): bump ossf/scorecard-action from 2.4.3 to 2.4.4 ([#5633](https://redirect.github.com/nodejs/undici/issues/5633))
* [`b96a116`](nodejs/undici@b96a116) fix(interceptors): allow interceptors without opts.origin ([#5628](https://redirect.github.com/nodejs/undici/issues/5628))
* [`a18ef2d`](nodejs/undici@a18ef2d) fix(mock): non-string path matchers under ignoreTrailingSlash, and DataView r...
* Additional commits viewable in [compare view](nodejs/undici@v8.5.0...v8.10.0)
  
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility\_score?dependency-name=undici&package-manager=npm\_and\_yarn&previous-version=8.5.0&new-version=8.10.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
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-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
  
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show  ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will 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 version` will 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 dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ArcadeData/arcadedb/network/alerts).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants