VNX-NODE-002 – eval() or new Function() in JavaScript
Overview
This rule detects calls to eval() and new Function() in JavaScript and TypeScript source files. Both constructs compile and execute a string as JavaScript at runtime. When any part of that string is derived from user input — a query parameter, request body, WebSocket message, or any other external source — an attacker can run arbitrary code with the full privileges of the Node.js process. This maps to CWE-94 (Improper Control of Generation of Code).
Severity: High | CWE: CWE-94 – Improper Control of Generation of Code | OWASP ASVS: V5.2.4
eval() and new Function() are not disabled by default in Node.js. They are available in every JavaScript runtime and must be explicitly avoided through code review, linting rules, and Content Security Policy enforcement.
Why This Matters
Code injection via eval() is one of the most direct paths to full server compromise. An attacker who can control the argument to eval() can read environment variables (stealing secrets and API keys), spawn child processes to execute OS commands, open reverse shells, exfiltrate files, or pivot to internal services. Unlike SQL injection, which is constrained to database operations, JavaScript code injection has unrestricted access to the Node.js runtime and everything it can reach.
The new Function() constructor is equally dangerous and is frequently used as an obfuscated substitute for eval(). Code like new Function('return ' + userInput)() is functionally identical to eval(userInput). Sandboxing libraries such as vm2 were once considered mitigations, but every version has been bypassed via prototype chain manipulation or native module escapes — the vm2 project was officially abandoned in 2023 following critical sandbox-escape CVEs (CVE-2023-29199, CVE-2023-30547). There is no reliable sandbox for untrusted JavaScript within Node.js.
OWASP ASVS v4 requirement V5.2.4 states: “Verify that the application does not use eval() or other dynamic code execution features.”
What Gets Flagged
The rule scans all .js, .ts, .jsx, .tsx, .mjs, and .cjs files for the patterns eval( and new Function(.
// FLAGGED: eval with user input
app.post('/calculate', (req, res) => {
const result = eval(req.body.expression);
res.json({ result });
});
// FLAGGED: new Function used to build dynamic logic
const fn = new Function('x', req.query.body);
fn(42);
// FLAGGED: eval even without obvious user input (static analysis cannot
// always trace data flow — treat all eval() as potentially dangerous)
const computed = eval(someVar);
Remediation
Remove
eval()andnew Function()entirely. In virtually every legitimate use case there is a safer alternative that does not require runtime code compilation.For mathematical expressions, use a dedicated, safe evaluator library that parses an AST without executing code:
npm install mathjs # or npm install expr-eval// SAFE: use mathjs for expression evaluation import { evaluate } from 'mathjs'; app.post('/calculate', (req, res) => { try { const result = evaluate(req.body.expression); // parses AST, does not eval res.json({ result }); } catch (err) { res.status(400).json({ error: 'Invalid expression' }); } });For dynamic configuration or plugins, use JSON for data and require plugins from a fixed local path, never from user-supplied strings:
// SAFE: load plugins from a controlled directory, never from user input const PLUGIN_DIR = path.resolve(__dirname, 'plugins'); const pluginName = req.query.plugin.replace(/[^a-z0-9-]/gi, ''); const pluginPath = path.join(PLUGIN_DIR, pluginName); if (!pluginPath.startsWith(PLUGIN_DIR)) throw new Error('Invalid plugin'); const plugin = require(pluginPath);For template rendering, pass user data as context variables — never as the template string itself (see also VNX-NODE-011):
// SAFE: template is a static string; user data is only context const template = fs.readFileSync('./templates/email.html', 'utf8'); const rendered = ejs.render(template, { name: req.body.name });Add ESLint rules to prevent
eval()at the linting stage:// .eslintrc.json { "rules": { "no-eval": "error", "no-new-func": "error" } }Apply a Content Security Policy header that includes
script-srcwithout'unsafe-eval'to prevent client-sideeval()as a defence-in-depth measure. Note that Express does not set any CSP headers by default — use helmet:// Requires: npm install helmet const helmet = require('helmet'); app.use(helmet.contentSecurityPolicy({ directives: { scriptSrc: ["'self'"], // no 'unsafe-eval' }, }));
References
- CWE-94: Improper Control of Generation of Code
- CAPEC-35: Leverage Executable Code in Non-Executable Files
- OWASP ASVS v4 – V5.2.4 Input Validation
- OWASP Node.js Security Cheat Sheet
- vm2 project abandoned – critical sandbox escape
- mathjs safe expression evaluation
- ESLint no-eval rule
- MDN – eval() security concerns
- MITRE ATT&CK T1059.007 – JavaScript