eval() called
| Vulnerability potential | High |
| DDoS potential | Low |
eval executes arbitrary code from a string and disables many engine optimisations
Impact
eval(s) parses and executes the string s as JavaScript in the current scope,
with the current privileges. If any part of s derives from input the program
does not fully control — a request parameter, a config value, a field from a
database, a postMessage payload — the program is executing attacker-authored
code. The consequences are the full power of the host: in a browser it can read
the DOM, cookies and localStorage, exfiltrate data and perform actions as the
user; in Node.js it can reach require, process, the filesystem and the
network, i.e. full server compromise.
Beyond the security hole, eval deoptimises the surrounding code. Because the
engine cannot know what names the evaluated string will reference or create, it
must disable scope-based optimisations for the calling function, so nearby code
runs slower and uses more memory. eval also defeats minifier name-mangling and
dead-code elimination.
Vulnerability potential
eval on attacker-influenced input is a textbook code-injection vulnerability
(CWE-95, “eval injection”).
- Remote code execution. Any string that reaches
evaland contains attacker data lets the attacker run arbitrary JavaScript. In Node this meansrequire("child_process").execSync(...)and full host takeover; in the browser it means script execution in the page’s origin (effectively stored or reflected XSS that bypasses naive output encoding). - Data theft and session hijacking. Injected browser code reads
document.cookie, tokens inlocalStorage, CSRF tokens and form data, and POSTs them to an attacker server, all within the same-origin trust boundary. - Privilege and sandbox escape. Code runs with the privileges of the
calling context, so
evalinside a privileged extension, service worker or server process inherits that authority. - Denial of service. Even without full RCE, an attacker who controls the
string can supply
while(true){}or an allocation bomb to hang or exhaust the single-threaded event loop — hence the Low DDoS rating.
Filtering or escaping the input is not a reliable defence: JavaScript has too
many ways to express the same operation. The only robust fix is to not use
eval on dynamic data at all.
Technical details
eval is a function-valued property of the global object. Called directly
(eval(s)) it runs the code in the caller’s lexical scope, so it can read and
write the caller’s locals. Called indirectly (e.g. (0, eval)(s),
window.eval(s), or aliased) it runs in global scope instead — a distinction
the spec calls direct vs indirect eval. Either way the code executes with the
realm’s full capabilities.
Relatives that are also eval
new Function("..."), and a string passed to setTimeout/setInterval, compile
and run a string the same way (new Function only in global scope). They carry
the same injection risk and should be treated identically.
Strict mode and CSP
Inside a "use strict" eval, variables and functions declared in the string do
not leak into the caller’s scope, which limits some abuse but does not stop code
execution. In the browser a Content-Security-Policy without 'unsafe-eval'
makes eval and new Function throw, which is the strongest mechanical
mitigation.
Legitimate alternatives
To parse data use JSON.parse. To read a property by computed name use bracket
access obj[name]. To dispatch on a value use a lookup table/Map of
functions. None of these execute arbitrary code.
Catching the issue
Linters and static analysis
ESLint’s no-eval and no-implied-eval (the latter covers new Function and
string setTimeout) flag these constructs. Security-focused scanners
(eslint-plugin-security’s detect-eval-with-expression, Semgrep, CodeQL’s
js/code-injection, SonarQube) report eval-injection sinks and can trace
tainted input into them.
Runtime / deployment
In the browser, ship a Content-Security-Policy that omits 'unsafe-eval' so any
remaining eval/new Function throws at runtime. In Node, the
--disallow-code-generation-from-strings flag (or the vm option of the same
name) makes eval/new Function throw process-wide.
Review rule
Any eval whose argument is not a string literal the developer fully controls is
a defect. Even literal eval is worth replacing with JSON.parse, a lookup
table, or bracket property access.
How to reproduce
Observe that data flowing into eval is executed: the “calculator” input below
runs an arbitrary side effect instead of doing arithmetic.
// pretend `expr` came from a URL parameter or request body
function calculate(expr) {
return eval(expr); // code-injection sink
}
calculate("1 + 2"); // 3 (intended)
calculate("globalThis.pwned = true; 0"); // executes attacker code
console.log(globalThis.pwned); // true
// safe replacement for the intended use case:
const safe = (a, b) => a + b; // or JSON.parse for data