Skip to main content

flat-to-nested CVE-2026-55091

| EUVDEUVD-2026-77691 HIGH
Improperly Controlled Modification of Dynamically-Determined Object Attributes (CWE-915)
2026-06-19 https://github.com/joaonuno/flat-to-nested-js GHSA-hp36-v28f-w3r4
7.5
CVSS 3.1 · Vendor: https://github.com/joaonuno/flat-to-nested-js
Share

Severity by source

Vendor (https://github.com/joaonuno/flat-to-nested-js) PRIMARY
7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
vuln.today AI
8.2 HIGH

Library is typically reached via network-fed records with no auth or interaction; integrity is high (arbitrary prototype writes), with low availability impact from likely logic crashes and no direct confidentiality loss.

3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/joaonuno/flat-to-nested-js).

CVSS VectorVendor: https://github.com/joaonuno/flat-to-nested-js

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
High
Availability
None

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 19, 2026 - 21:38 vuln.today
Analysis Generated
Jun 19, 2026 - 21:38 vuln.today

DescriptionCVE.org

Summary

convert() builds the nested tree by using each flat record's id and parent field values directly as object keys, with no guard against __proto__ / constructor / prototype. A record whose parent is the string "__proto__" makes temp[parent] resolve to Object.prototype, and the following initPush(...) writes attacker-controlled data onto the global prototype. Any application that passes attacker-influenced records to convert() is affected, and the base prototype methods stay intact so the pollution is stealthy.

Details

In index.js, convert() (FlatToNested.prototype.convert):

  • temp = {} (line 45) and pendingChildOf = {} (line 46) are plain objects, so they inherit from Object.prototype.
  • For each record, parent = flatEl[this.config.parent] (line 51) is taken verbatim from input.
  • Line 57: if (temp[parent] ! undefined) - when parent = "__proto__", temp["__proto__"] resolves via the prototype chain to Object.prototype, which is !== undefined, so the

branch is taken.

  • Line 59: initPush(this.config.children, temp[parent], flatEl) → effectively initPush("children", Object.prototype, flatEl).
  • initPush (lines 4-9): Object.prototype["children"] = [] then Object.prototype["children"].push(flatEl) - attacker-controlled data is written onto the global Object.prototype.

There is no sanitization of id / parent anywhere; they flow straight into temp[id], temp[parent], and pendingChildOf[parent] as dynamic keys.

PoC

js
  const FlatToNested = require('flat-to-nested');

  new FlatToNested().convert([
    { id: 1, parent: '__proto__', polluted: 'PWNED' }
  ]);

  console.log(({}).children); // => [ { id: 1, polluted: 'PWNED' } ]
  A freshly-created, unrelated object {} now carries an attacker-controlled children property. ({}).toString === Object.prototype.toString remains true, so existing methods are untouched (stealthy). If the consumer configures a custom children key, that arbitrary prototype property is polluted instead.

Impact

Prototype pollution (CWE-1321). Any service that builds a tree from attacker-influenced flat records (the package's core purpose - e.g. records derived from a DB/REST/user input) can have Object.prototype polluted. Consequences range from application-logic corruption and denial of service to serving as a gadget toward privilege escalation or RCE depending on downstream sinks. No special privileges or user interaction required; the malicious value is ordinary input data.

Suggested fix

Use prototype-less lookup tables so inherited keys like __proto__ cannot be reached: var temp = Object.create(null); var pendingChildOf = Object.create(null); (Optionally also reject id/parent values equal to __proto__, constructor, or prototype.) Verified: with Object.create(null) for both temp and pendingChildOf, the PoC no longer pollutes Object.prototype and normal nesting output is unchanged. A patch with a regression test is ready.

AnalysisAI

Prototype pollution in the npm package flat-to-nested (versions <= 1.1.1) lets attackers who control input records pollute Object.prototype by supplying a record with parent set to the string '__proto__'. The convert() function uses plain objects as lookup tables and writes attacker-controlled data through the prototype chain via initPush(), affecting any application that feeds untrusted flat records (REST input, DB rows, user-supplied data) into the library. No CISA KEV listing and no public exploit identified at time of analysis beyond the proof-of-concept embedded in the GitHub Security Advisory.

Technical ContextAI

flat-to-nested is a small JavaScript utility (pkg:npm/flat-to-nested) that converts flat parent/child records into a nested tree. In index.js, convert() initializes temp = {} and pendingChildOf = {} as plain objects that inherit from Object.prototype, then uses each record's id and parent fields directly as dynamic object keys without any allowlist or denylist. When parent === '__proto__', temp['__proto__'] resolves to Object.prototype and initPush() writes children data onto the global prototype. The root cause class is CWE-1321 / CWE-915 (improperly controlled modification of object prototype attributes), a well-known JavaScript sink-pattern where unsanitized strings used as keys cross the boundary between own properties and inherited prototype properties.

RemediationAI

Vendor-released patch: upgrade flat-to-nested to 1.1.2 or later, which replaces the plain-object lookup tables with Object.create(null) so that '__proto__', 'constructor', and 'prototype' become harmless own keys rather than prototype-chain references (commit 680a5ebe1194edda16fa93baaa56ff14fe0e3d7f, advisory GHSA-hp36-v28f-w3r4). If upgrading is not immediately possible, pre-sanitize input records before calling convert() by rejecting or stripping any record whose id or parent equals '__proto__', 'constructor', or 'prototype' - note this is a strict allowlist on key strings and will reject any legitimate record that happens to use those exact identifiers. As an additional defense, run the consumer process under Node's --frozen-intrinsics flag or call Object.freeze(Object.prototype) at startup; this blocks the write but will break any library that legitimately augments Object.prototype, so test thoroughly before enabling in production.

Share

CVE-2026-55091 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy