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.
Prevention & Best Practices
1. Never Use * or latest in Published Package Dependencies
For any package you publish to npm, treat * and latest as red flags in your dependencies and peerDependencies. Use explicit semver ranges. Tools like npm-check and depcheck can help audit your constraints.
2. Commit and Verify Lockfiles
For applications (not libraries), commit your package-lock.json or yarn.lock. In CI, use npm ci instead of npm install — it installs exclusively from the lockfile and fails if the lockfile doesn't match package.json.
3. Enable npm audit in CI Pipelines
Add npm audit --audit-level=high as a CI step. This catches known vulnerabilities in resolved dependencies before they reach production.
4. Consider Subresource Integrity and Package Provenance
For high-security contexts, explore npm's provenance attestations and tools like Socket.dev that analyze package behavior — not just known CVEs — to detect suspicious new versions of dependencies.
5. Use Dependabot or Renovate for Automated Dependency Updates
Automated tools like GitHub Dependabot or Renovate Bot will open PRs when new versions of your dependencies are released, keeping you on known-good versions without requiring manual monitoring.
Relevant Standards
- OWASP A06:2021 – Vulnerable and Outdated Components: Directly addresses the risk of uncontrolled third-party dependency versions.
- CWE-829: Inclusion of Functionality from Untrusted Control Sphere — the CWE that most precisely describes this vulnerability pattern.
- SLSA (Supply-chain Levels for Software Artifacts): A framework for improving supply chain integrity, including dependency pinning practices.
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.