# xss.cool A tiny HTTP response playground. Build any HTTP response from URL parameters, save it for replay, capture incoming requests, or craft byte-exact malformed responses. The same backend serves multiple host domains in parallel — currently xss.cool and marquee.lol — so any share or capture is reachable on every host. Pick whichever domain fits your test (cross-origin payloads, etc). ## Endpoints - GET / — editor SPA (no params); with params, serves a crafted response. - GET /?... — same crafted-response semantics on any path. - POST /api/shares — save a sane or raw share. See "POST /api/shares" below. - GET /api/shares/mine — list your own shares (owner cookie). - GET /api/shares/:id — get one share's JSON (owner-gated). - PATCH /api/shares/:id — rename / mark submitted. Body: { name?: string|null, submittedPayloadId?: string|null }. - DELETE /api/shares/:id — delete your own share (owner cookie). - POST /api/captures — create a capture endpoint (webhook-sink). Returns { id, captureUrl, streamUrl, expiresAt }. - GET /api/payloads — list community-curated payload templates (public list). - POST /api/payloads — submit a payload template for review (5/hr/owner). - GET /api/payloads/mine — list submitter's own payloads (any status). - GET /api/config — { domains: string[] } — list of public hosts the deployment serves. - GET /s/:id — replay a saved share (sane: rendered response; raw: 308 → raw.). - GET /e/:id — open the editor pre-loaded from the share (sane only; raw returns 400). - ANY /c/:id (and subpaths) — capture sink. Logs method, headers, body (1MB cap), query, source IP. - GET /c/:id/stream — Server-Sent Events stream of incoming requests for capture :id. - GET /api/captures/:id/requests?since=&limit= — list captured requests. - GET /api/schema.json — machine-readable param schema. - GET https://raw./?raw= — raw HTTP server (TCP+TLS passthrough). Returns byte-exact saved raw bytes; does not parse the response, so malformed status lines / mismatched Content-Length / weird CRLF survive. ## Crafted-response query parameters Status: - status, statuscode, code (int 200-599; default 200; first defined wins) Delay: - delay (milliseconds; clamped to 0..300000) Redirect (sets Location header): - redirect, redir (first defined wins) Body (checked in this order; first match used): - html=... — raw, Content-Type: text/html - script=... — wraps in , Content-Type: text/html - js=... — raw, Content-Type: application/javascript - text=... — raw, Content-Type: text/plain - json=... — raw, Content-Type: application/json - body=... — raw, Content-Type: text/html (same as html=, just lower precedence) Custom headers (qs-style nested): - h[Header-Name]=value preferred form - header[Header-Name]=value alternate - headers[Header-Name]=value alternate - Repeat the same key for multiple values (joined with ", "). Self-verification: - dryrun=1 — instead of sending the response, return JSON { status, headers, body, contentType, delay, method }. Useful when you want to confirm a crafted URL without curl -sI + curl -s. Examples: - /?status=418&html=

hi

- /?delay=3000&text=slow - /?redirect=https://example.com&status=302 - /?h[X-Frame-Options]=DENY&h[Content-Security-Policy]=default-src+'self'&html=

hi

- /?h[Set-Cookie]=a%3D1&h[Set-Cookie]=b%3D2 - /?status=302&redirect=https://example.com&dryrun=1 → JSON describing what would have been sent ## Raw mode (raw.) Saved with POST /api/shares where mode="raw" and state is a RawConfig (see /api/schema.json). Replay over the raw subdomain — e.g. https://raw.xss.cool/?raw= — which is a TCP/TLS passthrough route that does not parse the HTTP response. The bytes you save are the bytes the client sees: custom status lines, lying Content-Length, mismatched CRLF, slow chunked drip, etc. Fields: statusLine, headers (ordered, non-normalized), crlfStyle (crlf|lf|mixed|none), headerBodySeparator (crlfcrlf|lflf|crlf|none), contentLengthMode (auto|omit|exact|offset), contentLengthValue, body, bodyEncoding (utf8|base64), chunkDelayMs. Soft per-IP rate limit (~60 req/min, configurable via XSSCOOL_RAW_RATE). ## Cross-origin testing recipe The platform serves multiple hosts (xss.cool, marquee.lol, …) from one backend, and every saved share / capture is reachable on all of them. To build a cross-origin PoC without standing up your own attacker server: 1. Save the attacker fixture (e.g. an iframe / fetch / postMessage page) once. Reference it on host A — e.g. https://xss.cool/s/. 2. Reference the victim URL on host B — e.g. https://marquee.lol/s/ or https://marquee.lol/?html=... 3. The two URLs are now genuinely cross-origin in the browser even though they hit the same backend. CORS, COOP/COEP, document.domain, postMessage, SameSite cookies, and CSP frame-ancestors all behave as they would across distinct attacker/victim domains. GET /api/config returns the active list of public domains. ## POST /api/shares Body shape (discriminated union on `mode`): - { mode: "sane", state: , name?: string } - { mode: "raw", state: , name?: string } `state` is the same shape the editor stores — see `editorState` / `rawConfig` in /api/schema.json. The /s/:id replay re-derives the URL-grammar query at request time, so there is no separate "snippet payload" — a share IS the editor state. `name` is optional, ≤80 chars, shown in /api/shares/mine. Response: { id, name, mode, replayUrl: "/s/:id", editorUrl?: "/e/:id", curlExample, jsExample }. `editorUrl` is included for sane shares only (raw shares have no editor view; /e/:id returns 400 for raw). ## Notes - Owner cookie (xsscool_owner) lets you list and delete your captures and shares. - Captures and capture_requests expire after 7 days; shares never expire. - The editor lets you pick which domain generated URLs target (Domain switcher in the topbar). The selection is cosmetic — the underlying share/capture is reachable on every host.