Safeguard
Security

JavaScript Code Injection: How It Works and How to Prevent It

JavaScript code injection happens when an application treats untrusted input as executable code. This guide explains the attack class conceptually and focuses on detection and prevention.

Priya Mehta
Security Analyst
6 min read

JavaScript code injection is a vulnerability class where an application takes untrusted input and evaluates it as executable JavaScript, letting an attacker run their own code inside your application's context. It is the JavaScript member of the broader injection family, and like all injection it comes down to one failure: data that should have stayed data crossed the line into code. The distinction that makes code injection especially severe is that the attacker is not injecting into a query language or a markup document, they are injecting into the very language the runtime executes, which on a Node.js server can mean full command execution on the host.

This is a defensive walkthrough. I will explain how the flaw arises and, more importantly, how to keep it out of your code, without providing anything resembling a working exploit.

What makes code injection possible

Code injection requires two ingredients: a function that turns strings into running code, and untrusted input flowing into it. JavaScript has several such functions, and they are the ones to audit for:

  • eval(), which executes a string as JavaScript in the current scope.
  • The Function constructor, which builds a callable from a string body.
  • setTimeout and setInterval when passed a string instead of a function reference.
  • On the server, Node's child_process and vm APIs, which can turn input into shell commands or evaluated code.

The moment any of these receives a value that traces back to user input, you have a potential code-injection point. The vulnerable shape looks like this in the abstract:

// Vulnerable pattern: user input flows into a string evaluator
function computeExpression(userInput) {
  return eval(userInput); // never do this
}

If userInput is meant to be a number like 2 + 2 but the attacker sends something else, the runtime executes whatever they sent. The problem is not the specific payload; it is that eval will run any valid JavaScript.

Where it shows up in real apps

The pattern rarely looks as blatant as calling eval on a form field. It hides in more reasonable-seeming code:

  1. Dynamic configuration or rule engines that let users define "expressions" and evaluate them for convenience.
  2. Template rendering that compiles user-supplied templates by generating and running code.
  3. Deserializing data formats by evaluating them instead of parsing them, the classic mistake of using eval to parse JSON instead of JSON.parse.
  4. Server-side rendering that interpolates untrusted values into a string later passed to a code-generating function.

The through-line is developer convenience. Every code-injection sink exists because evaluating a string felt easier than writing a real parser or a constrained interpreter.

Preventing JavaScript code injection

The defenses are direct and, unlike some security work, mostly about not doing things.

First and most important: do not evaluate untrusted input. If you never pass user data to eval, Function, or a string-form timer, this entire vulnerability class disappears from that code path. The overwhelming majority of eval uses have a safe alternative, and code injection javascript flaws almost always start with an eval that should never have been written.

// Safe: parse structured data instead of evaluating it
const data = JSON.parse(userInput);

// Safe: use a lookup table instead of evaluating an operation name
const ops = { add: (a, b) => a + b, sub: (a, b) => a - b };
const result = ops[operation]?.(x, y);

Second, if you genuinely need to evaluate user-defined expressions, do not use the JavaScript engine to do it. Use a purpose-built, sandboxed expression library that supports only a restricted grammar (arithmetic, comparisons) and cannot reach the runtime, the filesystem, or the network. A constrained interpreter is a feature, not a limitation.

Third, on the server, avoid building shell commands from input. Use APIs that take an argument array rather than a command string, so the arguments can never be interpreted as additional commands.

// Safe: arguments are passed as data, not parsed as a command line
const { execFile } = require("node:child_process");
execFile("convert", [inputPath, "-resize", "100x100", outputPath]);

Fourth, validate and constrain input at the boundary. If a field should be a number, coerce and check it. Allowlisting the acceptable shape of input is far stronger than trying to blocklist dangerous characters, which attackers are endlessly creative at evading.

Detecting it before it ships

Code-injection sinks are visible in source, which makes static analysis effective here. A scanner that traces untrusted input to eval, the Function constructor, or child_process will flag the dangerous flow, and this is one of the higher-confidence findings static tools produce because the sinks are so specific. Our static code scanning walkthrough covers how that taint tracking works in practice.

Watch the dependency angle too. A vulnerable third-party package that itself calls eval on data you pass through can inject on your behalf, so a clean scan of your own code is not the whole story. Tracing that through an SCA tool such as Safeguard surfaces when a transitive dependency is the one doing the unsafe evaluation.

FAQ

What is JavaScript code injection?

It is a vulnerability where an application evaluates untrusted input as executable JavaScript, letting an attacker run code in the app's context. On a Node.js server this can escalate to running operating-system commands on the host, making it one of the most severe injection flaws.

Is eval always dangerous in JavaScript?

eval is dangerous whenever the string it evaluates can be influenced by untrusted input, because it will execute any valid JavaScript. Nearly every legitimate use has a safe alternative, such as JSON.parse for data or a lookup table for dynamic operations, so avoid eval on user input entirely.

How do I safely evaluate user-defined expressions?

Do not use the JavaScript engine. Use a sandboxed, purpose-built expression library that supports only a restricted grammar (arithmetic and comparisons) and cannot access the runtime, filesystem, or network. A deliberately limited interpreter is the safe choice.

How can I detect code injection in my codebase?

Static analysis is effective because the sinks (eval, the Function constructor, string-form timers, and child_process) are specific and visible in source. A taint-tracking scanner that follows untrusted input to those sinks produces high-confidence findings, and dependency scanning catches unsafe evaluation inside packages you import.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.