JavaScript injection is a family of attacks in which untrusted input is interpreted as JavaScript or HTML by the browser instead of being treated as inert data, letting an attacker run code in the victim's session. It overlaps heavily with cross-site scripting, but the term is broader: it also covers server-side eval-style flaws, prototype pollution that corrupts application logic, and the way input handled by a JavaScript back end can flow into SQL or command injection. The defenses differ by type, so the useful thing is to name the categories and match each to its fix.
What Counts as JavaScript Injection
At its core, every JavaScript injection bug is a confusion between code and data. Somewhere your application takes a value it does not control (a form field, URL parameter, header, message, database value) and hands it to something that will interpret rather than display it. The interpreter might be the HTML parser, the JavaScript engine via eval, the object model via property assignment, or a downstream query engine. Below are the categories that matter in practice.
Type 1: Cross-Site Scripting (the Big One)
XSS is the dominant form of JavaScript injection: input ends up in a page where the browser parses it as markup and runs any script it contains. The three sub-types:
- Reflected: payload bounces off the server in the immediate response.
- Stored: payload is persisted and served to later visitors.
- DOM-based: client-side JS reads an attacker-controlled source (like
location.hash) and writes it to a dangerous sink (likeinnerHTML) without the server involved.
A minimal illustrative sink:
// vulnerable: comment is attacker-controlled
container.innerHTML = "<p>" + comment + "</p>";
Prevention. Write untrusted data as text, not markup (textContent, not innerHTML). When rich HTML is genuinely required, sanitize with a maintained library like DOMPurify at the sink. On the server, apply context-aware output encoding. Deploy a strict Content-Security-Policy and, in Chromium, Trusted Types as a structural backstop. The full treatment for the client-side variant is in our DOM-based XSS guide.
Type 2: eval and Its Relatives
Passing untrusted input to eval, new Function, or setTimeout/setInterval with a string argument executes it as code directly:
// vulnerable
setTimeout("processUser('" + userName + "')", 100);
A userName of '); stealSession(); // runs attacker code. The same risk exists server-side in Node when input reaches eval, vm.runInThisContext, or a template engine configured to allow arbitrary expressions.
Prevention. Do not use eval on untrusted input, and in practice do not use it at all; there is almost always a direct API. Pass function references to timers, not strings (setTimeout(() => processUser(userName), 100)). For dynamic data structures, use JSON.parse, never eval. Enable a CSP without unsafe-eval so eval throws even if one slips in.
Type 3: Prototype Pollution
A JavaScript-specific injection where an attacker sets keys like __proto__, constructor, or prototype on an object, polluting Object.prototype so every object in the app inherits attacker-controlled properties. It typically arrives through unsafe deep-merge or query-string parsing of nested input:
// merging attacker JSON like {"__proto__": {"isAdmin": true}}
deepMerge(target, untrustedInput); // now {}.isAdmin === true everywhere
The impact ranges from logic bypasses (an isAdmin flag appearing on objects that never set it) to enabling XSS or, in some server setups, remote code execution.
Prevention. Reject or strip __proto__, constructor, and prototype keys during parsing and merging. Create data objects with Object.create(null) so they have no prototype to pollute. Use Map instead of plain objects for untrusted key/value data. Prefer libraries hardened against this; vulnerable versions of merge and query-string utilities are a recurring source, which is exactly what dependency scanning catches.
Type 4: Injection Through the JavaScript Back End Into Other Interpreters
Node back ends pass user input onward, and it can land in a different injectable interpreter:
- SQL injection: input concatenated into a query string. To prevent SQL injection in JavaScript, use parameterized queries:
db.query('SELECT * FROM users WHERE id = $1', [id])withpg, or the placeholder API ofmysql2, and use the query-builder/ORM safe methods (Knex bindings, Prisma, Sequelize) rather than raw interpolation. Never build SQL with template literals containing user values. - NoSQL injection: passing raw request objects into MongoDB queries lets
{ "$gt": "" }-style operators through; cast expected scalars and reject object-typed values where you expect strings. - OS command injection: input reaching
child_process.execruns in a shell. UseexecFile/spawnwith an argument array and no shell, and allowlist the command.
The unifying rule across all four: keep input as a bound parameter or a validated scalar, never as a fragment of the string you hand to an interpreter.
Type 5: Injection via Untrusted Dependencies
A large share of JavaScript injection exposure is code you installed, not code you wrote. A sanitizer, template engine, or parser with a known injection bug is a vulnerability in every app that bundles the affected version, and the npm graph makes it easy to pull one in transitively without noticing.
Prevention. Inventory and patch. An SCA tool such as Safeguard resolves the full dependency graph and flags packages sitting in a known-vulnerable range, including the transitive ones your package.json never names. Pair that with lockfile discipline so a fixed version actually ships.
A Prevention Checklist That Cuts Across Types
- Treat input as data. Bind it as a parameter, render it as text, parse it with
JSON.parse. Never hand it to an interpreter as a string fragment. - Context-aware output encoding for anything rendered into HTML, attributes, JS, or URLs.
- A strict CSP: nonced scripts, no
unsafe-inline, nounsafe-eval. It limits blast radius when a bug slips through. - Trusted Types (Chromium) to structurally forbid unsafe DOM sink assignments.
- Safe library APIs: parameterized DB queries,
execFileoverexec, hardened merge utilities. - Continuous verification: a DAST scan to find live injectable points, static analysis for taint flows, and SCA to catch vulnerable dependencies.
No single control covers the whole family. Encoding fixes XSS output, parameterization fixes SQL, prototype hardening fixes pollution, and CSP catches what leaks. Ship them together.
FAQ
Is JavaScript injection the same as XSS?
XSS is the most common form of JavaScript injection, but the term is broader. JavaScript injection also includes eval-based code execution, prototype pollution, and injection that flows through a JavaScript back end into SQL, NoSQL, or OS command interpreters. XSS is a subset.
How do I prevent SQL injection in a JavaScript/Node application?
Use parameterized queries with placeholders ($1, ?) and pass values as a separate array, or use an ORM/query builder's safe binding API. Never build SQL by interpolating user input into a template literal, and validate the types of values you expect to be scalars.
What is prototype pollution and why is it a JavaScript injection?
It is an attack where input sets special keys (__proto__, constructor, prototype) that modify Object.prototype, so every object inherits attacker-controlled properties. It is injection because untrusted input alters program state; prevent it by stripping those keys, using Object.create(null), or using Map.
Does a Content-Security-Policy prevent JavaScript injection?
It mitigates it. A strict CSP without unsafe-inline and unsafe-eval blocks injected inline scripts and eval from executing, reducing impact. It does not remove the underlying bug, so combine it with encoding, safe APIs, and input handling rather than relying on it alone.