Moderate severityNVD Advisory· Published Jul 19, 2022· Updated Apr 22, 2025
CRLF injection in request headers
CVE-2022-31150
Description
undici is an HTTP/1.1 client, written from scratch for Node.js. It is possible to inject CRLF sequences into request headers in undici in versions less than 5.7.1. A fix was released in version 5.8.0. Sanitizing all HTTP headers from untrusted sources to eliminate \r\n is a workaround for this issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
undicinpm | < 5.8.0 | 5.8.0 |
Affected products
1Patches
1a29a151d0140Merge pull request from GHSA-3cvr-822r-rqcc
2 files changed · +145 −0
lib/core/request.js+29 −0 modified@@ -7,6 +7,27 @@ const { const assert = require('assert') const util = require('./util') +// tokenRegExp and headerCharRegex have been lifted from +// https://github.com/nodejs/node/blob/main/lib/_http_common.js + +/** + * Verifies that the given val is a valid HTTP token + * per the rules defined in RFC 7230 + * See https://tools.ietf.org/html/rfc7230#section-3.2.6 + */ +const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/ + +/** + * Matches if val contains an invalid field-vchar + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + */ +const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/ + +// Verifies that a given path is valid does not contain control chars \x00 to \x20 +const invalidPathRegex = /[^\u0021-\u00ff]/ + const kHandler = Symbol('handler') const channels = {} @@ -54,10 +75,14 @@ class Request { method !== 'CONNECT' ) { throw new InvalidArgumentError('path must be an absolute URL or start with a slash') + } else if (invalidPathRegex.exec(path) !== null) { + throw new InvalidArgumentError('invalid request path') } if (typeof method !== 'string') { throw new InvalidArgumentError('method must be a string') + } else if (tokenRegExp.exec(method) === null) { + throw new InvalidArgumentError('invalid request method') } if (upgrade && typeof upgrade !== 'string') { @@ -301,6 +326,10 @@ function processHeader (request, key, val) { key.toLowerCase() === 'expect' ) { throw new NotSupportedError('expect header not supported') + } else if (tokenRegExp.exec(key) === null) { + throw new InvalidArgumentError('invalid header key') + } else if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) } else { request.headers += `${key}: ${val}\r\n` }
test/client.js+116 −0 modified@@ -1994,3 +1994,119 @@ function buildParams (path) { return builtParams } + +test('\\r\\n in Headers', (t) => { + t.plan(1) + + const reqHeaders = { + bar: '\r\nbar' + } + + const client = new Client('http://localhost:4242', { + keepAliveTimeout: 300e3 + }) + t.teardown(client.close.bind(client)) + + client.request({ + path: '/', + method: 'GET', + headers: reqHeaders + }, (err) => { + t.equal(err.message, 'invalid bar header') + }) +}) + +test('\\r in Headers', (t) => { + t.plan(1) + + const reqHeaders = { + bar: '\rbar' + } + + const client = new Client('http://localhost:4242', { + keepAliveTimeout: 300e3 + }) + t.teardown(client.close.bind(client)) + + client.request({ + path: '/', + method: 'GET', + headers: reqHeaders + }, (err) => { + t.equal(err.message, 'invalid bar header') + }) +}) + +test('\\n in Headers', (t) => { + t.plan(1) + + const reqHeaders = { + bar: '\nbar' + } + + const client = new Client('http://localhost:4242', { + keepAliveTimeout: 300e3 + }) + t.teardown(client.close.bind(client)) + + client.request({ + path: '/', + method: 'GET', + headers: reqHeaders + }, (err) => { + t.equal(err.message, 'invalid bar header') + }) +}) + +test('\\n in Headers', (t) => { + t.plan(1) + + const reqHeaders = { + '\nbar': 'foo' + } + + const client = new Client('http://localhost:4242', { + keepAliveTimeout: 300e3 + }) + t.teardown(client.close.bind(client)) + + client.request({ + path: '/', + method: 'GET', + headers: reqHeaders + }, (err) => { + t.equal(err.message, 'invalid header key') + }) +}) + +test('\\n in Path', (t) => { + t.plan(1) + + const client = new Client('http://localhost:4242', { + keepAliveTimeout: 300e3 + }) + t.teardown(client.close.bind(client)) + + client.request({ + path: '/\n', + method: 'GET' + }, (err) => { + t.equal(err.message, 'invalid request path') + }) +}) + +test('\\n in Method', (t) => { + t.plan(1) + + const client = new Client('http://localhost:4242', { + keepAliveTimeout: 300e3 + }) + t.teardown(client.close.bind(client)) + + client.request({ + path: '/', + method: 'GET\n' + }, (err) => { + t.equal(err.message, 'invalid request method') + }) +})
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-3cvr-822r-rqccghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-31150ghsaADVISORY
- github.com/nodejs/undici/commit/a29a151d0140d095742d21a004023d024fe93259ghsaWEB
- github.com/nodejs/undici/releases/tag/v5.8.0ghsax_refsource_MISCWEB
- github.com/nodejs/undici/security/advisories/GHSA-3cvr-822r-rqccghsax_refsource_CONFIRMWEB
- hackerone.com/reports/409943ghsax_refsource_MISCWEB
- security.netapp.com/advisory/ntap-20220915-0002ghsaWEB
- security.netapp.com/advisory/ntap-20220915-0002/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.