mirror of
https://github.com/tenrok/axios.git
synced 2026-06-20 20:00:40 +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:** 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**)
|
- **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 - 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**)
|
- **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**)
|
- **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**)
|
- **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 }
|
? { ...userHttpsAgent.options, ...agentOptions }
|
||||||
: agentOptions;
|
: agentOptions;
|
||||||
agent = new HttpsProxyAgent(merged);
|
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;
|
agent[kAxiosInstalledTunnel] = true;
|
||||||
cache.set(key, agent);
|
cache.set(key, agent);
|
||||||
return 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 () => {
|
it('should surface a CONNECT 407 from the proxy as an AxiosError (issue #6320)', async () => {
|
||||||
const proxy = await new Promise((resolve, reject) => {
|
const proxy = await new Promise((resolve, reject) => {
|
||||||
const p = http.createServer();
|
const p = http.createServer();
|
||||||
@@ -3369,11 +3438,11 @@ describe('supports http with nodejs', () => {
|
|||||||
assert.ok(options.agent instanceof HttpsProxyAgent);
|
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 userAgent = new https.Agent({ rejectUnauthorized: false, ca: 'sentinel-ca' });
|
||||||
const options = buildOptions();
|
const options = buildOptions();
|
||||||
__setProxy(options, proxyConfig, 'https://example.com/', false, userAgent);
|
__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.rejectUnauthorized, false);
|
||||||
assert.strictEqual(options.agent.proxy.ca, 'sentinel-ca');
|
assert.strictEqual(options.agent.proxy.ca, 'sentinel-ca');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user