Back to Blog
high SEVERITY5 min read

How Dependency Version Pinning Prevents Supply Chain Attacks in Node.js and How to Fix It

A critical supply chain vulnerability in `package.json` allowed automatic updates to a cryptographic library with known weaknesses. By pinning `rijndael-js` to version `2.0.0` instead of allowing `^2.0.0` updates, the fix prevents silent installation of vulnerable versions that could expose downstream consumers to weak block cipher modes and authentication bypasses.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

This is a **Dependency Confusion / Supply Chain Vulnerability** (CWE-1104) in Node.js where the `^2.0.0` semver range for `rijndael-js` in `package.json:29` allowed automatic patch/minor updates that could silently introduce vulnerable versions. The fix pins the dependency to exactly `2.0.0`, removing the caret operator to prevent automatic updates to potentially compromised or weakened cryptographic implementations. This protects downstream consumers from man-in-the-middle attacks exploiting weak ECB mode and improper IV handling in the rijndael-js library.

Vulnerability at a Glance

cweCWE-1104 (Uncontrolled Search Path Element), CWE-1395 (Dependency on Vulnerable Third-Party Component)
fixPin rijndael-js to exact version 2.0.0 by removing ^ prefix
riskAutomatic updates to vulnerable cryptographic library exposing MITM attacks, traffic decryption, and command modification
languageJavaScript/Node.js
root causeCaret (^) semver operator allowed automatic patch/minor version updates without security review
vulnerabilitySupply Chain / Dependency Confusion

How Dependency Version Pinning Prevents Supply Chain Attacks in Node.js and How to Fix It

Option C: Context-First Introduction

The package.json file in this Node.js repository manages cryptographic operations through the rijndael-js library, but a flaw in the dependency specification on line 29 created a supply chain security risk. The entry "rijndael-js": "^2.0.0" used a caret-prefixed semantic version range that automatically allows patch and minor version updates—potentially pulling in compromised or weakened versions of this encryption library without explicit review or testing.

This matters for developers because cryptographic libraries require rigorous vetting. The rijndael-js package implements Rijndael (AES) encryption, and version 2.0.0 has known limitations: it uses weak block cipher modes (likely ECB) and lacks authenticated encryption. When the ^ operator allows automatic updates, downstream consumers of this package could silently receive versions with even worse security properties—or worse, a compromised version from a compromised maintainer account.

The Vulnerability Explained

The vulnerable dependency specification:

// package.json (line 29) - BEFORE
"dependencies": {
  "@iobroker/adapter-core": "^3.4.3",
  "crc-32": "^1.2.0",
  "rijndael-js": "^2.0.0"
}

The specific problem: The caret (^) in ^2.0.0 tells npm to accept any version from 2.0.0 up to (but not including) 3.0.0. This means npm install could automatically install 2.0.1, 2.1.0, or any future 2.x.x release—versions that may:
- Contain intentional backdoors from compromised maintainer accounts
- Introduce breaking cryptographic changes (weaker modes, broken IV handling)
- Include supply chain malware injected during the build process

The real-world impact: This is an ioBroker adapter—a home automation platform where this package handles device communication encryption. An attacker in a man-in-the-middle position could exploit weak or broken encryption to:
1. Decrypt traffic between smart home devices and controllers
2. Modify commands undetected (lacking authentication)
3. Potentially inject malicious firmware updates or device commands

The rijndael-js library specifically has documented weaknesses in IV handling and lacks authenticated encryption (AEAD). Automatic updates could worsen these issues or introduce new vulnerabilities without consumers knowing their encryption had degraded.

The Fix

The hardened dependency specification:

// package.json:29 - AFTER
"dependencies": {
  "@iobroker/adapter-core": "^3.4.3",
  "crc-32": "^1.2.0",
-    "rijndael-js": "^2.0.0"
+    "rijndael-js": "2.0.0"
}

What changed:
- Removed the ^ caret prefix from rijndael-js
- Pinned to exact version 2.0.0 with no automatic update allowance

Why this solves the problem:
Exact version pinning ("2.0.0" vs "^2.0.0") ensures that every install gets precisely the same code that was security-reviewed. Updates require explicit human intervention—forcing a conscious security decision when any version change occurs.

