mirror of
https://github.com/tenrok/axios.git
synced 2026-06-17 19:21:29 +03:00
fix(http): preserve TLS options for proxy tunnels (#10957)
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
- **Types:** Add the missing readonly `name: 'CanceledError'` declaration to CommonJS `CanceledError` typings to match the ESM declarations. (**#10922**)
|
||||
- **Types:** Correct the CommonJS `isCancel` type guard to narrow cancellation errors to `CanceledError<T>`, matching the ESM declaration. (**#10952**)
|
||||
- **HTTP Adapter - Auth on Redirect:** HTTP Basic credentials supplied via `config.auth` are now restored on same-origin redirects, fixing a regression caused by `follow-redirects` >= 1.15.8 that broke `POST` requests answered with a 303 Location. Cross-origin redirects continue to drop credentials, preserving the existing T-R2 mitigation in `THREATMODEL.md`. (**#6929**)
|
||||
- **HTTP Adapter - Proxy TLS:** Preserve `httpsAgent` TLS options such as `ca` and `rejectUnauthorized` for HTTPS origins reached through a CONNECT proxy tunnel. (**#10953**)
|
||||
- **HTTP Adapter - Socket Path:** Ignore inherited `socketPath` and `allowedSocketPaths` config values when building Node.js requests, preventing prototype-pollution SSRF via Unix sockets. (**#10901**)
|
||||
- **React Native FormData:** Clear the default `Content-Type` header for React Native `FormData` requests so Android can build multipart bodies with the correct boundary. (**#10898**)
|
||||
- **Request Data:** Preserve enumerable symbol keys when merging plain request data before `transformRequest`. (**#6392**)
|
||||
|
||||
@@ -112,6 +112,14 @@ function getTunnelingAgent(agentOptions, userHttpsAgent) {
|
||||
? { ...userHttpsAgent.options, ...agentOptions }
|
||||
: agentOptions;
|
||||
agent = new HttpsProxyAgent(merged);
|
||||
if (userHttpsAgent && userHttpsAgent.options) {
|
||||
const originTLSOptions = { ...userHttpsAgent.options };
|
||||
const callback = agent.callback;
|
||||
agent.callback = function axiosTunnelingAgentCallback(req, opts) {
|
||||
// HttpsProxyAgent v5 reads callback opts for the post-CONNECT origin TLS upgrade.
|
||||
return callback.call(this, req, { ...originTLSOptions, ...opts });
|
||||
};
|
||||
}
|
||||
agent[kAxiosInstalledTunnel] = true;
|
||||
cache.set(key, agent);
|
||||
return agent;
|
||||
|
||||
@@ -2015,6 +2015,75 @@ describe('supports http with nodejs', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should apply httpsAgent TLS options to CONNECT-tunneled origins (issue #10953)', async () => {
|
||||
const tlsOptions = {
|
||||
key: fs.readFileSync(path.join(adaptersTestsDir, 'key.pem')),
|
||||
cert: fs.readFileSync(path.join(adaptersTestsDir, 'cert.pem')),
|
||||
};
|
||||
|
||||
const origin = await new Promise((resolve, reject) => {
|
||||
const s = https.createServer(tlsOptions, (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('trusted-through-agent');
|
||||
});
|
||||
s.listen(0, 'localhost', () => resolve(s));
|
||||
s.on('error', reject);
|
||||
});
|
||||
|
||||
const captured = { plaintext: 0, connectTargets: [] };
|
||||
const upstreamSockets = [];
|
||||
const proxy = await new Promise((resolve, reject) => {
|
||||
const p = http.createServer(() => {
|
||||
captured.plaintext += 1;
|
||||
});
|
||||
p.on('connect', (req, clientSocket, head) => {
|
||||
captured.connectTargets.push(req.url);
|
||||
const [host, port] = req.url.split(':');
|
||||
const upstream = net.connect(Number(port), host, () => {
|
||||
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
||||
if (head && head.length) upstream.write(head);
|
||||
upstream.pipe(clientSocket);
|
||||
clientSocket.pipe(upstream);
|
||||
});
|
||||
upstreamSockets.push(upstream);
|
||||
upstream.on('error', () => clientSocket.destroy());
|
||||
clientSocket.on('error', () => upstream.destroy());
|
||||
});
|
||||
p.listen(0, '127.0.0.1', () => resolve(p));
|
||||
p.on('error', reject);
|
||||
});
|
||||
|
||||
const httpsAgent = new https.Agent({ ca: tlsOptions.cert });
|
||||
|
||||
try {
|
||||
const response = await axios.get(`https://localhost:${origin.address().port}/`, {
|
||||
httpsAgent,
|
||||
proxy: {
|
||||
host: '127.0.0.1',
|
||||
port: proxy.address().port,
|
||||
protocol: 'http',
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(response.data, 'trusted-through-agent');
|
||||
assert.strictEqual(captured.plaintext, 0, 'proxy must not see plaintext HTTPS requests');
|
||||
assert.strictEqual(captured.connectTargets.length, 1, 'proxy should see exactly one CONNECT');
|
||||
assert.ok(
|
||||
captured.connectTargets[0].startsWith(`localhost:${origin.address().port}`),
|
||||
`CONNECT should target the origin host:port, got ${captured.connectTargets[0]}`
|
||||
);
|
||||
} finally {
|
||||
httpsAgent.destroy();
|
||||
for (const s of upstreamSockets) s.destroy();
|
||||
origin.closeAllConnections?.();
|
||||
proxy.closeAllConnections?.();
|
||||
origin.close();
|
||||
proxy.close();
|
||||
origin.unref?.();
|
||||
proxy.unref?.();
|
||||
}
|
||||
});
|
||||
|
||||
it('should surface a CONNECT 407 from the proxy as an AxiosError (issue #6320)', async () => {
|
||||
const proxy = await new Promise((resolve, reject) => {
|
||||
const p = http.createServer();
|
||||
@@ -3369,11 +3438,11 @@ describe('supports http with nodejs', () => {
|
||||
assert.ok(options.agent instanceof HttpsProxyAgent);
|
||||
});
|
||||
|
||||
it('forwards user httpsAgent options to the tunneling agent so origin TLS uses them', () => {
|
||||
it('includes user httpsAgent options in the tunneling agent constructor options', () => {
|
||||
const userAgent = new https.Agent({ rejectUnauthorized: false, ca: 'sentinel-ca' });
|
||||
const options = buildOptions();
|
||||
__setProxy(options, proxyConfig, 'https://example.com/', false, userAgent);
|
||||
// HttpsProxyAgent v5 surfaces the merged constructor options on `.proxy`.
|
||||
// Origin TLS behavior is covered by the issue #10953 integration test.
|
||||
assert.strictEqual(options.agent.proxy.rejectUnauthorized, false);
|
||||
assert.strictEqual(options.agent.proxy.ca, 'sentinel-ca');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user