The archive/CVE-2023-36665
protobufjs/protobuf.jsDisclosed 2023

CVE-2023-36665:protobuf.js: Prototype pollution

A reconstruction of the flaw, and the evidence path Lachesis follows to make it visible.

Historical recordprotobufjs-v7.2.3 (affected: 6.10.0 <= v < 6.11.4 and 7.0.0 <= v < 7.2.4)
WeaknessCWE-1321 · Prototype pollution
ImpactA prototype pollution in protobuf.js, reachable from attacker-controlled input.
Read the reconstruction
01What happened

A __proto__/constructor key path drives protobufjs util.setProperty to write through the prototype.

That is the shape of the failure. The rest of this case file follows the evidence behind it.

Separate the record from the run

What was known.
What Lachesis established.

01Historical record

Known before Lachesis ran

The CVE and vulnerable release were selected from public history. We are not claiming novel discovery.

CVE
CVE-2023-36665
Vulnerable target
protobufjs-v7.2.3 (affected: 6.10.0 <= v < 6.11.4 and 7.0.0 <= v < 7.2.4) · vulnerable
Prior knowledge
CVE-2023-36665 · protobufjs-v7.2.3 (affected: 6.10.0 <= v < 6.11.4 and 7.0.0 <= v < 7.2.4) is vulnerable
then
02Lachesis run
Verified captured run

Independent re-detection

The experiment graph was built around the protobufjs src/ tree at the last-vulnerable release tag protobufjs-v7.2.3 (affected: 6.10.0 <= v < 6.11.4 and 7.0.0 <= v < 7.2.4), not at the fix release -- v7.2.3 is genuinely vulnerable (fixed in 7.2.4 and 6.11.4). We did not point the tool at the CVE. Enumeration ran over the whole candidate registry (31 constructors, every family) with the pass-3 ranker ordering leads; the object-integrity.prototype family surfaced on its own -- 49 class-matched candidates fired blind -- alongside the memory-lifecycle, injection, and resource families. Every field under sast_output is the enumerator's own emission (candidate capsule plus sources_of value-flow cone); everything under adjudication is my reading of the source and fix and is labelled as such. Honesty note: the top-ranked object-integrity.prototype candidate anchored on merge(this, properties) at src/util/minimal.js:280 (a lodash-merge-style property-merge look-alike, Atropos model javascript.lodash.merge.a1), which is NOT the exact CVE vector; the real CVE-2023-36665 vector is util.setProperty writing an attacker-controlled __proto__/constructor/prototype key from a parsed protobuf message. The FAMILY firing blind is the tool-witnessed HIT; the exact setProperty path is adjudicated by hand.

Seeded inputs
None
Run timestamp
2026-09-02T13:40:12Z
Evidence artifact
protobufjs_src.kuzu

The commands and outputs below come from this recorded Lachesis run.

The reconstruction, in one frame

What Lachesis reconstructed.

One historical repository. Four captured queries. A complete source-to-sink argument.

Lachesis Casefiles · Unbound ComputeVerified runCVE-2023-36665
A __proto__/constructor key path drives protobufjs util.setProperty to write through the prototype.
01Sourcefunction CustomError(message, properties) {
02Sinkmerge(this, properties)
03Missing guardutil.setProperty(dst, path, value) // no rejection of __proto__/constructor/prototype segments
$ lachesis sources_of → reaches → guard_dominance
protobufjs/protobuf.js · protobufjs-v7.2.3 (affected: 6.10.0 <= v < 6.11.4 and 7.0.0 <= v < 7.2.4) · vulnerableunboundcompute.com
This was known history. Your code is not.

Lachesis follows the same evidence path through unfamiliar repositories.

The Lachesis reconstruction

Here is how the path becomes visible.

These are the recorded questions Lachesis asked of the historical vulnerable code, followed by the raw result and source location each query returned.

Repositoryprotobufjs/protobuf.js
Revisionprotobufjs-v7.2.3 (affected: 6.10.0 <= v < 6.11.4 and 7.0.0 <= v < 7.2.4) · vulnerable
EngineLachesis

Captured runThis is a real replay of Lachesis over the graph we built for this case at the last-vulnerable tag protobufjs-v7.2.3. Every command below was run against the protobufjs_src.kuzu graph and every result is the output captured on that drive. We did not point the tool at a file or a family. The hunt listed the whole taxonomy first and the object-integrity.prototype family came out of that with 49 class-matched candidates. No message or payload was seeded; the graph was built from the source alone and the finding was discovered from graph structure, so the mode is independent-redetection.

01 · load_graph01 / 05
Lachesis ran
load_graph --path protobufjs_src.kuzu --profile all && candidate_census

Load the graph and list every bug family

First we load the graph and ask the tool to list every bug family it knows, with nothing chosen ahead of time. It reports all 8 domains and 31 sink constructors and 633 candidates. object-integrity.prototype is just one family among many here, not something we aimed at.

Captured resultCaptured · load_graph
raw captured outputload_graph
[lachesis-mcp] loaded the graph; overlay: derived edges on demand, per cone

CANDIDATE_CENSUS
move: candidate_census
taxonomy (8): lifecycle, memory, injection, navigation, object-integrity, filesystem, crypto-config, resource
constructors (31): complete_for_observable_graph=True
n_candidates: 633
src/util/minimal.jsline 0
Code location returned for this evidence step.
The repair

A small line with a large consequence.

attacker-controlled key segments are assigned onto the target with no filter for the dangerous prototype-mutating keys, so an untrusted key reaches Object.prototype.

Before
util.setProperty(dst, path, value)  // no rejection of __proto__/constructor/prototype segments
After the patch
// reject __proto__/constructor/prototype key segments in setProperty (fixed in 7.2.4 and 6.11.4, commit e66379f451b0393c27d87b37fa7d271619e16b0d).
Sources & further reading

The original record.

Read the historical advisory and vulnerability record behind this reconstruction.

Open the Lachesis quickstart