CVE-2025-64718:js-yaml: the merge key that reached the prototype
How Lachesis made an unsafe property write stand out beside a guarded one.
The YAML document chose the property name, and the loader let __proto__ reach JavaScript's shared prototype chain.
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-2025-64718
- Vulnerable target
- 4.1.0 · vulnerable
- Prior knowledge
- CVE-2025-64718 · 4.1.0 is vulnerable
Independent re-detection
Blind emission probe: the CVE line 301 was surfaced from graph structure alone (no location fed). guard_status/guarded-sibling were computed by the reader, then checked against source only for scoring.
- Seeded inputs
- None
- Run timestamp
- 2026-08-24T12:06:09Z
- Evidence artifact
- ~/.lachesis/graphs/jsyaml_cve_2025_64718.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 block reflects the real blind drive of the graph we built for js-yaml. We loaded the whole graph and ran the flow pass, which emits prototype pollution sinks for dynamic object writes with the guard read at each write site. The write in mergeMappings came out as an unguarded outlier next to its guarded sibling. The facts below are the ones captured on that drive on that drive. No YAML document was seeded; the finding was rediscovered 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, with nothing picked ahead of time. It reports all 8 domains and 31 sink constructors and says the census is complete for what the graph can observe. Object integrity is one row among all of them.
{"move": "load_graph", "graph": "/Users/riyandhiman/.lachesis/graphs/jsyaml_cve_2025_64718.kuzu", "profile": "all"}
CANDIDATE_CENSUS
move: candidate_census
taxonomy (8):
domain=lifecycle title=Resource lifecycle enumerable=True
domain=memory title=Memory safety enumerable=True
domain=injection title=Injection enumerable=True
domain=navigation title=Request forgery & redirection enumerable=True
domain=object-integrity title=Object integrity enumerable=True
domain=filesystem title=Filesystem enumerable=True
domain=crypto-config title=Cryptography & transport config enumerable=True
domain=resource title=Resource exhaustion enumerable=True
constructors (31): complete_for_observable_graph=True
applied: TrueA small line with a large consequence.
The own-property check does not reject key === '__proto__', 'constructor', or 'prototype'. The assignment can still reach the prototype chain.
if (!_hasOwnProperty.call(destination, key)) {
destination[key] = source[key];
}// patched in 4.1.1 (commit 383665f): route the write through a setProperty() // helper that special-cases __proto__ via Object.defineProperty instead of a bare assignment.
The original record.
Read the historical advisory and vulnerability record behind this reconstruction.