Skip to content

XSS

The toBeResilientToXSS() matcher tests form inputs against XSS payloads. By default it uses API-first injection — submitting payloads via HTTP and analyzing response bodies for unescaped reflection, without causing DOM side effects. It falls back to DOM testing with alert() dialog detection when needed.

import { test, expect } from "@orlalabs/kovar";
test("search form resists XSS", async ({ page }) => {
await page.goto("/search");
await expect(page).toBeResilientToXSS({
selector: "#search-form",
depth: "quick",
});
});

40 polyglot payloads across 3 depth levels:

DepthPayloadsWhat it tests
quick10Common HTML injection: <img onerror>, <svg onload>, <script>, attribute breakouts, javascript: protocol, template literals
standard25 (cumulative)+ encoding evasion, mixed case, null bytes, newlines in handlers, comment breakouts, quote-style variations
thorough40 (cumulative)+ unicode escapes, HTML entity encoding, data URIs, base64, context breakouts (textarea, title, style, noscript), polyglot all-context
await expect(page).toBeResilientToXSS({
selector: "#login-form", // target a specific form (optional, auto-discovers all forms)
depth: "standard", // "quick" | "standard" | "thorough" (default: "quick")
timeout: 5000, // per-payload timeout in ms
skipPayloads: ["poly-009"], // skip specific payload IDs
apiFirst: false, // set false to use DOM testing instead of API-first
});
OptionTypeDescription
selectorstringCSS selector for a specific form (auto-discovers all forms if omitted)
depth"quick" | "standard" | "thorough"Payload depth level (default: "quick")
timeoutnumberPer-payload timeout in milliseconds
skipPayloadsstring[]Payload IDs to skip
apiFirstbooleanUse API-first injection (default: true)
delayBetweenPayloadsnumberDelay in ms between payload submissions (default: 0)
concurrencynumberMax parallel payload tests (default: 1, serial)

API-first (default) submits payloads directly via HTTP requests to form action URLs and checks response bodies for unescaped reflection. This is faster and doesn’t cause visual side effects in the browser.

DOM testing (set apiFirst: false) fills form inputs in the browser, submits the form, and listens for alert() dialogs. This catches payloads that execute in the browser context but is slower and may miss non-alerting XSS.

XSS testing is opt-in in the full audit because it’s slower than other checks:

test("XSS via audit", async ({ page, security }) => {
await page.goto("/search");
const report = await security.audit({
includeXSS: true,
xss: { selector: "#search-form", depth: "standard" },
});
expect(report.summary.critical).toBe(0);
});
  • Full Audit — include XSS testing in a comprehensive audit.
  • Standalone API — use XSSScanner outside Playwright test runner.
  • Limitations — what XSS patterns Kovar does and does not catch.