This is particularly critical for cryptographic dependencies where:
- Subtle implementation changes break security guarantees
- Supply chain attacks on crypto libraries have high impact
- Downstream consumers depend on specific, verified security properties

Key Takeaways

  • Never use ^ or ~ for cryptographic dependencies—the rijndael-js entry in package.json:29 now uses exact version pinning to prevent automatic updates to unvetted versions
  • The ^2.0.0 → 2.0.0 change blocks CWE-1104 exploitation by removing automatic version resolution that could introduce supply chain compromises
  • Downstream ioBroker consumers are now protected from silently receiving weakened or compromised encryption implementations
  • Explicit updates required—any future rijndael-js upgrade must be intentional, with security review of the new version's cryptographic properties
  • Consider replacing rijndael-js—Node.js built-in crypto module or noble-ciphers provide authenticated encryption that this library lacks

How Orbis AppSec Detected This

Source: The package.json dependency manifest where "rijndael-js": "^2.0.0" allows automatic version resolution to any 2.x.x release.

Sink: Downstream consumption in cryptographic operations where the library's weak ECB mode and lack of authenticated encryption create exploitable conditions for MITM attackers.

Missing control: No version pinning or integrity verification (no package-lock.json enforcement, no explicit security review of automatic updates).

CWE: CWE-1104: Uncontrolled Search Path Element and CWE-1395: Dependency on Vulnerable Third-Party Component

Fix: Removed the ^ semver operator to pin rijndael-js to exactly version 2.0.0, preventing automatic updates to potentially vulnerable 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 security requires vigilance at every dependency declaration. The simple change from "^2.0.0" to "2.0.0" in package.json represents a fundamental security principle: trust must be explicit, not automatic. For cryptographic libraries especially, where the cost of a compromised dependency is total security failure, exact version pinning is non-negotiable.

Review your package.json files today. Identify security-critical dependencies—encryption, authentication, network communication—and pin them to exact versions. Your downstream consumers depend on it.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #358

Related Articles

critical

Slim CLI Unverified Remote Fetch in Version Check

The Slim CLI's version check command fetched remote package metadata without integrity verification, enabling attackers to serve malicious responses through repository hijacking or man-in-the-middle attacks. The fix adds input validation, HTTP status checking, and response schema verification to ensure only legitimate version data is processed.

critical

ensureTrivy() CWE-494: Unverified Trivy Binary Download

The `ensureTrivy()` function fetched the Trivy vulnerability scanner from GitHub releases without verifying its integrity, exposing applications to supply-chain attacks. An attacker in a MITM position or a compromised CDN could substitute a malicious binary that executes with the application's privileges. The fix adds cryptographic verification using SHA256 checksums published alongside each release.

critical

Updater.parseUpdate() CWE-494: Unsigned Metadata Download

The parseUpdate function in the Updater component extracted download URLs from remote server responses without cryptographic verification, enabling supply chain attacks via compromised or spoofed update servers. The fix adds strict URL validation requiring HTTPS and a trusted hostname before accepting any update metadata.

high

fs.readFileSync(process.argv[2]) Path Traversal in Zola Build

A build-time helper that extracts the expected SHA-256 for a downloaded Zola release passed `process.argv[2]` straight into `fs.readFileSync()` with no directory constraint, so any caller able to influence that argument could make the integrity check read an arbitrary file. The fix resolves the requested path and requires it to be a direct child of the tools directory, which is now passed in as an extra argument, and exits with an error otherwise. Because the bytes read become the "expected" che

critical

`requests.get()`/`delete()`/`post()` with `verify=False` in Release

A critical security vulnerability in a release automation script disabled SSL certificate verification on every HTTPS request to GitHub's API. By passing `verify=False` to `requests.get()`, `requests.delete()`, and `requests.post()`, the script exposed OAuth tokens and release binaries to man-in-the-middle attacks on any network the script ran from.

high

installPlugin(): Unvalidated npm Package Names Reach npm install

A plugin manager service exposed an `installPlugin(plugin: PluginInfo)` method that passed `plugin.packageName` and `plugin.version` straight into the platform's npm install routine with no validation, no blocklist, and no integrity verification of the fetched tarball. Because npm treats a non-semver "version" as a fetch specifier — a tarball URL, a git ref, a local path — an attacker who could influence the plugin listing could get arbitrary code installed and executed with full Electron/Node p