{"id":"CVE-2026-59952","revision":null,"records":[{"revision":"043a089e901141e0b17305faada73caf","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-59952","aliases":[],"title":"Valibot: record() issue paths can make flatten() throw for inherited Object property names","description":"Valibot helps validate data using a schema. Versions prior to 1.4.2 can throw a TypeError inside its flatten() helper when validation issues contain attacker-controlled object keys such as toString, valueOf, or hasOwnProperty. The issue is reachable through normal record() validation. record() intentionally filters __proto__, prototype, and constructor, but it still accepts other own keys that collide with inherited Object.prototype properties. If the record key schema or value schema rejects such an entry, Valibot creates an issue path containing that key. Passing the resulting issues to Valibot's documented flatten() helper causes flatErrors.nested[dotPath] to resolve to the inherited method instead of an own error array, and the helper calls .push(...) on that function. This is not a global prototype pollution issue. The impact is availability/error handling: applications that validate user-controlled objects with record() and flatten validation errors for API responses can crash the request path with a TypeError instead of returning structured validation errors. This issue has been fixed in version 1.4.2.","state":0,"publishedAt":"2026-07-30T00:10:32.598+00:00","modifiedAt":"2026-07-30T14:42:29.575+00:00","affected":[{"ecosystem":"Vendor product","name":"valibot","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"open-circle\",\n                    \"product\": \"valibot\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 1.4.2\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 4.0","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","score":6.9,"severity":"MEDIUM"}],"references":["https://github.com/open-circle/valibot/security/advisories/GHSA-5qjj-4xww-7phc","https://github.com/open-circle/valibot/pull/1522","https://github.com/open-circle/valibot/commit/1bd01c304657cd0809cc92694360b6cc60f700bf","https://github.com/open-circle/valibot/releases/tag/v1.4.2"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-59952","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-59952","identifiers":["CVE-2026-59952"]},"retained":false,"historicalAffected":[]},{"revision":"de7d882011fb450d936af0690a1999f4","observedAt":"2026-10-25T14:19:22.811+00:00","statement":{"source":"nvd","id":"CVE-2026-59952","aliases":[],"title":"Valibot helps validate data using a schema. Versions prior to 1.4.2 can throw a TypeError inside its flatten() helper when validation issues contain attacker-controlled object keys such as toString, valueOf, or hasOwnProperty. The issue is …","description":"Valibot helps validate data using a schema. Versions prior to 1.4.2 can throw a TypeError inside its flatten() helper when validation issues contain attacker-controlled object keys such as toString, valueOf, or hasOwnProperty. The issue is reachable through normal record() validation. record() intentionally filters __proto__, prototype, and constructor, but it still accepts other own keys that collide with inherited Object.prototype properties. If the record key schema or value schema rejects such an entry, Valibot creates an issue path containing that key. Passing the resulting issues to Valibot's documented flatten() helper causes flatErrors.nested[dotPath] to resolve to the inherited method instead of an own error array, and the helper calls .push(...) on that function. This is not a global prototype pollution issue. The impact is availability/error handling: applications that validate user-controlled objects with record() and flatten validation errors for API responses can crash the request path with a TypeError instead of returning structured validation errors. This issue has been fixed in version 1.4.2.","state":0,"publishedAt":"2026-07-30T01:17:06.84+00:00","modifiedAt":"2026-10-23T22:10:00.16+00:00","affected":[],"metrics":[{"system":"CVSS 4.0 · security-advisories@github.com · Secondary","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/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","score":6.9,"severity":"MEDIUM"}],"references":["https://github.com/open-circle/valibot/commit/1bd01c304657cd0809cc92694360b6cc60f700bf","https://github.com/open-circle/valibot/pull/1522","https://github.com/open-circle/valibot/releases/tag/v1.4.2","https://github.com/open-circle/valibot/security/advisories/GHSA-5qjj-4xww-7phc"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-59952","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-755"],"productCriteria":[],"sourceDates":{"published":"2026-07-30T01:17:06.840","lastModified":"2026-10-23T22:10:00.160"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-59952","identifiers":["CVE-2026-59952"]},"retained":false,"historicalAffected":[]},{"revision":"271e9cf2ec8f44ce9a8dd95cc48ffb55","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-5qjj-4xww-7phc","aliases":["CVE-2026-59952"],"title":"Valibot: record() issue paths can make flatten() throw for inherited Object property names","description":"## Summary\n\n`valibot` 1.4.1 can throw a `TypeError` inside its `flatten()` helper when validation issues contain attacker-controlled object keys such as `toString`, `valueOf`, or `hasOwnProperty`.\n\nThe issue is reachable through normal `record()` validation. `record()` intentionally filters `__proto__`, `prototype`, and `constructor`, but it still accepts other own keys that collide with inherited `Object.prototype` properties. If the record key schema or value schema rejects such an entry, Valibot creates an issue path containing that key. Passing the resulting issues to Valibot's documented `flatten()` helper causes `flatErrors.nested[dotPath]` to resolve to the inherited method instead of an own error array, and the helper calls `.push(...)` on that function.\n\nThis is not a global prototype pollution issue. The impact is availability/error handling: applications that validate user-controlled objects with `record()` and flatten validation errors for API responses can crash the request path with a `TypeError` instead of returning structured validation errors.\n\n## Affected package\n\n- Ecosystem: npm\n- Package: `valibot`\n- Affected version verified: `1.4.1`\n- Fixed version: none known\n- Repository: `open-circle/valibot`\n- Current main ref tested by source review: `9bb6617`\n\n## Root cause\n\n`record()` uses `_isValidObjectKey()` before validating record entries. The helper blocks the three classic prototype pollution keys:\n\n```ts\nkey !== '__proto__' &&\nkey !== 'prototype' &&\nkey !== 'constructor'\n```\n\nIt does not block other inherited `Object.prototype` names such as `toString`, `valueOf`, and `hasOwnProperty`. These remain valid own JSON object keys and can appear in issue paths when either the record key schema or value schema rejects the entry.\n\n`flatten()` then creates nested error storage with an ordinary object:\n\n```ts\nflatErrors.nested = {};\n```\n\nFor a dot path such as `toString`, this check reads the inherited `Object.prototype.toString` function:\n\n```ts\nif (flatErrors.nested![dotPath]) {\n  flatErrors.nested![dotPath]!.push(issue.message);\n}\n```\n\nBecause the inherited function is truthy, `flatten()` calls `.push(...)` on a function and throws `TypeError: flatErrors.nested[dotPath].push is not a function`.\n\n## Impact\n\nA remote attacker can trigger this if an application:\n\n1. validates attacker-controlled JSON objects with `v.record(...)`;\n2. receives an invalid key or invalid value under a key such as `toString`;\n3. uses Valibot's `flatten(result.issues)` helper to prepare validation errors.\n\nThis is a common pattern in API/form validation: `safeParse()` collects issues and `flatten()` converts them into response-friendly error objects. Instead of a validation response, the request can hit an unexpected exception path.\n\nThe same root cause can also affect manually constructed issues or other schemas that place inherited Object property names into dot paths. I am reporting the `record()` path because it uses only public Valibot APIs and attacker-controlled JSON keys.\n\n## Local reproduction\n\nRun in a disposable directory:\n\n```bash\nnpm install valibot@1.4.1\nnode poc_record_flatten_inherited_key_dos.mjs\n```\n\nMinimal example:\n\n```js\nimport * as v from 'valibot';\n\nconst schema = v.record(v.string(), v.number());\nconst input = JSON.parse('{\"toString\":\"not-a-number\"}');\n\nconst result = v.safeParse(schema, input);\nconsole.log(result.success); // false\nconsole.log(result.issues[0].path.map((item) => item.key)); // [\"toString\"]\n\nv.flatten(result.issues); // TypeError\n```\n\nObserved output from `valibot@1.4.1`:\n\n```json\n{\n  \"name\": \"record value schema rejects attacker-controlled value\",\n  \"key\": \"toString\",\n  \"success\": false,\n  \"issueCount\": 1,\n  \"firstPath\": [\"toString\"],\n  \"firstMessage\": \"Invalid type: Expected number but received \\\"not-a-number\\\"\",\n  \"flattened\": {\n    \"ok\": false,\n    \"exception\": \"TypeError\",\n    \"message\": \"flatErrors.nested[dotPath].push is not a function\"\n  }\n}\n```\n\nThe local PoC also reproduces the same exception for `valueOf`, `hasOwnProperty`, `isPrototypeOf`, `propertyIsEnumerable`, and `toLocaleString`. A control case with an ordinary key produces normal flattened errors.\n\n## Duplicate checks performed before submission\n\n- npm metadata confirmed current `valibot` release is `1.4.1` and maps to `open-circle/valibot`.\n- `gh api repos/open-circle/valibot/private-vulnerability-reporting` returned `{\"enabled\":true}`.\n- `npm audit` for a clean project containing only `valibot@1.4.1` returned no vulnerabilities.\n- Repository advisories and the GitHub Advisory Database only returned the historical emoji ReDoS advisory fixed in `1.2.0`.\n- OSV exact-version query for npm `valibot` `1.4.1` returned no vulnerabilities.\n- Public issue/PR searches for `flatten toString`, `flatten hasOwnProperty`, `record toString`, `__proto__`, `constructor`, and `prototype pollution` did not find a matching disclosure of this `record()` issue-path / `flatten()` exception.\n- Reviewed related public PRs: `open-circle/valibot#67` added prototype pollution mitigation for `record()` by blacklisting `__proto__`, `prototype`, and `constructor`; it does not cover `flatten()` collisions with other inherited property names. `open-circle/valibot#1429` is an open plain-object / `record()` type semantics PR and does not disclose this `flatten()` exception behavior.\n\n## Suggested remediation\n\nUse null-prototype containers for flat error maps and/or perform own-property checks before appending:\n\n- Initialize `flatErrors.nested` as `Object.create(null)`.\n- Check nested entries with `Object.prototype.hasOwnProperty.call(flatErrors.nested, dotPath)` rather than truthiness.\n- Consider filtering or escaping unsafe dot path segments in `getDotPath()` / `flatten()`, including inherited Object property names.\n- Add regression tests for `flatten()` with paths `toString`, `valueOf`, `hasOwnProperty`, `__proto__`, `prototype`, and `constructor`.\n- Consider using the same hardening for other accumulator objects that store attacker-controlled keys.","state":0,"publishedAt":"2026-07-24T16:14:31+00:00","modifiedAt":"2026-07-24T16:33:10.281535+00:00","affected":[{"ecosystem":"npm","name":"valibot","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"1.4.2"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V4","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","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/open-circle/valibot/security/advisories/GHSA-5qjj-4xww-7phc","https://github.com/open-circle/valibot/pull/1522","https://github.com/open-circle/valibot/commit/1bd01c304657cd0809cc92694360b6cc60f700bf","https://github.com/open-circle/valibot","https://github.com/open-circle/valibot/releases/tag/v1.4.2"],"sourceUrl":"https://github.com/advisories/GHSA-5qjj-4xww-7phc","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-5qjj-4xww-7phc","identifiers":["GHSA-5qjj-4xww-7phc","CVE-2026-59952"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-26T11:36:23.2920201+00:00","complete":true,"records":1756,"lastAttemptAt":"2026-10-26T11:36:23.2920201+00:00","lastSuccessAt":"2026-10-26T11:36:23.5142731+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:36:23.5142748+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-5ba1945314e042c2ced14b33f3680fa71692812d925544312b218f5eb8a26e71-e7962156c388e029d9e5dbbcda79fbd40ade61bdd41d51546adb4696b3e29228","complete":true,"records":414161,"lastAttemptAt":"2026-10-26T11:36:10.3293998+00:00","lastSuccessAt":"2026-10-26T11:36:10.2537359+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:36:10.3293998+00:00","snapshotPublishedAt":"2026-10-26T11:28:55+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-43321610382942b8ad8c85917eab1546","complete":false,"records":4735,"lastAttemptAt":"2026-10-26T11:36:27.570174+00:00","lastSuccessAt":"2026-10-24T23:18:49.862+00:00","error":"InvalidDataException","lastVerifiedAt":"2026-10-24T23:18:49.862+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":4764,"expectedIdentifiers":13477},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-26T11:36:23.2920201+00:00","complete":true,"records":1093,"lastAttemptAt":"2026-10-26T11:36:23.2920201+00:00","lastSuccessAt":"2026-10-26T11:36:23.6271598+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:36:23.6271612+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-114096430e32489f93a3ae96f397b2db","complete":true,"records":13416,"lastAttemptAt":"2026-10-26T11:36:25.3338531+00:00","lastSuccessAt":"2026-10-26T11:36:25.326+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:36:25.326+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13477,"expectedIdentifiers":13477},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-2375a4e597279abf92e8747e140fe8da9d421877cd49cae3ac69a2cb78469b2c","complete":true,"records":9779,"lastAttemptAt":"2026-10-26T11:36:10.7420905+00:00","lastSuccessAt":"2026-10-24T13:19:53.1346702+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:36:10.7420905+00:00","snapshotPublishedAt":"2026-10-24T03:03:12+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-16ad89dfea13ab9e9b0108e63a759d63bd4559cc68b58491107d62ee35bbbaf2","complete":true,"records":1965,"lastAttemptAt":"2026-10-26T11:36:10.517478+00:00","lastSuccessAt":"2026-10-23T16:01:26.4487605+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:36:10.517478+00:00","snapshotPublishedAt":"2026-10-21T12:18:07+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-7d18ad8657bc2f95d55948e9e16b3c82a8ac06830df5ec8d356048b8420b72c3","complete":true,"records":2972,"lastAttemptAt":"2026-10-26T11:36:23.2053293+00:00","lastSuccessAt":"2026-10-26T11:36:23.0131888+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:36:23.2053293+00:00","snapshotPublishedAt":"2026-10-26T11:33:16+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-654e90fb44abcd729ddecf56d54c1b46ce87ca3181850e8bc65d5c8befceb659","complete":true,"records":230805,"lastAttemptAt":"2026-10-26T11:36:10.6367182+00:00","lastSuccessAt":"2026-10-26T08:22:30.1390577+00:00","error":null,"lastVerifiedAt":"2026-10-26T11:36:10.6367182+00:00","snapshotPublishedAt":"2026-10-26T07:48:28+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null}]}