xss.cool

An HTTP response playground for browser / web-security testing. Build any response from URL params, save it as a share, and capture incoming requests.

Crafted-response URLs

Every URL on the site can serve a custom HTTP response. The response is described entirely in the query string — no signup, no save required. Use this when you want a one-off fixture: a 302 to your attacker, an HTML page with a specific CSP, a JSON endpoint that returns slowly.

Quick example

https://xss.cool/?status=302&redirect=https://example.com&h[X-Frame-Options]=DENY

This serves a 302 to example.com with an extra X-Frame-Options: DENY header. Tack &dryrun=1 on the end to see exactly what would be sent without firing the response.

Parameters

ParamWhat it does
statusStatus code, 200–599 (default 200). Aliases: statuscode, code.
delayDelay the response by N ms (max 300000).
chunks + chunkdelaySend the body in N pieces (1–64) with a pause of M ms (max 60000) between them. Chunked transfer-encoding, so the client sees each piece as it lands. Good for timing oracles.
redirectSets Location. Alias: redir. Pair with status=302.
html / script / js / text / json / bodyBody. First defined wins (in this order). All sent raw and pick the matching Content-Type — except script, which wraps in <script>…</script>. html and body are functionally identical; body just sits at the end of the precedence chain.
h[Header-Name]=valueCustom header (qs-style nesting). Repeat the same key for multiple values. Aliases: header[…], headers[…]. h[Content-Type] overrides the default; h[Content-Length] is passed through untouched.
dryrun=1Don't send the response — return JSON describing what would have been sent. Also works on /s/<id>.

The editor

The home page is a live editor that builds the URL above as you type. Status, delay and body type sit next to the tabs; the body editor (Monaco) handles HTML / JS / JSON with syntax highlighting and has a right-click encode/decode menu (URL-encode, base64, etc). Switch between Body, Headers, and Captures with the tabs (⌘1 / ⌘2 / ⌘3; ? for the full shortcut list).

The editor is either a scratch document or an open share. Save (⌘S) creates a share from scratch and moves the address bar to /e/<id>; on an open share it updates in place. Save as new forks. New clears the editor (undoable from the toast). The Domain switcher in the topbar picks which host the generated URLs print — useful for cross-origin tests (see below).

Shares

A share is a saved editor state. One id, two views:

The Shares menu in the topbar lists everything you've saved with copy, open, rename, submit-to-library and delete. Shares get a name automatically (the page title or first line); click the name to rename.

Captures — receiving requests

The Captures tab creates a webhook-sink endpoint at /c/<id>. Anything that hits it (any method, any subpath) is logged with method, path, headers, body (1 MB cap), and source IP. The panel streams new requests live via Server-Sent Events.

Use it for exfil targets, oracle pings, or any "did this fire?" check. Captures expire after 7 days. The Body editor toolbar has an "Insert capture URL" button that pastes a capture URL at your cursor.

Cross-origin testing

The platform serves multiple host domains (xss.cool, ctf.baby, …) from one backend. Every share and capture is reachable on all of them. To set up a cross-origin PoC without standing up your own attacker server:

  1. Save your attacker fixture once. Reference it on host A — e.g. https://xss.cool/s/<id>.
  2. Reference the victim URL on host B — e.g. https://ctf.baby/?html=….
  3. The two are now genuinely cross-origin in the browser (CORS, COOP/COEP, postMessage, SameSite cookies, CSP frame-ancestors) even though they share a backend.

Ownership and the CLI

Whoever creates a share or capture owns it. Browsers carry ownership in the xsscool_owner cookie; scripts send the same token as Authorization: Bearer <token>. Get your browser's token from the ⋯ → API token menu, or mint a fresh one with POST /api/token.

The xsscli CLI wraps the whole API. Install it from this deployment (Node 26+ required):

curl -fsSL https://xss.cool/cli/install.sh | sh     # or: npm i -g https://xss.cool/cli/xsscool-cli.tgz
xsscli login <token>                 # or: xsscli token new
xsscli share '<h1>hi</h1>' --status 418 --header 'X-A: 1' --name demo
xsscli list · xsscli get <id> · xsscli update <id> --status 500 · xsscli rm <id>
xsscli dryrun 'https://xss.cool/?status=302&redirect=https://example.com'
xsscli capture new · xsscli capture tail <id>     # streams requests as JSON lines
xsscli capture rm <id|url> -f                      # delete a sink and its log
Every command takes --json for machine-readable output and --host to target another deployment or domain.

API reference

For machine consumers (LLMs, scripts, integrations) the canonical schema lives at /api/schema.json (versioned JSON) and the terse summary at /llms.txt. Errors are JSON { error, issues?, hint? }.

EndpointNotes
POST /api/sharesBody: { state, name?, ttlDays? }. Returns the share with absolute replayUrl / editorUrl.
GET /api/shares/mineYour own shares (owner token).
GET /api/shares/:idOne share with its state. Public; owned tells you if you can edit.
PATCH /api/shares/:idOwner only. Body: { state?, name?, ttlDays?, submittedPayloadId? }.
DELETE /api/shares/:idOwner only.
GET/POST /api/tokenCurrent owner token / mint a fresh one.
POST /api/capturesReturns { id, captureUrl, streamUrl, requestsUrl, expiresAt }.
ANY /c/:id (and subpaths)Capture sink. Body capped at 1 MB.
GET /c/:id/streamSSE feed of incoming requests.
GET /api/payloadsPublic community-curated payload templates.
GET /api/config{ domains: string[] } — the public hosts this deployment serves.

Notes

For a one-screen reference suitable for LLMs, see /llms.txt.