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.
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.
valibot1.4.1open-circle/valibot9bb6617record() uses _isValidObjectKey() before validating record entries. The helper blocks the three classic prototype pollution keys:
key !== '__proto__' &&
key !== 'prototype' &&
key !== 'constructor'
It 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.
flatten() then creates nested error storage with an ordinary object:
flatErrors.nested = {};
For a dot path such as toString, this check reads the inherited Object.prototype.toString function:
if (flatErrors.nested![dotPath]) {
flatErrors.nested![dotPath]!.push(issue.message);
}
Because the inherited function is truthy, flatten() calls .push(...) on a function and throws TypeError: flatErrors.nested[dotPath].push is not a function.
A remote attacker can trigger this if an application:
v.record(...);toString;flatten(result.issues) helper to prepare validation errors.This 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.
The 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.
Run in a disposable directory:
npm install [email protected]
node poc_record_flatten_inherited_key_dos.mjs
Minimal example:
import * as v from 'valibot';
const schema = v.record(v.string(), v.number());
const input = JSON.parse('{"toString":"not-a-number"}');
const result = v.safeParse(schema, input);
console.log(result.success); // false
console.log(result.issues[0].path.map((item) => item.key)); // ["toString"]
v.flatten(result.issues); // TypeError
Observed output from [email protected]:
{
"name": "record value schema rejects attacker-controlled value",
"key": "toString",
"success": false,
"issueCount": 1,
"firstPath": ["toString"],
"firstMessage": "Invalid type: Expected number but received \"not-a-number\"",
"flattened": {
"ok": false,
"exception": "TypeError",
"message": "flatErrors.nested[dotPath].push is not a function"
}
}
The 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.
valibot release is 1.4.1 and maps to open-circle/valibot.gh api repos/open-circle/valibot/private-vulnerability-reporting returned {"enabled":true}.npm audit for a clean project containing only [email protected] returned no vulnerabilities.1.2.0.valibot 1.4.1 returned no vulnerabilities.flatten toString, flatten hasOwnProperty, record toString, __proto__, constructor, and prototype pollution did not find a matching disclosure of this record() issue-path / flatten() exception.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.Use null-prototype containers for flat error maps and/or perform own-property checks before appending:
flatErrors.nested as Object.create(null).Object.prototype.hasOwnProperty.call(flatErrors.nested, dotPath) rather than truthiness.getDotPath() / flatten(), including inherited Object property names.flatten() with paths toString, valueOf, hasOwnProperty, __proto__, prototype, and constructor.| Software | From | Fixed in |
|---|---|---|
valibot
|
- | 1.4.2 |
A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.
CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.
A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.
Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.
Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.
SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.