From f73f06985417081986d323ab69f41dc390d75b9a Mon Sep 17 00:00:00 2001 From: greymoth-jp Date: Mon, 29 Jun 2026 13:17:29 +0900 Subject: [PATCH] Strip credential headers when a redirect changes scheme on the same host --- lib/needle.js | 7 +++++++ lib/utils.js | 8 ++++++++ test/redirect_spec.js | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/lib/needle.js b/lib/needle.js index a7f1627c3..1f1b93bae 100644 --- a/lib/needle.js +++ b/lib/needle.js @@ -562,6 +562,13 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data, // else delete config.headers['cookie']; } + + // the host and port match, but a scheme change (e.g. https -> http) is + // still cross-origin, so authentication headers must not be carried over. + if (!utils.protocols_match(headers.location, uri)) { + delete config.headers['authorization']; + delete config.headers['proxy-authorization']; + } } else { delete config.headers['cookie']; delete config.headers['authorization']; diff --git a/lib/utils.js b/lib/utils.js index b06c552c7..15faa0313 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -20,6 +20,13 @@ function host_and_ports_match(url1, url2) { == String(b.port || (b.protocol == 'https:' ? 443 : 80)); } +function protocols_match(url1, url2) { + if (url1.indexOf('http') < 0) url1 = 'http://' + url1; + if (url2.indexOf('http') < 0) url2 = 'http://' + url2; + + return url.parse(url1).protocol == url.parse(url2).protocol; +} + // returns false if a no_proxy host or pattern matches given url function should_proxy_to(uri) { var no_proxy = get_env_var(['NO_PROXY'], true); @@ -103,6 +110,7 @@ module.exports = { resolve_url: resolve_url, get_env_var: get_env_var, host_and_ports_match: host_and_ports_match, + protocols_match: protocols_match, should_proxy_to: should_proxy_to, parse_content_type: parse_content_type, is_stream: is_stream, diff --git a/test/redirect_spec.js b/test/redirect_spec.js index 5b97c5a8f..ffb31370f 100644 --- a/test/redirect_spec.js +++ b/test/redirect_spec.js @@ -462,4 +462,44 @@ describe('redirects', function() { }) + describe('and redirected to the same host and port over a downgraded scheme', function() { + + // The origin is the scheme, host and port together, so a redirect that only + // changes the scheme (https -> http on the same host and port) is still + // cross-origin and must not carry the original credential headers along. + + before(function() { + needle.defaults({ follow_max: 1 }); + }) + + after(function() { + needle.defaults({ follow_max: 0 }); + }) + + it('strips credential headers from the redirected request', function(done) { + code = 301; + location = 'http://127.0.0.1:' + ports.https + '/hello'; + spies.http.reset(); + + var opts = { + rejectUnauthorized: false, + headers: { + authorization: 'Bearer secret-token', + cookie: 'sid=secret', + 'proxy-authorization': 'Basic secret' + } + }; + + needle.get('https://127.0.0.1:' + ports.https + '/hello', opts, function() { + spies.http.callCount.should.be.above(0); + var sent = spies.http.args[0][0].headers; + should.not.exist(sent['authorization']); + should.not.exist(sent['cookie']); + should.not.exist(sent['proxy-authorization']); + done(); + }); + }) + + }) + });