Introduction
The package-lock.json file in localpdf-studio locks the exact version of every dependency the application resolves at install time, including js-yaml — a YAML parser used transitively by the project's dependency tree. Before this fix, that lockfile pinned js-yaml at version 4.1.1, a version that never received the backported fix for GHSA-5p4m-2wfm-xmqj (CVE-2026-59870): a quadratic CPU consumption bug in the !!omap type resolver that affects both the 3.x and 4.x release lines of js-yaml.
This matters even if your application doesn't directly call yaml.load() on untrusted input. js-yaml is one of the most widely used YAML parsers in the JavaScript ecosystem, and it often gets pulled in transitively by build tools, config loaders, and other packages. If any code path in the dependency graph feeds attacker-influenced YAML into js-yaml's parser, the vulnerable resolver becomes reachable — which is exactly why Trivy flagged this as "present in dependency tree, not confirmed reachable." Fixing it proactively, before reachability is proven, is the responsible move: it removes an exploit primitive before automated tooling or a future code change turns it into a concrete attack path.
The Vulnerability Explained
YAML supports a special tag called !!omap — an "ordered map" — which lets a document represent a sequence of single-key mappings that preserve insertion order:
--- !!omap
- key1: value1
- key2: value2
- key3: value3
Internally, js-yaml's resolver for this type has to walk through the sequence and validate the structure of each entry. In the vulnerable versions (js-yaml < 4.3.1 and < 3.15.1), this validation logic scaled poorly: as the number of entries in the !!omap structure grew, the work done by the resolver grew quadratically rather than linearly.
That's the classic signature of an algorithmic complexity vulnerability (CWE-407). A well-behaved parser should process an N-entry structure in roughly O(N) time. A vulnerable one that's O(N²) looks fine on small, benign inputs — but an attacker who controls the YAML being parsed can supply a document with a large, repetitive !!omap structure and force the parser to burn CPU disproportionately to the size of the payload sent.
A concrete attack scenario
Imagine any part of localpdf-studio's build pipeline, CI configuration parsing, or a transitive dependency that ends up calling something like:
const yaml = require('js-yaml');
const doc = yaml.load(untrustedInput);
An attacker who can influence untrustedInput — whether through an uploaded config file, a webhook payload, or a crafted document processed as part of a larger workflow — could submit a YAML document containing a large !!omap block. On the vulnerable js-yaml versions, parsing that single request could pin a CPU core at 100% for a disproportionate amount of time relative to the payload's size, potentially stalling the event loop in a Node.js process and degrading or denying service for every other request being handled by that process.
Because the lockfile showed js-yaml 4.1.1 — a version that predates the CVE-2026-59870 backport — this vulnerability was sitting in the dependency tree regardless of whether it was actively reachable today. That's the nature of a lot of supply-chain risk: the vulnerable code ships whether or not you're using it yet.
The Fix
The fix here is a targeted dependency upgrade, not a code rewrite — because the vulnerability lives inside js-yaml itself, not in localpdf-studio's application logic.
Before:
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
...
}
After:
"node_modules/js-yaml": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
"integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
"funding": [
{ "type": "github", "url": "https://github.com/sponsors/puzrin" },
{ "type": "github", "url": "https://github.com/sponsors/nodeca" }
],
...
}
To make sure this pinned version actually sticks — even if a transitive dependency somewhere else in the tree still asks for an older js-yaml — the PR also adds an explicit override in package.json:
"overrides": {
"js-yaml": "4.3.1"
}
This is the important part: without the overrides entry, npm's resolution algorithm could still let a nested dependency pull in an older, vulnerable js-yaml copy elsewhere in node_modules. The override forces every consumer in the tree — direct or transitive — onto the patched 4.3.1 release, which contains the fix for the !!omap resolver's quadratic-time behavior. The project's own version field was also bumped from 4.0.2 to 4.0.3 to mark the patch release.
Because the change is confined to package.json and package-lock.json, and js-yaml 4.3.1 is a patch-level release focused on this security fix, valid YAML documents parse identically to before — only the pathological, attacker-crafted !!omap structures that previously triggered quadratic blowup are now handled efficiently.
Prevention & Best Practices
- Run software composition analysis (SCA) regularly. Tools like Trivy,
npm audit, Snyk, or Orbis AppSec scan your lockfile against advisory databases and catch exactly this class of issue — a vulnerable transitive dependency that your own code never directly touches. - Don't assume "not directly reachable" means "safe." Trivy's own note here was "present in dependency tree, not confirmed reachable" — but dependency graphs change, and code that calls
yaml.load()on untrusted input can be added later without anyone realizing the underlying parser is vulnerable. - Use
overrides(npm) orresolutions(Yarn) to enforce minimum safe versions across your entire dependency tree, not just your direct dependencies. - Prefer
yaml.load()withjson: trueschema restrictions oryaml.safeLoad()-equivalent strict parsing when consuming untrusted YAML, and consider input size limits or timeouts around any untrusted document parsing to bound worst-case CPU usage regardless of parser bugs. - Track CVEs for your core parsing libraries. js-yaml is foundational infrastructure for countless JS/Node projects; subscribe to its release notes or GitHub Security Advisories.
Key Takeaways
- The vulnerable code lived entirely inside the
js-yamlpackage's!!omapresolver, not in localpdf-studio's own source — a reminder that dependency risk is application risk. - js-yaml
4.1.1predated the backported fix for CVE-2026-59870; the patched line is4.3.1(4.x) or3.15.1(3.x). - Simply bumping the lockfile version wasn't fully sufficient — the fix also adds an
overridesentry inpackage.jsonto guarantee no transitive dependency can reintroduce the vulnerablejs-yamlversion. - Quadratic-time parsers can turn a small, attacker-supplied YAML payload into a disproportionate CPU/denial-of-service cost — always worth patching even without confirmed reachability.
- This is scoped to exactly two files (
package.json,package-lock.json), preserving all valid-input behavior while eliminating the exploit primitive.
How Orbis AppSec Detected This
- Source: Any untrusted YAML document reaching the application through a transitive dependency's call into js-yaml's parsing API (e.g., config loaders, CI tooling, or future code paths calling
yaml.load()/yaml.loadAll()) - Sink: js-yaml's internal
!!omaptype resolver (versions< 4.3.1/< 3.15.1), reached whenever a YAML document containing an!!omaptag is parsed - Missing control: No enforced minimum-safe-version constraint on the
js-yamldependency — the lockfile pinned an unpatched4.1.1, and nooverrides/resolutionsentry existed to prevent transitive resolution of vulnerable versions - CWE: CWE-407 (Algorithmic Complexity) / related to CWE-1333 and CWE-400 (Uncontrolled Resource Consumption)
- Fix: Upgraded
js-yamlto4.3.1inpackage-lock.jsonand added apackage.jsonoverridesentry pinningjs-yamlto4.3.1across the entire dependency tree
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This fix is a great example of why dependency hygiene matters just as much as reviewing your own application code. localpdf-studio's package-lock.json was quietly pinning a version of js-yaml that missed a critical backported security fix for a quadratic CPU consumption bug in !!omap resolution — a flaw that could turn a small, crafted YAML payload into an outsized CPU cost anywhere the parser is invoked on untrusted input. By upgrading to js-yaml 4.3.1 and locking in that version with a package.json override, the project closes off this exploit primitive tree-wide, protecting against both current and future code paths that might parse attacker-influenced YAML.