Skip to main content

Pentrova is launching soon. Join the waitlist for early access.Join the waitlist

Research

Sample

Canary-based taint tracking for DOM XSS: catching client-side bugs static analysis misses

How canary-based taint tracking tags every DOM ingress channel and watches a broad sink surface to catch DOM XSS that static analysis and reflection scans miss.

Pentrova Research Pentrova Research
8 min read

DOM-based cross-site scripting is one of the easier vulnerabilities to introduce and one of the harder ones to find with confidence. Static analysis cannot see what the browser actually renders, and a reflection-style scan misses anything that only materialises after a framework hydrates. Pentrova’s approach is to tag every ingress channel with a unique, distinguishable canary and then observe which sinks execute it.

This post explains why DOM evades traditional tooling, what taint tracking with canaries looks like in practice, and which sources and sinks the technique covers.

Why DOM is hard to detect#

DOM (CWE-79, and the client-side variant in the OWASP DOM XSS prevention cheat sheet) lives entirely in the browser. The tainted value flows from a JavaScript-readable source into a dangerous sink without ever round-tripping through the server in a way a proxy can flag.

  • Static analysis sees source code, not the hydrated DOM, so it cannot follow a value through a framework’s runtime transformations.
  • Reflection scanners fire payloads and watch the server response, but a DOM-only bug never appears in that response — it executes after client-side routing or a framework re-render.

The result is a long tail of false negatives that conventional tooling structurally cannot reach.

The ingress surface we cover#

Pentrova injects canaries through every DOM-reachable source a real attacker can control:

  • Cookies
  • window.name
  • postMessage payloads
  • URL hash
  • URL search parameters
  • document.referrer

Each canary is a short random token with a predictable wrapper — for example pv_a9f3_{channel}_ — so the sink detector can attribute a hit back to the exact ingress that carried it. The persistence of window.name across navigations makes it a particularly interesting source; we cover that case in canary patterns for window.name.

The sinks we watch#

A broad catalog of DOM sinks is instrumented, covering the usual suspects and the less-obvious ones:

innerHTML, outerHTML, document.write, document.writeln,
insertAdjacentHTML, eval, Function, setTimeout(string),
setInterval(string), location, location.href,
location.assign, location.replace, src attributes on
script/iframe, javascript: URIs, srcdoc, style expressions,
Element.setAttribute for event handlers

When a canary reaches one of these sinks, Pentrova captures the call stack, the framework component that wrote the value, and the path from source to sink. That path is the proof.

Why canaries instead of payloads#

A traditional scanner fires hundreds of payloads and watches for alerts. That approach is noisy, slow, and brittle against frameworks that sanitise differently based on context. A canary is passive: it does not try to execute, it just carries a unique identifier. The sink detector tells Pentrova where it landed, and only then does the platform craft a context-appropriate payload to confirm execution.

This is the same separation of concerns that drives deterministic proof across the platform: detection identifies the reachable path; confirmation reproduces the exploit. DOM taint is a browser-side signal, so it runs as part of every Web App Pentesting engagement and is documented in the platform pipeline.

What this eliminates#

  • False positives from reflections that are never written to a dangerous sink.
  • False negatives from sinks that only fire after a client-side route change or framework hydration.
  • The “this parameter looks reflected” long tail that never had evidence of exploitability in the first place.

Key takeaways#

  • DOM executes in the browser, so static analysis and reflection scanners structurally miss it.
  • Canary taint tracking tags six controllable sources and follows them to a broad catalog of sinks.
  • Passive canaries identify the reachable source-to-sink path; a context-aware payload then confirms execution.
  • The output is a proof path — source read, transformations, sink write — not a maybe.

FAQ#

What is the difference between reflected and DOM ? Reflected is reflected by the server into the response. DOM never touches the server response — a client-side script reads a controllable source and writes it to a dangerous sink. That is why server-response scanning misses DOM .

Why use canaries instead of just firing payloads? Payload-spraying is noisy and context-fragile. A passive canary reveals which sink a given source reaches; Pentrova then crafts a single context-appropriate payload to confirm execution, which is far more reliable than guessing.

Does this run against single-page apps? Yes. The technique instruments the live page after hydration, so it follows tainted values through client-side routing and framework re-renders — exactly where SPA DOM hides.

See DOM taint tracking in Web App Pentesting, or start a free engagement.

Updated

Written by

Pentrova Research Pentrova Research

Pentrova Research writes about deterministic offensive-security proof, LLM-driven pentest chains, and how to ship exploit-grade evidence into engineering pipelines.

Keep reading

Site search

↑↓ navigateEnter openEsc close