Quick Answer
eval() and new Function() execute arbitrary JavaScript strings at runtime, meaning any user-controlled input passed to them becomes executable code. AI tools reach for these constructs when building dynamic formula evaluators, template engines, or config parsers. In almost every real-world case there is a safer alternative - JSON.parse, a math expression library, or a sandboxed interpreter.
What Makes eval() So Dangerous?
eval() takes a string and runs it as JavaScript code in the current scope, with full access to all variables, the DOM, network calls, and the Node.js process. There is no sandbox. There is no permission check. Whatever string you pass in, the JavaScript engine executes it with your app's full privileges.
OWASP lists code injection - the category that covers eval() misuse - as part of its Top 10 Web Application Security Risks. Veracode's 2023 State of Software Security report found that injection vulnerabilities appear in 74% of applications in some form. Dynamic code execution via eval() is one of the hardest injection variants to detect because it doesn't look like SQL injection or XSS - it looks like a feature.
GitHub's analysis of security advisories found that eval-based code injection vulnerabilities consistently appear in the top 20 JavaScript CVEs each year. AI-generated code raises the risk because models use eval() as a shortcut when they need to parse complex expressions or dynamically construct behavior based on user input.
How AI Tools Reach for eval()
The typical scenario: a founder asks Bolt or Cursor to "let users enter a custom formula" or "dynamically evaluate a config expression." The AI produces working code instantly - using eval() because it's the simplest path to a working demo.
// ❌ BAD - eval() with user input
app.post('/api/calculate', (req, res) => {
const { formula } = req.body;
// AI-generated: "this evaluates any math formula the user enters"
const result = eval(formula);
res.json({ result });
});
// ❌ BAD - new Function() with user-controlled string
function buildFilter(userExpression) {
// AI-generated: "creates a dynamic filter function"
const fn = new Function('item', `return ${userExpression}`);
return items.filter(fn);
}
// ❌ BAD - setTimeout with string argument (also eval!)
setTimeout(`updateDashboard('${userId}')`, 1000);
All three of these patterns give attackers a direct code execution path. An attacker sending formula=require('child_process').execSync('cat /etc/passwd').toString() to the first endpoint gets full shell access on your server. The second lets an attacker exfiltrate data by constructing a filter expression that calls fetch(). The third is a classic XSS vector if userId comes from user input.
The Full Family of Dynamic Execution Functions
| Function | Risk | Common misuse pattern |
|---|---|---|
eval(str) |
Critical | Formula evaluation, JSON parsing fallback |
new Function(str) |
Critical | Dynamic filter/sort callbacks, template functions |
setTimeout(str, ms) |
High | Delayed callbacks with interpolated values |
setInterval(str, ms) |
High | Polling with dynamic function names |
document.write(str) |
High | Dynamic HTML injection (also XSS risk) |
execScript(str) |
Critical | Legacy IE pattern, still found in AI-generated code |
A Apiiro study found that AI-generated code is 2.74x more likely to contain injection-class vulnerabilities than human-written code. Dynamic code execution patterns are a significant contributor because they appear in AI training data as legitimate patterns - they were legitimate before user-controlled input entered the picture.
Safe Alternatives for Every Use Case
For every scenario where AI reaches for eval(), there's a safe alternative that handles 90% of real use cases:
// ✅ GOOD - Math expressions: use a safe parser library
import { evaluate } from 'mathjs';
app.post('/api/calculate', (req, res) => {
const { formula } = req.body;
try {
// mathjs evaluates math expressions without code execution
const result = evaluate(formula); // "2 + 3 * sin(pi/2)" → safe
res.json({ result });
} catch (err) {
res.status(400).json({ error: 'Invalid formula' });
}
});
// ✅ GOOD - JSON parsing: use JSON.parse, not eval
const data = JSON.parse(userInput); // Never: eval('(' + userInput + ')')
// ✅ GOOD - Dynamic property access: use bracket notation
const key = req.query.field; // e.g. "name" or "email"
const allowedFields = ['name', 'email', 'age'];
if (allowedFields.includes(key)) {
const value = user[key]; // safe - no eval needed
}
// ✅ GOOD - Dynamic callbacks: define functions statically
const sorters = {
name: (a, b) => a.name.localeCompare(b.name),
date: (a, b) => new Date(a.date) - new Date(b.date),
price: (a, b) => a.price - b.price,
};
const sortFn = sorters[req.query.sort] ?? sorters.name;
items.sort(sortFn);
The key principle: define your dynamic behavior in code, not in strings. If users need to choose between behaviors, give them a controlled set of options and map those options to real functions.
When eval() Is Unavoidable: Sandboxing Options
There are legitimate use cases - plugin systems, no-code builders, user-defined scripting - where you genuinely need to execute user-supplied code. In those cases, the answer is isolation, not eval().
vm2 / isolated-vm: Node.js sandboxes that run code in a separate V8 isolate with no access to your process. Use isolated-vm for production-grade isolation. Note: vm2 has had CVEs and is no longer maintained; prefer isolated-vm.
Web Workers: In the browser, Web Workers run in a separate thread with no DOM access. You can pass code strings to a worker and execute them without exposing your main application context.
Deno: If you're building a runtime that executes user scripts, Deno's permission system provides OS-level sandboxing. No file system, network, or env access unless explicitly granted.
Even with sandboxing, validate and sanitize inputs, rate-limit execution, and set CPU/memory timeouts. Sandboxes contain damage - they don't eliminate the need for input validation.
How to Find eval() Usage in Your Codebase
Search your project for eval(, new Function(, setTimeout(", and setInterval(". Any hit where the argument includes a variable, template literal, or string concatenation is a potential vulnerability. Pay special attention to places where the variable originates from req.body, req.query, req.params, or any user-facing input.
Tools like VibeDoctor (vibedoctor.io) automatically scan your codebase for dynamic code execution patterns and flag specific file paths and line numbers. Free to sign up.
Content Security Policy (CSP) headers can also help. Setting script-src 'self' without 'unsafe-eval' causes browsers to block eval() calls, which gives you both a runtime protection layer and a detection mechanism - you'll see CSP violations in your browser console if any eval() calls fire.
FAQ
Is eval() ever safe to use?
Only if the string being evaluated is entirely developer-controlled - never touches user input, environment variables, database values, or network responses. In practice, that covers very few real use cases. If you're certain the input is static, consider whether a safer pattern (like a lookup table or conditional) would be more maintainable anyway.
Is new Function() safer than eval()?
new Function() runs in the global scope rather than the local scope, which limits access to local variables. But it still executes arbitrary code with full access to globals, DOM APIs, and Node.js built-ins. It is not meaningfully safer when accepting user input.
Does ESLint catch eval() usage?
Yes - the no-eval and no-implied-eval ESLint rules flag eval() and string-argument forms of setTimeout/setInterval. Enable the eslint:recommended ruleset or the security plugin (eslint-plugin-security) to catch these patterns automatically.
What about template engines that use eval internally?
Some older template engines (EJS, Handlebars with custom helpers) use eval() or new Function() internally. The risk depends on whether user input reaches the template compilation step. Always use the engine's escaping mechanisms and never pass raw user input as template source code.
How do I safely evaluate math formulas from users?
Use a purpose-built math expression library like mathjs, expr-eval, or nerdamer. These parse mathematical expressions into ASTs and evaluate them without executing arbitrary JavaScript. They also reject non-math syntax, so require('child_process') simply throws a parse error.