Skip to content

Headers

The toHaveSecureHeaders() matcher checks 12 security headers against OWASP recommendations. Pass a Playwright Response object.

import { test, expect } from "@orlalabs/kovar";
test("API has secure headers", async ({ page }) => {
const response = await page.goto("/dashboard");
await expect(response!).toHaveSecureHeaders();
});
HeaderSeverity if Missing
Strict-Transport-Securitycritical
Content-Security-Policycritical
X-Content-Type-Optionshigh
X-Frame-Optionshigh
Referrer-Policymedium
Permissions-Policymedium
Cross-Origin-Opener-Policylow
Cross-Origin-Resource-Policylow
Cross-Origin-Embedder-Policylow
X-XSS-Protection (non-zero)info
X-Powered-By (present)low
Server (version exposed)info

Content-Security-Policy validation goes beyond checking for the header’s presence. It flags:

  • Wildcard sources (default-src *, script-src *)
  • Dangerous directives ('unsafe-eval', 'unsafe-hashes')
  • 'unsafe-inline' without a 'nonce-' fallback
await expect(response).toHaveSecureHeaders({
skip: ["permissions-policy"], // skip specific headers
only: ["strict-transport-security"], // check only these
requiredCSPDirectives: ["script-src"], // require specific CSP directives
minHSTSMaxAge: 604800, // custom HSTS max-age threshold (default: 31536000)
});
OptionTypeDescription
skipstring[]Header names to skip
onlystring[]Check only these headers
requiredCSPDirectivesstring[]CSP directives that must be present
minHSTSMaxAgenumberMinimum HSTS max-age in seconds (default: 31536000)
allowXFrameOptionsSameOriginbooleanAllow SAMEORIGIN for X-Frame-Options (default: false)

Security matchers also work with API responses via the request fixture:

test("API endpoint has secure headers", async ({ request }) => {
const response = await request.get("/api/users");
await expect(response).toHaveSecureHeaders({
skip: ["x-frame-options"], // API endpoints don't need framing protection
});
});

For programmatic control, use the security fixture:

test("check headers programmatically", async ({ page, security }) => {
await page.goto("/dashboard");
// Throws on critical/high findings:
await security.headers.assert();
// Or inspect findings manually:
const findings = await security.headers.check();
const critical = findings.filter((f) => f.severity === "critical");
expect(critical).toHaveLength(0);
});