Summary
Axios: Deep formToJSON Key Recursion Can Cause Denial of Service
Axios versions starting with 0.28.0 contain uncontrolled recursion in formDataToJSON, which is exposed as axios.formToJSON() and used internally when axios serialises FormData with Content-Type: application/json.
If an application passes attacker-controlled FormData field names to this functionality, a field name with thousands of nested bracket segments can exhaust the JavaScript call stack and cause denial of service for that request or, in applications without appropriate error handling, process termination.
Affected Functionality
Affected functionality:
axios.formToJSON(formData)- Named ESM export
formToJSON - Default
transformRequestbehaviour forFormDatawhenContent-Typecontainsapplication/json
Unaffected functionality:
- Normal multipart
FormDatasubmission without JSON serialisation toFormData, which already enforces amaxDepthguard- Axios versions
<=0.27.2, whereformDataToJSONwas not present
Technical Details
The vulnerable code is in lib/helpers/formDataToJSON.js.
parsePropPath() splits a field name such as a[x][x][x] into path segments. buildPath() then recursively processes one segment per call without enforcing a maximum depth:
const result = buildPath(path, value, target[name], index);
A key with thousands of bracket-delimited segments causes thousands of recursive calls and can exceed the JavaScript engine's call stack limit.
Relevant source locations:
lib/helpers/formDataToJSON.jscontains the unbounded recursivebuildPath().lib/axios.jsexposes the helper asaxios.formToJSON.index.jsexposesformToJSONas a named export.index.d.tsandindex.d.ctsdeclare the public API.lib/defaults/index.jscallsformDataToJSON(data)when JSON-serializingFormData.
The inverse helper, toFormData, already enforces maxDepth and throws AxiosError with ERR_FORM_DATA_DEPTH_EXCEEDED, but formDataToJSON does not have an equivalent guard.
Proof of Concept of Attack
import axios from 'axios';
const fd = new FormData();
fd.append('a' + '[x]'.repeat(15000), 'value');
try {
axios.formToJSON(fd);
console.log('not vulnerable');
} catch (e) {
console.log(`${e.constructor.name}: ${e.message}`);
}
Expected result on affected versions:
RangeError: Maximum call stack size exceeded
The same condition can be reached via an axios request transformation when attacker-controlled FormData is sent with Content-Type: application/json.
Workarounds
Applications can reject or normalise untrusted form field names before calling axios.formToJSON().
Applications can avoid sending untrusted FormData through axios as JSON unless JSON conversion is required.
Applications should catch errors around formToJSON() or axios requests that transform untrusted FormData.
An uncontrolled recursion vulnerability in formDataToJSON allows any user who controls FormData input to crash a Node.js process with a single request. The function recurses once per bracket-delimited segment in a FormData key name with no depth limit, so a key like a[x][x][x]... with 15,000+ segments exhausts the call stack. This is a denial-of-service that kills the process via an unrecoverable RangeError. The inverse function toFormData already enforces a maxDepth limit (default 100) for exactly this reason, formDataToJSON lacks the equivalent guard.
Details
Vulnerable function: buildPath in lib/helpers/formDataToJSON.js, lines 50–82.
buildPath(path, value, target, index) is called recursively, once per segment in the parsed property path, with no depth check:
// lib/helpers/formDataToJSON.js, lines 50–82
function buildPath(path, value, target, index) {
let name = path[index++]; // advance one level
if (name === '__proto__') return true;
// ...
if (!isLast) {
// ...
const result = buildPath(path, value, target[name], index); // recurse, NO depth guard
// ...
}
}
The key is first split into segments by parsePropPath (line 17), which extracts every [segment] via regex. A key with 15,000 bracket pairs produces a 15,001-element array, causing 15,001 recursive calls, well beyond the V8 default stack limit (~10,000–15,000 frames).
formDataToJSON is a public API consumed two ways:
Directly by consumers, exported as
axios.formToJSON()(lib/axios.js:80), with TypeScript declarations in bothindex.d.ts:699andindex.d.cts:708, and documented in the API reference in four languages (docs/pages/advanced/api-reference.md).Internally by
transformRequest, called atlib/defaults/index.js:56when the request body isFormDataandContent-Typecontainsapplication/json:return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data;
Contrast with toFormData: The inverse function (lib/helpers/toFormData.js:118) enforces maxDepth (default 100) and throws AxiosError with code ERR_FORM_DATA_DEPTH_EXCEEDED when exceeded. formDataToJSON has no equivalent protection.
PoC
Requires only Node.js and an unmodified axios v1.x install:
import formDataToJSON from 'axios/lib/helpers/formDataToJSON.js';
// Build a FormData with a single key containing 15,000 nested bracket segments
const fd = new FormData();
const key = "a" + "[x]".repeat(15000);
fd.append(key, "value");
try {
formDataToJSON(fd);
console.log("Not vulnerable");
} catch (e) {
console.log(e.constructor.name + ": " + e.message);
// RangeError: Maximum call stack size exceeded
}
Verified output on Node.js 22.22.3 against axios v1.16.1 (current v1.x HEAD):
RangeError: Maximum call stack size exceeded
The process crashes. In a server context (e.g., Express middleware calling axios.formToJSON() on an uploaded form), a single crafted request terminates the process.
Impact
Denial of Service (process crash). Any unauthenticated user who can submit FormData to a Node.js application that passes it through axios.formToJSON(), or that sends it as a JSON-serialized FormData body via axios, can crash the server process with a single request. The RangeError from stack exhaustion is unrecoverable in many contexts (it cannot be reliably caught when the stack is already full). No authentication or special privileges are required; the attacker only needs to control a FormData key name.
Impact
Applications are affected only when untrusted users can control FormData key names that are converted through axios.
Affected paths include direct use of axios.formToJSON() on untrusted FormData and axios requests in which attacker-controlled FormData is sent with Content-Type: application/json.
The observed failure is RangeError: Maximum call stack size exceeded. In local testing, this error is catchable, so process-wide crash depends on the consuming application's error handling and runtime behaviour.
Crafted input forces the application to consume excessive CPU, memory, or other resources, degrading or denying service. Typical impact: denial of service.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
axios to 0.33.0 or later; axios to 1.18.0 or later
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is GHSA-PMV8-RQ9R-6J72? GHSA-PMV8-RQ9R-6J72 is a medium-severity uncontrolled resource consumption vulnerability in axios (npm), affecting versions >= 0.28.0, < 0.33.0. It is fixed in 0.33.0, 1.18.0. Crafted input forces the application to consume excessive CPU, memory, or other resources, degrading or denying service.
- Which versions of axios are affected by GHSA-PMV8-RQ9R-6J72? axios (npm) versions >= 0.28.0, < 0.33.0 is affected.
- Is there a fix for GHSA-PMV8-RQ9R-6J72? Yes. GHSA-PMV8-RQ9R-6J72 is fixed in 0.33.0, 1.18.0. Upgrade to this version or later.
- Is GHSA-PMV8-RQ9R-6J72 exploitable, and should I be worried? Whether GHSA-PMV8-RQ9R-6J72 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-PMV8-RQ9R-6J72 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-PMV8-RQ9R-6J72?
- Upgrade
axiosto 0.33.0 or later - Upgrade
axiosto 1.18.0 or later
- Upgrade