VYPR
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.

PackageAffected versionsPatched versions
undicinpm
< 5.8.05.8.0

Affected products

1

Patches

1
a29a151d0140

Merge pull request from GHSA-3cvr-822r-rqcc

https://github.com/nodejs/undiciMatteo CollinaJul 18, 2022via ghsa
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

News mentions

0

No linked articles in our index yet.