CVE-2024-30260: Insufficient Information (NVD-CWE-noinfo)
value: Undici is an HTTP/1.1 client, written from scratch for Node.js. Undici cleared Authorization and Proxy-Authorization headers for `fetch()`, but did not clear them for `undici.request()`. This vulnerability was patched in version(s) 5.28.4 and 6.11.1.

CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-04T16:15Z
Repository: https://github.com/nodejs/undici
Language: JavaScript
Files changed: lib/handler/redirect-handler.js, test/redirect-cross-origin-header.js
Commit date: 2024-04-02 12:41:40 +0200
Commit message: Merge pull request from GHSA-m4v8-wqvr-p9f7

Signed-off-by: Matteo Collina <hello@matteocollina.com>
Diff: diff --git a/lib/handler/redirect-handler.js b/lib/handler/redirect-handler.js
index 368ef520d..16a7b2150 100644
--- a/lib/handler/redirect-handler.js
+++ b/lib/handler/redirect-handler.js
@@ -199,13 +199,13 @@ function shouldRemoveHeader (header, removeContent, unknownOrigin) {
     return util.headerNameToString(header) === 'host'
   }
   if (removeContent && util.headerNameToString(header).startsWith('content-')) {
     return true
   }
-  if (unknownOrigin && (header.length === 13 || header.length === 6)) {
+  if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) {
     const name = util.headerNameToString(header)
-    return name === 'authorization' || name === 'cookie'
+    return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization'
   }
   return false
 }
 
 // https://tools.ietf.org/html/rfc7231#section-6.4
diff --git a/test/redirect-cross-origin-header.js b/test/redirect-cross-origin-header.js
new file mode 100644
index 000000000..451563cad
--- /dev/null
+++ b/test/redirect-cross-origin-header.js
@@ -0,0 +1,52 @@
+'use strict'
+
+const { test } = require('node:test')
+const { tspl } = require('@matteo.collina/tspl')
+const { createServer } = require('node:http')
+const { once } = require('node:events')
+const { request } = require('..')
+
+test('Cross-origin redirects clear forbidden headers', async (t) => {
+  const { strictEqual } = tspl(t, { plan: 6 })
+
+  const server1 = createServer((req, res) => {
+    strictEqual(req.headers.cookie, undefined)
+    strictEqual(req.headers.authorization, undefined)
+    strictEqual(req.headers['proxy-authorization'], undefined)
+
+    res.end('redirected')
+  }).listen(0)
+
+  const server2 = createServer((req, res) => {
+    strictEqual(req.headers.authorization, 'test')
+    strictEqual(req.headers.cookie, 'ddd=dddd')
+
+    res.writeHead(302, {
+      ...req.headers,
+      Location: `http://localhost:${server1.address().port}`
+    })
+    res.end()
+  }).listen(0)
+
+  t.after(() => {
+    server1.close()
+    server2.close()
+  })
+
+  await Promise.all([
+    once(server1, 'listening'),
+    once(server2, 'listening')
+  ])
+
+  const res = await request(`http://localhost:${server2.address().port}`, {
+    maxRedirections: 1,
+    headers: {
+      Authorization: 'test',
+      Cookie: 'ddd=dddd',
+      'Proxy-Authorization': 'test'
+    }
+  })
+
+  const text = await res.body.text()
+  strictEqual(text, 'redirected')
+})

Vulnerable code:   if (unknownOrigin && (header.length === 13 || header.length === 6)) {
    return name === 'authorization' || name === 'cookie'
Fixed code:   if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) {
    return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization'
'use strict'

const { test } = require('node:test')
const { tspl } = require('@matteo.collina/tspl')
const { createServer } = require('node:http')
const { once } = require('node:events')
const { request } = require('..')

test('Cross-origin redirects clear forbidden headers', async (t) => {
  const { strictEqual } = tspl(t, { plan: 6 })

  const server1 = createServer((req, res) => {
    strictEqual(req.headers.cookie, undefined)
    strictEqual(req.headers.authorization, undefined)
    strictEqual(req.headers['proxy-authorization'], undefined)

    res.end('redirected')
  }).listen(0)

  const server2 = createServer((req, res) => {
    strictEqual(req.headers.authorization, 'test')
    strictEqual(req.headers.cookie, 'ddd=dddd')

    res.writeHead(302, {
      ...req.headers,
      Location: `http://localhost:${server1.address().port}`
    })
    res.end()
  }).listen(0)

  t.after(() => {
    server1.close()
    server2.close()
  })

  await Promise.all([
    once(server1, 'listening'),
    once(server2, 'listening')
  ])

  const res = await request(`http://localhost:${server2.address().port}`, {
    maxRedirections: 1,
    headers: {
      Authorization: 'test',
      Cookie: 'ddd=dddd',
      'Proxy-Authorization': 'test'
    }
  })

  const text = await res.body.text()
  strictEqual(text, 'redirected')
})