GitHub Advisoryadvisory · npm

GHSA-hcpx-6fm6-wx23 Axios form serializer maxDepth bypass via {} metatoken

MEDIUM Published 2026-07-20 Modified 2026-09-08 Official page ↗
Aliases
CVE-2026-67321
Ecosystem
npm
Packages
axios
Severity
Moderate
CVSS vector
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
Weaknesses
CWE-674
Reviewed
2026-07-20

Affected packages

• npm axios: >= 0.31.1, < 0.33.0 → fixed in 0.33.0 • npm axios: >= 1.15.1, < 1.18.0 → fixed in 1.18.0

Description

Summary Axios versions in the fixed lines for GHSA-62hf-57xw-28j9 still contain an incomplete depth-limit bypass in `lib/helpers/toFormData.js`. When serializing an object with a top-level key ending in `{}`, axios calls `JSON.stringify()` on that value before the `formSerializer.maxDepth` guard can inspect the nested structure. An attacker who can control object keys and nested values passed by an application into axios form or parameter serialization can trigger a raw `RangeError: Maximum call stack size exceeded`, causing a denial of service in the affected request path. Impact The impact is availability only. No confidentiality or integrity impact was confirmed. Server-side applications are the primary concern when they accept user-controlled input and pass it into axios as `data` or `params` for `multipart/form-data`, `application/x-www-form-urlencoded`, or default parameter serialization. Browser impact is limited to the page or request context unless the application builds a broader failure mode around the thrown exception. The attack requires control over a top-level object key ending in `{}` and a deeply nested object value. The option `formSerializer.metaTokens: false` is not a workaround because it only changes the emitted key name; the value is still stringified. Affected Functionality Affected paths include: • `lib/helpers/toFormData.js` when a top-level key ends with `{}`. • `lib/helpers/toURLEncodedForm.js`, which delegates to `helpers.defaultVisitor`. • `lib/helpers/AxiosURLSearchParams.js`, used by default params serialization. • Request transforms in `lib/defaults/index.js` when object data is serialized as `multipart/form-data` or `application/x-www-form-urlencoded`. Unaffected paths include: • Already-created `FormData` or `URLSearchParams` values that axios does not walk with `toFormData`. • Custom `paramsSerializer.serialize` implementations that do not call axios `toFormData`. • Non-`{}` deeply nested values in `toFormData`, which hit `ERR_FORM_DATA_DEPTH_EXCEEDED` as intended. Technical Details In `lib/helpers/toFormData.js`, `defaultVisitor()` handles top-level keys ending in `{}` before recursive traversal: ```js if (value && !path && typeof value === 'object') { if (utils.endsWith(key, '{}')) { key = metaTokens ? key : key.slice(0, -2); value = JSON.stringify(value); } } ``` The depth guard is in `build()`: ```js if (depth > maxDepth) { throw new AxiosError( 'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth, AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED ); } ``` For `{}` metatoken values, `build()` only sees the top-level property. The nested value is handed directly to native `JSON.stringify()`, which recurses internally and can throw `RangeError` before axios emits the intended `AxiosError`. Proof of Concept of Attack Safe local PoC with no network I/O: ```js import toFormData from './lib/helpers/toFormData.js'; function buildDeep(depth) { const head = {}; let cur = head; for (let i = 0; i Original Report Summary The `maxDepth=100` guard added in axios 1.15.0 to fix GHSA-62hf-57xw-28j9 lives inside the `build()` recursion in `lib/helpers/toFormData.js`. The default visitor at `lib/helpers/toFormData.js:166-170` still has a top-level shortcut that calls `JSON.stringify(value)` whenever a key ends in `'{}'`, before `build()` ever sees the nested value. JSON.stringify on a deeply nested object stack-overflows with `RangeError: Maximum call stack size exceeded`, which propagates synchronously out of the axios call. The exact attacker-data flow that the original advisory described (proxy-style code that forwards client JSON into `axios({ data, params })`) still crashes the process at depth ~3000 on a default Node.js stack, despite v1.16.0 being patched. Details Affected: axios 1.15.0 - 1.16.0 (every released version that carries the GHSA-62hf-57xw-28j9 fix). The bug is reachable from any code path that hits `toFormData`, which includes: • `axios.post(url, data, { headers: { 'content-type': 'application/x-www-form-urlencoded' } })` -> `defaults.transformRequest` -> `toURLEncodedForm(data)` -> `toFormData` • `axios.post(url, data, { headers: { 'content-type': 'multipart/form-data' } })` -> same path via `toFormData` • `axios.get(url, { params })` -> `buildURL` -> `new AxiosURLSearchParams(params)` -> `toFormData` Vulnerable code, `lib/helpers/toFormData.js`: ```javascript // 156 function defaultVisitor(value, key, path) { // 165 if (value && !path && typeof value === 'object') { // 166 if (utils.endsWith(key, '{}')) { // 167 // eslint-disable-next-line no-param-reassign // 168 key = metaTokens ? key : key.slice(0, -2); // 169 // eslint-disable-next-line no-param-reassign // 170 value = JSON.stringify(value); // maxDepth) { // 215 throw new AxiosError( // 216 'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth, // 217 AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED // 218 ); ``` The `'{}'` shortcut runs in `defaultVisitor`, which is invoked from inside `build()` for top-level keys (the `!path` clause at line 165 means the shortcut only triggers at top level, where `path` is `undefined`). At that point `depth === 0` and the maxDepth check has already passed; the recursion-aware guard never sees the nested value because `defaultVisitor` reassigns `value = JSON.stringify(value)` and returns the rendered string straight to `formData.append`. JSON.stringify itself is recursive in V8 and stack-overflows on deeply nested objects, throwing `RangeError` synchronously. The behaviour is independent of the `metaTokens` option: line 168 only changes whether `'{}'` stays on the key name, line 170 stringifies regardless. `toURLEncodedForm`'s wrapper visitor in `lib/helpers/toURLEncodedForm.js:11-14` falls through to the same `defaultVisitor`, so the form-encoded path is also affected. The attacker payload is a single top-level key ending in `'{}'` whose value is a nested object. The keys themselves do not have to be deep, so the payload is small to send (a few KB of `{"x":{"x":...}}` produces enough nesting to overflow). The original advisory's threat model -- a server that forwards `req.body` or `req.query` into axios -- is unchanged: ```javascript app.post('/forward', async (req, res) => { await axios.post('https://upstream/api', req.body); // req.body attacker-controlled res.send('ok'); }); // attacker POST /forward with content-type: application/x-www-form-urlencoded // body: {"evil{}": } // -> JSON.stringify recurses inside defaultVisitor -> RangeError -> handler crashes ``` The error is not an `AxiosError`; it is a raw `RangeError` thrown from the stringifier, so handlers that look for `err.code === 'ERR_FORM_DATA_DEPTH_EXCEEDED'` (the documented signal that the maxDepth guard fired) do not see it. Synchronous startup paths or worker threads still take the whole process down. The fix is to also depth-limit (…

Related

Alias · CVE (1)

  • CVE-2026-67321 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… vulnerability

Weakness · CWE (1)

  • CWE-674 Uncontrolled Recursion weakness

References