Skip to content

JavaScript Tutorial JavaScript Security Basics

DOM-Based XSS and Dangerous APIs

Lesson 231 of 258 3 min read By Deepak Paswan Updated Oct 4, 2026

In DOM-based XSS, the problem is entirely in the page’s own JavaScript: it takes data from somewhere an outsider can influence (a source) and passes it to something that interprets it as code or HTML (a sink). The server may never see the data.

Sources: where untrusted data enters

  • location.search, location.hash, location.href
  • document.referrer
  • window.name
  • postMessage data
  • Web storage and cookies (they can be set by an earlier attack, or by another script)
  • API responses and anything typed by a user

Sinks: places that can execute or parse

SinkWhy it is dangerousSafer choice
innerHTML, outerHTML, insertAdjacentHTMLparses HTMLtextContent, createElement
document.write()writes raw HTMLDOM methods
eval(code)runs a string as codeJSON.parse for data, real functions
new Function("...")runs a string as codea normal function
setTimeout("code string", ...)a string becomes codesetTimeout(() => ..., ...)
element.setAttribute("onclick", ...) and inline onclickstring becomes codeaddEventListener
location = userValue or a.href = userValuejavascript: URLsvalidate the URL
script.src = userValueloads attacker codenever from untrusted data
iframe.srcdocparses HTMLavoid with untrusted data

The pattern to avoid

script.js
JS
// Takes a name from the URL and writes it as HTML. Do not do this.
const name = new URLSearchParams(location.search).get("name");
document.querySelector("#welcome").innerHTML = "Welcome, " + name;

The safe pattern

index.html
HTML
<p id="welcome"></p>
<script>
  // Imagine this came from location.search
  const name = "<img src=x> Maya";

  document.querySelector("#welcome").textContent = "Welcome, " + name;   // text, never parsed
</script>

Keep data as data

  • Parse JSON with JSON.parse, never eval.
  • Pass functions to timers and event listeners, never strings.
  • Use dataset and setAttribute for data attributes, not for event handlers.
  • If a value must be used in a URL, build it with URL/URLSearchParams.

Check messages from other windows

postMessage data is untrusted. Always check who sent it.

script.js
JS
window.addEventListener("message", event => {
  if (event.origin !== "https://trusted.example") return;   // ignore anyone else
  if (typeof event.data !== "string") return;
  status.textContent = event.data;                           // and treat it as text
});

Content Security Policy and Trusted Types

Two browser features add a safety net:

  • Content Security Policy (CSP): a response header that tells the browser which scripts may run, for example banning inline scripts and eval. Even if something slips through, the browser refuses to execute it.
  • Trusted Types: a browser feature (not supported everywhere) that makes dangerous sinks accept only values that went through an approved policy.

Both are configured on the server or in the page’s security headers, and they take some planning. They are excellent second layers, not replacements for writing safe code.

Review habit

When you read code, search for the sinks above. For each one ask: can an outsider influence this value? If yes, change it.

info
Linters help
ESLint security plugins and code scanners can flag innerHTML and eval uses automatically. Turn them on in real projects.