CVE-2023-36665:protobuf.js: Prototype pollution
A reconstruction of the flaw, and the evidence path Lachesis follows to make it visible.
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.
What was known.
What Lachesis established.
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
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.
What Lachesis reconstructed.
One historical repository. Four captured queries. A complete source-to-sink argument.
Lachesis follows the same evidence path through unfamiliar repositories.
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.
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.
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.
[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: 633A 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.
util.setProperty(dst, path, value) // no rejection of __proto__/constructor/prototype segments
// reject __proto__/constructor/prototype key segments in setProperty (fixed in 7.2.4 and 6.11.4, commit e66379f451b0393c27d87b37fa7d271619e16b0d).
The original record.
Read the historical advisory and vulnerability record behind this reconstruction.