The Risk Hidden in a Single Character: * in Your package.json
The package.json file is the beating heart of any Node.js project — it declares what your project is, how to build it, and critically, what it depends on. In the bpmn-js-task-resize library, two lines in this file contained a single character that quietly opened a supply chain attack vector affecting every downstream consumer of the package:
"bpmn-js": "*",
"diagram-js": "*"
That * is a wildcard version constraint. It tells npm: "I'll take any version of this package." And in a world where npm package hijacking and dependency confusion attacks are increasingly common, that's an open invitation for trouble.
The Vulnerability Explained
What Does * Actually Mean?
When npm resolves a * version constraint, it fetches the latest published version of the package at the time of installation. There is no floor and no ceiling — no minimum version required, no maximum version blocked. Every npm install is a fresh roll of the dice.
Here's the vulnerable section of package.json before the fix:
"dependencies": {
"bpmn-js": "*",
"diagram-js": "*"
}
For a library like bpmn-js-task-resize — which is itself a published npm package consumed by other projects — this is especially dangerous. When a downstream developer adds bpmn-js-task-resize to their own project and runs npm install, npm will also resolve bpmn-js and diagram-js using the * constraint. Their lockfile is generated from scratch based on what's currently published on the npm registry.
The Attack Scenario
Consider this realistic attack chain:
-
An attacker gains publish access to the
bpmn-jsordiagram-jsnpm package — either by compromising maintainer credentials, exploiting a typosquatting opportunity, or through a dependency confusion attack targeting private package namespaces. -
The attacker publishes
bpmn-js@99.0.0containing malicious code — a cryptominer, a credential stealer, or a script that exfiltrates diagram data (which may contain sensitive business process information) to an external server. -
A developer working on a downstream project runs
npm install bpmn-js-task-resize. Because the constraint is*, npm happily resolvesbpmn-jsto99.0.0— the latest available version. -
The malicious code is now bundled into the downstream application and executes in end-user browsers during BPMN diagram rendering or editing. BPMN diagrams often model sensitive business workflows, making this data particularly valuable.
-
The downstream developer has no idea anything is wrong. Their
package.jsononly referencesbpmn-js-task-resize, notbpmn-jsdirectly.
Why This Is Especially Risky for a Library Package
If this were a standalone application with a committed package-lock.json, the lockfile would pin the resolved version and partially mitigate the risk (though the lockfile itself could be tampered with). But bpmn-js-task-resize is a library — a package consumed by others. Downstream consumers generate their own lockfiles. The * constraint in this library's package.json propagates directly into those consumers' dependency resolution, with no protection from the library's own lockfile.
This is precisely why npm's own documentation and security best practices explicitly warn against wildcard constraints in published packages.
The Fix
The fix is surgical and precise — two lines changed in package.json:
"dependencies": {
- "bpmn-js": "*",
- "diagram-js": "*"
+ "bpmn-js": "^4.0.4",
+ "diagram-js": "^4.0.3"
}
What This Change Does
By replacing * with ^4.0.4 and ^4.0.3, the fix establishes a semver floor and a bounded ceiling:
^4.0.4means: acceptbpmn-jsversions>=4.0.4and<5.0.0. This allows bug fixes and non-breaking feature additions, but blocks major version jumps that could introduce breaking or malicious changes.^4.0.3means: acceptdiagram-jsversions>=4.0.3and<5.0.0. Same protection applies.
This is the npm community's recommended constraint style for library dependencies — it balances security (bounded range) with maintainability (automatic patch/minor updates).
Before vs. After: Security Impact
| Before | After | |
|---|---|---|
| Accepted versions | Any version ever published | >=4.0.4 <5.0.0 / >=4.0.3 <5.0.0 |
Malicious v99.0.0 accepted? |
✅ Yes | ❌ No |
| Patch updates accepted? | ✅ Yes | ✅ Yes |
| Breaking major version accepted? | ✅ Yes | ❌ No |
| Downstream consumers protected? | ❌ No | ✅ Yes |
The fix also implicitly documents the known-good version of these dependencies — 4.0.4 and 4.0.3 — giving future maintainers a clear baseline to reason about compatibility.
Key Takeaways
- Wildcard
*constraints in a published library'spackage.jsonare not just sloppy — they're a security vulnerability that affects every downstream consumer who runsnpm install. bpmn-jsanddiagram-jsare rendering engines that execute in user browsers; a compromised version of either could silently exfiltrate sensitive BPMN diagram data or execute arbitrary JavaScript.- Lockfiles do not protect library consumers — only the constraints in
package.jsongovern what versions downstream projects resolve. - Pinning to
^4.0.4and^4.0.3is the minimal correct fix — it preserves the ability to receive safe patch updates while blocking the unbounded version window that enabled the attack. - One character (
*) in a manifest file can create a supply chain risk affecting an entire ecosystem of downstream users — dependency constraints deserve the same security scrutiny as application code.
How Orbis AppSec Detected This
- Source: The
dependenciesblock inpackage.json, specifically the version constraint fields forbpmn-jsanddiagram-js. - Sink: The npm registry resolution process — any
npm installinvocation by a downstream consumer would resolve these fields against all publicly published package versions, including future malicious ones. - Missing control: No version floor or ceiling was specified. The
*wildcard imposed zero constraints on which package version would be fetched and executed. - CWE: CWE-829 — Inclusion of Functionality from Untrusted Control Sphere.
- Fix: Replaced
"bpmn-js": "*"with"bpmn-js": "^4.0.4"and"diagram-js": "*"with"diagram-js": "^4.0.3", establishing a bounded semver range that prevents resolution of unknown future versions.
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
Supply chain attacks via npm have become one of the most impactful and underappreciated attack vectors in modern software development. The bpmn-js-task-resize vulnerability is a textbook example of how a single permissive character — * — in a dependency manifest can expose an entire ecosystem of downstream users to arbitrary code execution in their users' browsers.
The fix is simple, non-breaking, and immediately effective: pin bpmn-js to ^4.0.4 and diagram-js to ^4.0.3. But the broader lesson is that dependency constraints in published packages are a security boundary, not just a compatibility hint. Treat them accordingly — audit them, pin them, and monitor them with the same rigor you'd apply to your application code.