Skip to content

CSRF

The toBeCSRFProtected() matcher tests that endpoints reject state-changing requests without CSRF tokens. Pass a Playwright APIRequestContext and a URL.

import { test, expect } from "@orlalabs/kovar";
test("API is protected against CSRF", async ({ request }) => {
await expect(request).toBeCSRFProtected("/api/transfer");
});

The matcher sends state-changing HTTP methods (POST, PUT, DELETE, PATCH) without a CSRF token and checks whether the endpoint accepts them. It also verifies that:

  • CSRF tokens are present in response headers or meta tags.
  • Cookies use SameSite=Strict or SameSite=Lax.

CWE mapping: CWE-352 (Cross-Site Request Forgery)

await expect(request).toBeCSRFProtected("/api/transfer", {
endpoints: ["/api/transfer", "/api/settings"], // check multiple endpoints
methods: ["POST", "DELETE"], // specific methods (default: POST, PUT, DELETE, PATCH)
tokenHeaders: ["x-csrf-token", "x-xsrf-token"], // custom token header names
tokenCookies: ["csrf_token"], // token cookie names
skip: ["/api/health"], // skip URL patterns
});
OptionTypeDescription
endpointsstring[]Multiple endpoints to check
methodsstring[]HTTP methods to test (default: POST, PUT, DELETE, PATCH)
tokenHeadersstring[]Custom CSRF token header names
tokenCookiesstring[]Cookie names that carry CSRF tokens
skipstring[]URL patterns to skip
import { test, expect } from "@orlalabs/kovar";
test("API security audit", async ({ page, security }) => {
await page.goto("/dashboard");
// Throws on critical/high findings:
await security.csrf.assert();
// Or inspect findings:
const findings = await security.csrf.check({ methods: ["POST", "DELETE"] });
const critical = findings.filter((f) => f.severity === "critical");
expect(critical).toHaveLength(0);
});
  • CORS — CORS misconfigurations can weaken CSRF protections.
  • Authentication — authentication and CSRF protections complement each other.
  • Full Audit — include CSRF in a comprehensive audit with checks: ["csrf"].
  • Standalone API — use checkCSRF() outside Playwright.