Context
Dependabot alert 221 (GHSA-5p4m-2wfm-xmqj, high, dev-scope): JS-YAML quadratic CPU consumption in !!omap resolution. Raised by @tobiu, who asked the obvious question first — "can we upgrade to the latest version?" — and the answer is no, which is why this needs a ticket rather than a version bump.
The Problem
We are already on the safe major, and it does not help. package.json declares js-yaml: ^5.2.0, resolved to 5.2.3 — not affected. The vulnerable copy is a second, nested install:
neo.mjs@13.1.0
+-- js-yaml@5.2.3 <- ours, safe
+-- gray-matter@4.0.3
| `-- js-yaml@3.15.0 <- vulnerable
gray-matter pins js-yaml: ^3.13.1, so npm update will never move it off the 3.x line.
Three upgrade paths that do not work, checked rather than assumed:
- Upgrade
js-yaml — already latest-major here; the direct dep is not the vulnerable one.
- Upgrade
gray-matter — 4.0.3 IS the latest published version. It is unmaintained; there is nothing to move to.
- Override
js-yaml to 5.x — breaks gray-matter at runtime. lib/engines.js:16-17 binds yaml.safeLoad / yaml.safeDump, both removed after 3.x. This would trade a CPU-exhaustion alert for a hard TypeError on every front-matter read.
The Architectural Reality
The advisory metadata and the published 3.x line disagree, and the code settles it. The advisory summary says "3.x and 4.x — CVE-2026-59870 fix not backported", while Dependabot reports first_patched_version: 3.15.1. Unpacking js-yaml@3.15.1 shows the backport is real:
if (objectKeys.indexOf(pairKey) !== -1) return false;
if (_hasOwnProperty.call(objectKeys, pairKey)) return false;
Object.defineProperty(objectKeys, pairKey, { value: true });Different mechanism from 5.x's Set, same quadratic elimination. And 3.15.1 still exports safeLoad/safeDump (lib/js-yaml.js:24,27), verified by parsing with it — so gray-matter keeps working.
Exploitability here is currently nil, and that is a measurement, not a shrug. gray-matter only YAML-parses front matter. Two files in resources/content/** do contain !!omap — pulls/chunk-10/pr-16608.md and pulls/chunk-5/pr-15627.md — but in both the occurrences sit at lines 85/93 and 28/32, after front-matter ends at 19 and 17. They are prose in the body, never parsed. The input is also our own generated corpus, not third-party YAML.
That is why this is enhancement rather than bug: the alert is real and worth closing, the runtime exposure today is not.
The Fix
Two changes, because the transitive pin alone leaves Dependabot correctly unsatisfied.
1. Raise the direct declared range's floor. js-yaml: ^5.2.0 resolved to a safe 5.2.3 — but Dependabot reads the declared range, not the resolved version, and ^5.2.0 still admits 5.2.0, which is the quadratic build. A fresh install against different registry state could land there. The floor moves to the first fixed release:
"js-yaml": "^5.2.3"
2. Pin the nested copy with an npm overrides entry forcing it to the patched 3.x line, keeping the API gray-matter requires:
"overrides": {
"gray-matter": {
"js-yaml": "^3.15.1"
}
}Scoped to gray-matter rather than global, so our own direct js-yaml is untouched and a future direct consumer cannot be silently dragged onto 3.x.
Why both. Change 2 removes the vulnerable installed copy; change 1 removes the vulnerable declarable one. Either alone leaves a true statement the alert is still entitled to make.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
package.json overrides |
npm overrides |
pins only gray-matter's nested js-yaml to ^3.15.1 |
none needed; npm fails loudly on an unsatisfiable override |
inline comment in the ticket, not the manifest |
npm ls js-yaml shows no 3.15.0 |
gray-matter front-matter API |
lib/engines.js:16-17 |
UNCHANGED — safeLoad/safeDump still present in 3.15.1 |
— |
— |
functional parse against 3.15.1 |
direct js-yaml declared range |
package.json dependencies |
^5.2.0 → ^5.2.3 — the floor moves off the quadratic build; the resolved version stays 5.2.3, so nothing reinstalls |
none needed; 5.2.3 already satisfies both ranges |
— |
npm ls js-yaml root entry unchanged at 5.2.3 across the edit |
root js-yaml resolution |
the lockfile |
UNCHANGED at 5.2.3 |
— |
— |
the scoped override cannot reach it, and the floor raise does not move it |
Decision Record impact
none. A transitive pin, no architectural surface.
Acceptance Criteria
Non-gating post-merge observation — deliberately not an acceptance criterion. Dependabot alert 221 should close on GitHub's next scan of dev. It is excluded from the AC set because the alert is its own watcher: it stays open until the dependency graph shows no vulnerable version and re-opens if one returns, so a human residual owner would add a second, worse observer of a fact the platform already tracks. If it does not close, that is a fresh finding about the fix rather than an unticked box on this ticket.
Out of Scope
- Replacing
gray-matter. It is unmaintained and pinned to a dead major, which is a real tech-debt lane — but a dependency swap is not a security patch, and bundling them would make this un-reviewable. Worth its own ticket if the next advisory lands on the same package.
- The root
js-yaml version. Already safe.
js-yaml@4.x. Still affected per the advisory, and irrelevant to us — nothing in the tree uses it.
Avoided Traps
- "Upgrade to latest." The direct dep already is latest-major; the vulnerable copy is transitive, and the latest
gray-matter still pins the dead line.
- Global
overrides. Would force our own 5.2.3 down to 3.x, silently reverting a safe dependency to a vulnerable major to fix a vulnerability.
- Trusting the advisory's "not backported" line. It is wrong for 3.x; the published 3.15.1 carries the fix. Read the code, not the metadata.
Related
Dependabot alert 221 · GHSA-5p4m-2wfm-xmqj.
Retrieval Hint: query_raw_memories("js-yaml omap quadratic gray-matter transitive override 3.15.1")
Live latest-open sweep: checked the latest 20 open issues at 2026-08-21T12:34:18Z plus a keyword search for js-yaml / dependabot / gray-matter; no equivalent ticket and no in-flight claim.
Origin Session ID: ab15d2b8-eb14-4237-ad18-ce48584b2d07
Context
Dependabot alert 221 (
GHSA-5p4m-2wfm-xmqj, high, dev-scope): JS-YAML quadratic CPU consumption in!!omapresolution. Raised by @tobiu, who asked the obvious question first — "can we upgrade to the latest version?" — and the answer is no, which is why this needs a ticket rather than a version bump.The Problem
We are already on the safe major, and it does not help.
package.jsondeclaresjs-yaml: ^5.2.0, resolved to 5.2.3 — not affected. The vulnerable copy is a second, nested install:gray-matterpinsjs-yaml: ^3.13.1, sonpm updatewill never move it off the 3.x line.Three upgrade paths that do not work, checked rather than assumed:
js-yaml— already latest-major here; the direct dep is not the vulnerable one.gray-matter— 4.0.3 IS the latest published version. It is unmaintained; there is nothing to move to.js-yamlto 5.x — breaksgray-matterat runtime.lib/engines.js:16-17bindsyaml.safeLoad/yaml.safeDump, both removed after 3.x. This would trade a CPU-exhaustion alert for a hardTypeErroron every front-matter read.The Architectural Reality
The advisory metadata and the published 3.x line disagree, and the code settles it. The advisory summary says "3.x and 4.x — CVE-2026-59870 fix not backported", while Dependabot reports
first_patched_version: 3.15.1. Unpackingjs-yaml@3.15.1shows the backport is real:// 3.15.0 (vulnerable) — linear scan inside the pair loop if (objectKeys.indexOf(pairKey) !== -1) return false; // 3.15.1 (fixed) — O(1) property probe if (_hasOwnProperty.call(objectKeys, pairKey)) return false; Object.defineProperty(objectKeys, pairKey, { value: true });Different mechanism from 5.x's
Set, same quadratic elimination. And 3.15.1 still exportssafeLoad/safeDump(lib/js-yaml.js:24,27), verified by parsing with it — sogray-matterkeeps working.Exploitability here is currently nil, and that is a measurement, not a shrug.
gray-matteronly YAML-parses front matter. Two files inresources/content/**do contain!!omap—pulls/chunk-10/pr-16608.mdandpulls/chunk-5/pr-15627.md— but in both the occurrences sit at lines 85/93 and 28/32, after front-matter ends at 19 and 17. They are prose in the body, never parsed. The input is also our own generated corpus, not third-party YAML.That is why this is
enhancementrather thanbug: the alert is real and worth closing, the runtime exposure today is not.The Fix
Two changes, because the transitive pin alone leaves Dependabot correctly unsatisfied.
1. Raise the direct declared range's floor.
js-yaml: ^5.2.0resolved to a safe 5.2.3 — but Dependabot reads the declared range, not the resolved version, and^5.2.0still admits 5.2.0, which is the quadratic build. A fresh install against different registry state could land there. The floor moves to the first fixed release:"js-yaml": "^5.2.3"2. Pin the nested copy with an npm
overridesentry forcing it to the patched 3.x line, keeping the APIgray-matterrequires:"overrides": { "gray-matter": { "js-yaml": "^3.15.1" } }Scoped to
gray-matterrather than global, so our own directjs-yamlis untouched and a future direct consumer cannot be silently dragged onto 3.x.Why both. Change 2 removes the vulnerable installed copy; change 1 removes the vulnerable declarable one. Either alone leaves a true statement the alert is still entitled to make.
Contract Ledger Matrix
package.jsonoverridesgray-matter's nestedjs-yamlto^3.15.1npm ls js-yamlshows no 3.15.0gray-matterfront-matter APIlib/engines.js:16-17safeLoad/safeDumpstill present in 3.15.1js-yamldeclared rangepackage.jsondependencies^5.2.0→^5.2.3— the floor moves off the quadratic build; the resolved version stays 5.2.3, so nothing reinstallsnpm ls js-yamlroot entry unchanged at 5.2.3 across the editjs-yamlresolutionDecision Record impact
none. A transitive pin, no architectural surface.Acceptance Criteria
npm ls js-yaml --allreports nojs-yaml@3.15.0, andgray-matter's nested copy is>= 3.15.1.js-yamlresolution remains5.2.3— the override must not widen. Control: asserting only that 3.15.0 is gone would also pass if the override had dragged everything onto 3.x.^5.2.3, not^5.2.0. The resolved version is identical either way, so this is the arm that would silently pass on the old range — Dependabot reads the range, and^5.2.0still admits the quadratic 5.2.0.gray-matterstill parses front matter: the four*Syncer.spec.mjs/verifyFrontmatterIntegrity.spec.mjsarms that consume it stay green, provingsafeLoadsurvived the pin.Non-gating post-merge observation — deliberately not an acceptance criterion. Dependabot alert 221 should close on GitHub's next scan of
dev. It is excluded from the AC set because the alert is its own watcher: it stays open until the dependency graph shows no vulnerable version and re-opens if one returns, so a human residual owner would add a second, worse observer of a fact the platform already tracks. If it does not close, that is a fresh finding about the fix rather than an unticked box on this ticket.Out of Scope
gray-matter. It is unmaintained and pinned to a dead major, which is a real tech-debt lane — but a dependency swap is not a security patch, and bundling them would make this un-reviewable. Worth its own ticket if the next advisory lands on the same package.js-yamlversion. Already safe.js-yaml@4.x. Still affected per the advisory, and irrelevant to us — nothing in the tree uses it.Avoided Traps
gray-matterstill pins the dead line.overrides. Would force our own 5.2.3 down to 3.x, silently reverting a safe dependency to a vulnerable major to fix a vulnerability.Related
Dependabot alert 221 ·
GHSA-5p4m-2wfm-xmqj.Retrieval Hint:
query_raw_memories("js-yaml omap quadratic gray-matter transitive override 3.15.1")Live latest-open sweep: checked the latest 20 open issues at 2026-08-21T12:34:18Z plus a keyword search for js-yaml / dependabot / gray-matter; no equivalent ticket and no in-flight claim.
Origin Session ID: ab15d2b8-eb14-4237-ad18-ce48584b2d07