CVE-2026-86250
h3 before 2.0.1-rc.18 Denial of Service via Unbounded Chunked Cookie
h3 versions before 2.0.1-rc.18 fail to validate the chunk count parsed from user-controlled cookie values in setChunkedCookie() and deleteChunkedCookie() functions. Attackers can send a crafted cookie header with an extremely large chunk count to trigger an O(n²) cleanup loop that hangs the server process.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
8.7
h3 before 2.0.1-rc.18 Denial of Service via Unbounded Chunked Cookie
h3 versions before 2.0.1-rc.18 fail to validate the chunk count parsed from user-controlled cookie values in setChunkedCookie() and deleteChunkedCookie() functions. Attackers can send a crafted cookie header with an extremely large chunk count to trigger an O(n²) cleanup loop that hangs the server process.
- Record
CVE-2026-86250- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-400
- Updated
- 2026-11-02
EUVDEUVD-2026-72106
8.7
h3 versions before 2.0.1-rc.18 fail to validate the chunk count parsed from user-controlled cookie values in setChunkedCookie() and deleteChunkedCookie() functions. Attackers can send a crafted cookie header with an extremely large chunk count to trigger an O(n²) cleanup loop that hangs the server process.
- Record
EUVD-2026-72106- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-fg72-q65g-wm5w
8.7
Duplicate Advisory: H3: Unbounded Chunked Cookie Count in Session Cleanup Loop may Lead to Denial of Service
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-q5pr-72pq-83v3. This link is maintained to preserve external references.
Original Description
h3 versions before 2.0.1-rc.18 fail to validate the chunk count parsed from user-controlled cookie values in setChunkedCookie() and deleteChunkedCookie() functions. Attackers can send a crafted cookie header with an extremely large chunk count to trigger an O(n²) cleanup loop that hangs the server process.
- Record
GHSA-fg72-q65g-wm5w- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-400
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-q5pr-72pq-83v3
5.3
H3: Unbounded Chunked Cookie Count in Session Cleanup Loop may Lead to Denial of Service
Summary
The setChunkedCookie() and deleteChunkedCookie() functions in h3 trust the chunk count parsed from a user-controlled cookie value (__chunked__N) without any upper bound validation. An unauthenticated attacker can send a single request with a crafted cookie header (e.g., Cookie: h3=__chunked__999999) to any endpoint using sessions, causing the server to enter an O(n²) loop that hangs the process.
Details
The chunked cookie system stores large cookie values by splitting them into numbered chunks. The main cookie stores a sentinel value __chunked__N indicating how many chunks exist. When setting a new chunked cookie, the code cleans up any previous chunks that are no longer needed.
The vulnerability is in getChunkedCookieCount() at src/utils/cookie.ts:244-249:
function getChunkedCookieCount(cookie: string | undefined): number {
if (!cookie?.startsWith(CHUNKED_COOKIE)) {
return Number.NaN;
}
return Number.parseInt(cookie.slice(CHUNKED_COOKIE.length));
// No upper bound check — attacker controls this value
}
This value is consumed without validation in the cleanup loop of setChunkedCookie() at src/utils/cookie.ts:182-190:
const previousCookie = getCookie(event, name); // reads from request headers
if (previousCookie?.startsWith(CHUNKED_COOKIE)) {
const previousChunkCount = getChunkedCookieCount(previousCookie);
if (previousChunkCount > chunkCount) {
for (let i = chunkCount; i <= previousChunkCount; i++) {
deleteCookie(event, chunkCookieName(name, i), options);
// Each deleteCookie → setCookie → scans ALL existing set-cookie headers
}
}
}
The same issue exists in deleteChunkedCookie() at src/utils/cookie.ts:227-232:
const chunksCount = getChunkedCookieCount(mainCookie);
if (chunksCount >= 0) {
for (let i = 0; i < chunksCount; i++) {
deleteCookie(event, chunkCookieName(name, i + 1), serializeOptions);
}
}
The exploit chain through sessions:
- Attacker sends
Cookie: h3=__chunked__999999to any session-using endpoint getSession()(src/utils/session.ts:83) callsgetChunkedCookie(event, "h3")(line 124)getChunkedCookie()returnsundefined— the early return at line 153 fires because no actual chunk cookies (e.g.,h3.1) exist in the request- Since
sealedSessionis undefined,session.idremains empty (line 140), triggeringupdateSession()(line 143) updateSession()callssetChunkedCookie()with the newly sealed session value (line 179)- Inside
setChunkedCookie(),getCookie(event, name)re-reads the original request cookie__chunked__999999at line 182 previousChunkCount= 999999,chunkCount= 1 (new sealed session is small)- The cleanup loop runs 999,998 iterations, each calling
deleteCookie()→setCookie() - Each
setCookie()call reads ALL existingset-cookieresponse headers viagetSetCookie()(line 91) and iterates through them for deduplication (lines 100-106) - This creates O(n²) complexity — approximately 10¹² operations for n=999999
Key observation: While getChunkedCookie() has an early-return optimization (line 153) that prevents it from looping on missing chunks, the cleanup loops in setChunkedCookie() and deleteChunkedCookie() have no such protection and run unconditionally for the full claimed chunk count.
PoC
Prerequisites: An h3 application with any endpoint using getSession() or useSession().
Example minimal server:
import { H3 } from "h3";
import { getSession } from "h3";
const app = new H3();
app.get("/dashboard", async (event) => {
const session = await getSession(event, {
password: "my-secret-password-at-least-32-chars-long!",
});
return { user: session.data.user || "anonymous" };
});
export default app;
Attack (single request, no authentication):
# This single request will hang the server process
curl -H 'Cookie:…
- Record
GHSA-q5pr-72pq-83v3- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400
- Updated
- 2026-11-02
NVD
8.7
h3 versions before 2.0.1-rc.18 fail to validate the chunk count parsed from user-controlled cookie values in setChunkedCookie() and deleteChunkedCookie() functions. Attackers can send a crafted cookie header with an extremely large chunk count to trigger an O(n²) cleanup loop that hangs the server process.
- Record
CVE-2026-86250- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-400
- Updated
- 2026-11-02
OSV
8.7
h3 before 2.0.1-rc.18 Denial of Service via Unbounded Chunked Cookie
h3 versions before 2.0.1-rc.18 fail to validate the chunk count parsed from user-controlled cookie values in setChunkedCookie() and deleteChunkedCookie() functions. Attackers can send a crafted cookie header with an extremely large chunk count to trigger an O(n²) cleanup loop that hangs the server process.
- Record
CVE-2026-86250- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-400
- Updated
- 2026-11-02
OSVGHSA-fg72-q65g-wm5w
8.7
Duplicate Advisory: H3: Unbounded Chunked Cookie Count in Session Cleanup Loop may Lead to Denial of Service
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-q5pr-72pq-83v3. This link is maintained to preserve external references.
Original Description
h3 versions before 2.0.1-rc.18 fail to validate the chunk count parsed from user-controlled cookie values in setChunkedCookie() and deleteChunkedCookie() functions. Attackers can send a crafted cookie header with an extremely large chunk count to trigger an O(n²) cleanup loop that hangs the server process.
- Record
GHSA-fg72-q65g-wm5w- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-400
- Updated
- 2026-11-02
OSVGHSA-q5pr-72pq-83v3
5.3
H3: Unbounded Chunked Cookie Count in Session Cleanup Loop may Lead to Denial of Service
Summary
The setChunkedCookie() and deleteChunkedCookie() functions in h3 trust the chunk count parsed from a user-controlled cookie value (__chunked__N) without any upper bound validation. An unauthenticated attacker can send a single request with a crafted cookie header (e.g., Cookie: h3=__chunked__999999) to any endpoint using sessions, causing the server to enter an O(n²) loop that hangs the process.
Details
The chunked cookie system stores large cookie values by splitting them into numbered chunks. The main cookie stores a sentinel value __chunked__N indicating how many chunks exist. When setting a new chunked cookie, the code cleans up any previous chunks that are no longer needed.
The vulnerability is in getChunkedCookieCount() at src/utils/cookie.ts:244-249:
function getChunkedCookieCount(cookie: string | undefined): number {
if (!cookie?.startsWith(CHUNKED_COOKIE)) {
return Number.NaN;
}
return Number.parseInt(cookie.slice(CHUNKED_COOKIE.length));
// No upper bound check — attacker controls this value
}
This value is consumed without validation in the cleanup loop of setChunkedCookie() at src/utils/cookie.ts:182-190:
const previousCookie = getCookie(event, name); // reads from request headers
if (previousCookie?.startsWith(CHUNKED_COOKIE)) {
const previousChunkCount = getChunkedCookieCount(previousCookie);
if (previousChunkCount > chunkCount) {
for (let i = chunkCount; i <= previousChunkCount; i++) {
deleteCookie(event, chunkCookieName(name, i), options);
// Each deleteCookie → setCookie → scans ALL existing set-cookie headers
}
}
}
The same issue exists in deleteChunkedCookie() at src/utils/cookie.ts:227-232:
const chunksCount = getChunkedCookieCount(mainCookie);
if (chunksCount >= 0) {
for (let i = 0; i < chunksCount; i++) {
deleteCookie(event, chunkCookieName(name, i + 1), serializeOptions);
}
}
The exploit chain through sessions:
- Attacker sends
Cookie: h3=__chunked__999999to any session-using endpoint getSession()(src/utils/session.ts:83) callsgetChunkedCookie(event, "h3")(line 124)getChunkedCookie()returnsundefined— the early return at line 153 fires because no actual chunk cookies (e.g.,h3.1) exist in the request- Since
sealedSessionis undefined,session.idremains empty (line 140), triggeringupdateSession()(line 143) updateSession()callssetChunkedCookie()with the newly sealed session value (line 179)- Inside
setChunkedCookie(),getCookie(event, name)re-reads the original request cookie__chunked__999999at line 182 previousChunkCount= 999999,chunkCount= 1 (new sealed session is small)- The cleanup loop runs 999,998 iterations, each calling
deleteCookie()→setCookie() - Each
setCookie()call reads ALL existingset-cookieresponse headers viagetSetCookie()(line 91) and iterates through them for deduplication (lines 100-106) - This creates O(n²) complexity — approximately 10¹² operations for n=999999
Key observation: While getChunkedCookie() has an early-return optimization (line 153) that prevents it from looping on missing chunks, the cleanup loops in setChunkedCookie() and deleteChunkedCookie() have no such protection and run unconditionally for the full claimed chunk count.
PoC
Prerequisites: An h3 application with any endpoint using getSession() or useSession().
Example minimal server:
import { H3 } from "h3";
import { getSession } from "h3";
const app = new H3();
app.get("/dashboard", async (event) => {
const session = await getSession(event, {
password: "my-secret-password-at-least-32-chars-long!",
});
return { user: session.data.user || "anonymous" };
});
export default app;
Attack (single request, no authentication):
# This single request will hang the server process
curl -H 'Cookie:…
- Record
GHSA-q5pr-72pq-83v3- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
h3npm |
2.0.0-beta.4 up to 2.0.1-rc.18 | 2.0.1-rc.18 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-86250 is a high vulnerability (CVSS 8.7) in H3js H3, published March 23, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.49%, the 42nd percentile. Fixed in h3 2.0.1-rc.18.
References
github.com/h3js/h3/security/advisories/GHSA-q5pr-72pq-83v3
vulncheck.com/advisories/h3-before-2.0.1-rc.18-denial-of-service-via-unbounded-chunked-cookie
nvd.nist.gov/vuln/detail/CVE-2026-86250
github.com/advisories/GHSA-fg72-q65g-wm5w
github.com/h3js/h3/commit/399257cb406fbeda313d88c1e288a15124fc82af
github.com/h3js/h3/releases/tag/v2.0.1-rc.18
github.com/advisories/GHSA-q5pr-72pq-83v3
github.com/CVEProject/cvelistV5/tree/main/cves/2026/86xxx/CVE-2026-86250.json
github.com/h3js/h3
Left out here: the rest of 2 source descriptions. The JSON record has them.