Intentionally vulnerable static site used to test XSS scanners and manual testing techniques against DOM-based cross-site scripting patterns.
⚠️ This site is deliberately insecure. Every vulnerability here is intentional and exists for security testing and learning purposes only. Do not reuse this code in production.
Server-side reflected XSS test targets (like testphp.vulnweb.com) are plentiful, but static-site DOM XSS is a different attack surface: there's no backend to reflect input, so the vulnerability lives entirely in client-side JavaScript reading location.search or location.hash and writing it into the DOM unsafely.
This repository is a small, self-contained page with a few classic vulnerable patterns side by side with a fixed/safe equivalent, for comparison.
| # | Sink | Source | Notes |
|---|---|---|---|
| 1 | innerHTML |
?q= query param |
Classic query-string DOM XSS |
| 2 | innerHTML |
# URL fragment |
Never sent to the server — purely client-side, invisible to server logs/WAFs |
| 3 | document.write |
?name= query param |
Different sink behavior than innerHTML (can inject raw <script> tags) |
| 4 | textContent |
?safe= query param |
Safe — same input, properly escaped, shown for comparison |
Visit the deployed page and append a payload like:
https://xss-lab.pages.dev/?q=<img src=x onerror=alert(1)>
https://xss-lab.pages.dev/?name=<script>alert(2)</script>
https://xss-lab.pages.dev/#<img src=x onerror=alert(3)>Or scan with Dalfox
dalfox scan "https://xss-lab.pages.dev/?q=<img src=x onerror=alert(1)>" --skip-discoveryThis website is a good illustration of Dalfox's detection tiers. Since the DOM sinks here are written entirely by client-side JavaScript — never by the server — findings surface as [A] (static AST source→sink analysis), not
[V]. [V] requires the payload to appear in a DOM tree parsed from the actual server response, which never happens for pure client-side DOM-XSS: the payload is only ever written into the page by JavaScript, after the browser has already received and rendered the (unchanging) static response. This is expected, structural behavior — not a scan failure — see Dalfox's Detection Model docs (added in hahwul/dalfox#1244) and the discussion below for the full explanation.
This lab was used as a real test case in hahwul/dalfox#1238, which started as a reported inconsistency (--skip-mining-dom appearing to have no effect) and turned into a full clarification of Dalfox's detection architecture:
-
Dalfox has three independent subsystems: parameter mining (harvesting candidate param names from HTML), AST source→sink analysis (static parsing of inline JS to trace data flow from sources like
location.search/location.hashto sinks likeinnerHTML/document.write), and reflection + DOM verification (checking whether a payload appears in the real HTTP response).--skip-mining-domonly ever controlled the first of these — it was never the mechanism producing this lab's findings. -
[V](verified) does not mean a headless browser executed the payload — Dalfox v3.1.2 has no browser and no CDP client. It means the payload was matched in a DOM tree parsed from the real server response (via a CSS-selector-style check, or an executable scheme landing in a dangerous attribute). Still static analysis, just on a real response rather than the raw JS source. -
Because this lab's vulnerabilities are all pure client-side DOM-XSS — written into the page only after the browser renders it — the payload never touches the actual server response bytes. That means
[V]has structurally nothing to match here, and--only-poc vcorrectly returns zero findings on this site. This is by design, not a gap. -
The issue directly led to hahwul/dalfox#1244, which fixed a misleading summary line (
XSS found 0 XSSpreviously printed above real[A]findings), surfaced the AST-derived source→sink description in plain output instead of hiding it in JSON, fixed AST findings reporting-instead of the real parameter name, and added a permanent "Detection Model" documentation page defining what[V],[A],[R], and[I]actually mean.
Open follow-up: the discussion also led to hahwul/dalfox#1245, an open proposal for an opt-in headless verification pass (--headless-verify) that would actually load a finding's PoC URL in a real browser and promote genuinely-executing [A] findings to [V]. That is the real path from static inference to runtime confirmation for websites like this one — until it lands, treat every [A] finding from this lab (or any pure client-side DOM-XSS target) as something to confirm manually in a browser, not as a final verdict.
This is a static website with no build step. Deployed via Cloudflare Pages directly from this repository.
MIT License, please check LICENSE