GHSA-325J-MG25-8Q58
Vulnerability from github – Published: 2026-09-11 22:11 – Updated: 2026-09-11 22:11Summary
Store/LegacyStore key internal lookup tables by the type, id, and relationship names from a JSON:API document. Because these were plain objects, a document with type: "__proto__" writes onto Object.prototype, polluting every object in the process.
Severity
Suggested CVSS v3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H (8.1). The guaranteed impact is process-wide DoS / logic corruption; escalation to authorization bypass or RCE is dependent on gadgets in the consuming application.
Affected / Patched
- Affected:
<= 4.2.0(verified in3.0.0and4.2.0; all 3.x and 4.x). - Patched:
4.3.0.
Details
type is a string value, untouched by JSON.parse, and is used directly as an object key:
if (!models[type]) models[type] = {} // models["__proto__"] is Object.prototype → skipped
if (!models[type][id]) models[type][id] = model // → Object.prototype[id] = model
The attacker controls the polluted key (id) and value (the model, populated from attributes). Pollution persists for the process lifetime. The malicious type can also arrive via an included resource referenced by a relationship, bypassing any data.type allow-list. LegacyStore is additionally reachable when a configured types mapping resolves to "__proto__".
Proof of concept
const { Store } = require('yayson')()
new Store().sync({ data: { type: '__proto__', id: 'polluted', attributes: { x: 1 } } })
console.log(({}).polluted) // { x: 1, id: 'polluted' } ← Object.prototype polluted
Workarounds
Reject documents whose type or relationship names are __proto__, constructor, or prototype, or run Node with --disable-proto=throw.
Fix
Null-prototype lookup tables, rejection of __proto__/constructor/prototype as document-derived member names, Object.keys() iteration, and null-prototype normalization of caller-supplied caches.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.0"
},
"package": {
"ecosystem": "npm",
"name": "yayson"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.3.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-61534"
],
"database_specific": {
"cwe_ids": [
"CWE-1321"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-11T22:11:38Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "# Summary\n`Store`/`LegacyStore` key internal lookup tables by the `type`, `id`, and relationship names from a JSON:API document. Because these were plain objects, a document with `type: \"__proto__\"` writes onto `Object.prototype`, polluting every object in the process.\n\n# Severity\nSuggested CVSS v3.1 `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H` (8.1). The guaranteed impact is process-wide DoS / logic corruption; escalation to authorization bypass or RCE is dependent on gadgets in the consuming application.\n\n# Affected / Patched\n- Affected: `\u003c= 4.2.0` (verified in `3.0.0` and `4.2.0`; all 3.x and 4.x).\n- Patched: `4.3.0`.\n\n# Details\n`type` is a string *value*, untouched by `JSON.parse`, and is used directly as an object key:\n```js\nif (!models[type]) models[type] = {} // models[\"__proto__\"] is Object.prototype \u2192 skipped\nif (!models[type][id]) models[type][id] = model // \u2192 Object.prototype[id] = model\n```\nThe attacker controls the polluted key (`id`) and value (the model, populated from `attributes`). Pollution persists for the process lifetime. The malicious type can also arrive via an `included` resource referenced by a relationship, bypassing any `data.type` allow-list. `LegacyStore` is additionally reachable when a configured `types` mapping resolves to `\"__proto__\"`.\n\n# Proof of concept\n```js\nconst { Store } = require(\u0027yayson\u0027)()\nnew Store().sync({ data: { type: \u0027__proto__\u0027, id: \u0027polluted\u0027, attributes: { x: 1 } } })\nconsole.log(({}).polluted) // { x: 1, id: \u0027polluted\u0027 } \u2190 Object.prototype polluted\n```\n\n# Workarounds\nReject documents whose `type` or relationship names are `__proto__`, `constructor`, or `prototype`, or run Node with `--disable-proto=throw`.\n\n# Fix\nNull-prototype lookup tables, rejection of `__proto__`/`constructor`/`prototype` as document-derived member names, `Object.keys()` iteration, and null-prototype normalization of caller-supplied caches.",
"id": "GHSA-325j-mg25-8q58",
"modified": "2026-09-11T22:11:38Z",
"published": "2026-09-11T22:11:38Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/confetti/yayson/security/advisories/GHSA-325j-mg25-8q58"
},
{
"type": "PACKAGE",
"url": "https://github.com/confetti/yayson"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "yayson: Prototype pollution in Store/LegacyStore deserialization"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.