logo

DOMinator

GitHub action badge

DOMinator is a Chrome (and Firefox) extension for developers and security testers hunting DOM XSS — especially postMessage bugs, but also URL/DOM sources, prototype pollution and DOM clobbering. It records traffic, classifies listeners, taints payloads through transforms, confirms when a value actually reaches a sink, and turns that into a replay / PoC.

What it captures

Every window.postMessage call and every message event on the inspected tab is recorded in the background worker, per tab, and shown live in the toolbar popup and in the DOMinator DevTools panel.

Messages

Listeners

Other findings (Findings tab)

Views

Active testing

DOMinator is not only a recorder — these features turn a capture into a test.

Replay / re-sender. Every message row in the DevTools panel has a Replay button. It opens an editor pre-filled with the captured payload. For nested JSON that is a collapsible tree with per-field inject (XSS / canary / __proto__). You can pick the target frame, targetOrigin, and the origin presented to listeners:

The page helper is window.__DOMINATOR__.replay(data, origin). Replay is only available in the DevTools panel.

Intercept. Pause a message in the wrapped listener, edit origin + payload, then deliver or drop. One-shot init messages no longer sail past you. Auto-delivers after 20s so the page cannot hang forever.

Auto-probe / canary fuzz. When enabled, each new listener is hit with a unique canary string, a nested JSON probe and a __proto__ payload. Live traffic can also have a canary appended to every string leaf. Confirmed sink flows show up in the message list as before.

Dynamic sink trace. Taint survives JSON.parse / stringify, encodeURIComponent / decodeURI, btoa / atob, replace, split/join, case transforms and friends. Runtime hooks cover the sinks the static scan already names, including location.href / hash / search, new Function, element.src, script.text, DOMParser.parseFromString, Range.createContextualFragment, jQuery html/append, document.cookie, localStorage.setItem, fetch / XHR, and script insertion. If Trusted Types or CSP block the assignment, the flow is still recorded as blocked.

Configurable sources/sinks. Options page (and the probe strip in the panel) let you turn sinks off when they break a site, raise the taint minimum, and disable canary injection.

Auto-generated PoC. The detail pane of any listener generates a ready-to-serve proof of concept — iframe, window.open / opener, or a console / Burp snippet. Payload templates prefer a captured JSON leaf that actually reached the listener, then sink-aware XSS / JS / javascript: / prototype pollution. Copy it or download it.

Headless / Playwright

The page realm exposes:

window.__DOMINATOR__.dump()
// { messages, listeners, flows }

Load a URL with the extension installed, wait for network idle, then page.evaluate(() => window.__DOMINATOR__.dump()) in each frame you care about.

Firefox

yarn build:firefox

loads as a temporary add-on from dist/. Safari is not a target yet.

Known limitation

A send to a cross-origin window (iframe.contentWindow.postMessage(…), parent.postMessage(…) across origins) goes through that window's cross-origin proxy, which no page script can hook, so the send has no call site. The message is still captured in the receiving frame, with its origin and the listeners it reaches. Origin spoofing is a presented origin on the wrapped listener (or a synthetic MessageEvent); it cannot change the browser-enforced origin of a real postMessage.