2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-17 19:21:29 +03:00

test: add regression coverage for GHSA-pf86-5x62-jrwf gadgets

This commit is contained in:
Jason Saayman
2026-04-18 15:15:30 +02:00
parent 74a05bc336
commit 48fd644be1
+96
View File
@@ -1,13 +1,25 @@
/* eslint-disable no-prototype-builtins */
import { afterEach, describe, it } from 'vitest';
import assert from 'assert';
import http from 'http';
import utils from '../../lib/utils.js';
import mergeConfig from '../../lib/core/mergeConfig.js';
import defaults from '../../lib/defaults/index.js';
import axios from '../../index.js';
describe('Prototype Pollution Protection', () => {
afterEach(() => {
// Clean up any pollution that might have occurred.
delete Object.prototype.polluted;
delete Object.prototype.parseReviver;
delete Object.prototype.transport;
delete Object.prototype.transformRequest;
delete Object.prototype.transformResponse;
delete Object.prototype.formSerializer;
delete Object.prototype.httpVersion;
delete Object.prototype.lookup;
delete Object.prototype.family;
delete Object.prototype.http2Options;
});
describe('utils.merge', () => {
@@ -207,5 +219,89 @@ describe('Prototype Pollution Protection', () => {
assert.strictEqual(result.headers.common.Accept, 'application/json');
assert.strictEqual(result.headers.common['Content-Type'], 'application/json');
});
// GHSA-pf86-5x62-jrwf gadget 3: polluted transformRequest/Response must not
// replace the safe defaults through inherited reads during merge.
it('should not inherit polluted transformRequest from Object.prototype', () => {
const polluted = () => 'attacker';
Object.prototype.transformRequest = polluted;
const result = mergeConfig({ transformRequest: [(d) => d] }, { url: '/x' });
assert.notStrictEqual(result.transformRequest, polluted);
assert.ok(Array.isArray(result.transformRequest));
});
it('should not inherit polluted transformResponse from Object.prototype', () => {
const polluted = () => 'attacker';
Object.prototype.transformResponse = polluted;
const result = mergeConfig({ transformResponse: [(d) => d] }, { url: '/x' });
assert.notStrictEqual(result.transformResponse, polluted);
assert.ok(Array.isArray(result.transformResponse));
});
});
// GHSA-pf86-5x62-jrwf gadget 1: parseReviver read via prototype chain.
describe('defaults.transformResponse parseReviver', () => {
it('should ignore Object.prototype.parseReviver when parsing JSON', () => {
let reviverCalled = false;
Object.prototype.parseReviver = function polluted(k, v) {
reviverCalled = true;
if (k === 'role') return 'admin';
return v;
};
const ctx = { transitional: defaults.transitional };
const result = defaults.transformResponse[0].call(
ctx,
'{"role":"user","balance":100}'
);
assert.strictEqual(reviverCalled, false);
assert.strictEqual(result.role, 'user');
assert.strictEqual(result.balance, 100);
});
it('should ignore Object.prototype.responseType', () => {
Object.prototype.responseType = 'json';
const ctx = { transitional: defaults.transitional };
// Non-JSON string body must be returned as-is; polluted responseType must
// not force strict JSON parsing.
const result = defaults.transformResponse[0].call(ctx, 'plain text');
assert.strictEqual(result, 'plain text');
delete Object.prototype.responseType;
});
});
// GHSA-pf86-5x62-jrwf gadget 2: http adapter must not read config.transport
// (or related keys) from Object.prototype.
describe('http adapter prototype reads', () => {
it('should not invoke Object.prototype.transport on a request', async () => {
let hijackCalled = false;
Object.prototype.transport = {
request(options, handleResponse) {
hijackCalled = true;
return http.request(options, handleResponse);
},
};
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end('{"ok":true}');
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const { port } = server.address();
try {
const res = await axios.get(`http://127.0.0.1:${port}/`);
assert.strictEqual(res.data.ok, true);
assert.strictEqual(hijackCalled, false);
} finally {
await new Promise((resolve) => server.close(resolve));
}
}, 10000);
});
});