From 95a2cbdbd2e0b36049ac1977499c33745cd266a7 Mon Sep 17 00:00:00 2001 From: DysektAI <33468668+DysektAI@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:53:14 -0400 Subject: [PATCH 1/2] fix(deps): pin follow-redirects credential-stripping patch --- package-lock.json | 6 ++-- package.json | 3 +- test/redirect-security.test.js | 60 ++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 test/redirect-security.test.js diff --git a/package-lock.json b/package-lock.json index 0118c0f..ce36493 100644 --- a/package-lock.json +++ b/package-lock.json @@ -758,9 +758,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", "funding": [ { "type": "individual", diff --git a/package.json b/package.json index d54ba6b..59ac0f6 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "overrides": { "undici": "^6.28.0", "ws": "^8.21.0", - "shell-quote": "1.12.0" + "shell-quote": "1.12.0", + "follow-redirects": "1.16.1" }, "devDependencies": { "concurrently": "^9.2.4" diff --git a/test/redirect-security.test.js b/test/redirect-security.test.js new file mode 100644 index 0000000..cd986e6 --- /dev/null +++ b/test/redirect-security.test.js @@ -0,0 +1,60 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import { once } from "node:events"; +import followRedirects from "follow-redirects"; + +async function redirectedHeaders(t, headers, sensitiveHeaders = []) { + let received; + const target = http.createServer((req, res) => { + received = req.headers; + res.end("ok"); + }); + target.listen(0, "127.0.0.1"); + await once(target, "listening"); + const origin = http.createServer((req, res) => { + req.resume(); + res.writeHead(302, { Location: `http://127.0.0.1:${target.address().port}/` }); + res.end(); + }); + origin.listen(0, "127.0.0.1"); + await once(origin, "listening"); + t.after(async () => { + for (const server of [origin, target]) { + server.closeAllConnections(); + await new Promise(resolve => server.close(resolve)); + } + }); + if (Array.isArray(headers)) { + headers = ["Host", `127.0.0.1:${origin.address().port}`, ...headers]; + } + await new Promise((resolve, reject) => { + const request = followRedirects.http.get({ + protocol: "http:", hostname: "127.0.0.1", port: origin.address().port, + path: "/", headers, sensitiveHeaders + }, response => { + response.resume(); + response.on("end", resolve); + response.on("error", reject); + }); + request.on("error", reject); + }); + assert.ok(received, "the redirected target must receive a request"); + return received; +} + +test("redirects strip array-form credentials across origins", { timeout: 5000 }, async t => { + const headers = await redirectedHeaders(t, ["Authorization", "test-marker", "Cookie", "test-cookie", "Proxy-Authorization", "test-proxy"]); + for (const name of ["authorization", "cookie", "proxy-authorization"]) { + assert.equal(headers[name], undefined, `${name} must not cross origins`); + } +}); + +test("redirects strip sensitive header names containing regex metacharacters", { timeout: 5000 }, async t => { + const headers = await redirectedHeaders(t, { + "X-Api^Key": "test-marker", "X-Token|Secret": "test-marker", "X-Control": "preserved" + }, ["X-Api^Key", "X-Token|Secret"]); + assert.equal(headers["x-api^key"], undefined); + assert.equal(headers["x-token|secret"], undefined); + assert.equal(headers["x-control"], "preserved"); +}); From e38b89589aaff779ce00b023c779b71c637cc346 Mon Sep 17 00:00:00 2001 From: DysektAI <33468668+DysektAI@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:48:02 -0400 Subject: [PATCH 2/2] test(deps): resolve redirects from the Axios installation --- test/redirect-security.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/redirect-security.test.js b/test/redirect-security.test.js index cd986e6..c9bc64d 100644 --- a/test/redirect-security.test.js +++ b/test/redirect-security.test.js @@ -2,7 +2,11 @@ import test from "node:test"; import assert from "node:assert/strict"; import http from "node:http"; import { once } from "node:events"; -import followRedirects from "follow-redirects"; +import { createRequire } from "node:module"; + +// Exercise Axios's actual dependency, even when it is nested rather than hoisted. +const axiosRequire = createRequire(createRequire(import.meta.url).resolve("axios")); +const followRedirects = axiosRequire("follow-redirects"); async function redirectedHeaders(t, headers, sensitiveHeaders = []) { let received;