Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rename-response-headers-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cronn/playwright-utils": patch
---

Rename `responseHeaderReport` to `responseHeadersReport`
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
4 changes: 2 additions & 2 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ export default defineConfig({
items: [
{ text: "CSP Report", link: "/security/csp-report" },
{
text: "Response Header Report",
link: "/security/response-header-report",
text: "Response Headers Report",
link: "/security/response-headers-report",
},
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Response Header Report
# Response Headers Report

Security headers protect against common attacks like clickjacking, MIME sniffing or protocol downgrades, but a missing or weakened header usually goes unnoticed because the application keeps working. `responseHeaderReport` evaluates the headers of a response against a set of recommended security headers and returns a Markdown report, which can be stored as a file snapshot to detect any change to the headers.
Security headers protect against common attacks like clickjacking, MIME sniffing or protocol downgrades, but a missing or weakened header usually goes unnoticed because the application keeps working. `responseHeadersReport` evaluates the headers of a response against a set of recommended security headers and returns a Markdown report, which can be stored as a file snapshot to detect any change to the headers.

## Usage

```ts
import {
getResponseHeaders,
responseHeaderReport,
responseHeadersReport,
} from "@cronn/playwright-utils";
import { expect, test } from "@playwright/test";

test("has secure response headers", async ({ page }) => {
const response = await page.goto("/");
const headers = await getResponseHeaders(response);

expect(responseHeaderReport(headers, { type: "document" })).toMatchTextFile({
expect(responseHeadersReport(headers, { type: "document" })).toMatchTextFile({
fileExtension: "md",
});
});
Expand All @@ -24,7 +24,7 @@ test("has secure API response headers", async ({ request }) => {
const response = await request.get("/api/users");
const headers = await getResponseHeaders(response);

expect(responseHeaderReport(headers, { type: "api" })).toMatchTextFile({
expect(responseHeadersReport(headers, { type: "api" })).toMatchTextFile({
fileExtension: "md",
});
});
Expand All @@ -35,7 +35,7 @@ test("has secure API response headers", async ({ request }) => {
The report lists all evaluated headers of the response, followed by the recommendations:

```md
# Response Header Report
# Response Headers Report

## Detected Headers

Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export {
export { cspReport, getCspHeader } from "./security/csp-report";
export {
getResponseHeaders,
responseHeaderReport,
type ResponseHeaderReportOptions,
responseHeadersReport,
type ResponseHeadersReportOptions,
type ResponseHeaderType,
} from "./security/response-header-report";
} from "./security/response-headers-report";

export { resolveFromPackageRoot } from "./file";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { maskCspNonces } from "./csp-report";

export type ResponseHeaderType = "document" | "api";

export interface ResponseHeaderReportOptions {
export interface ResponseHeadersReportOptions {
type: ResponseHeaderType;
}

Expand Down Expand Up @@ -132,14 +132,14 @@ export async function getResponseHeaders(
* const response = await page.goto("/");
* const headers = await getResponseHeaders(response);
*
* expect(responseHeaderReport(headers, { type: "document" })).toMatchTextFile({
* expect(responseHeadersReport(headers, { type: "document" })).toMatchTextFile({
* fileExtension: "md",
* });
* ```
*/
export function responseHeaderReport(
export function responseHeadersReport(
headers: Record<string, string>,
options: ResponseHeaderReportOptions,
options: ResponseHeadersReportOptions,
): string {
const recommendedHeaders = RECOMMENDED_HEADERS[options.type];
const discouragedHeaders = DISCOURAGED_HEADERS[options.type];
Expand Down Expand Up @@ -168,7 +168,7 @@ export function responseHeaderReport(
(a, b) => ACTION_ORDER.indexOf(a.action) - ACTION_ORDER.indexOf(b.action),
);

return markdownDocument("Response Header Report", [
return markdownDocument("Response Headers Report", [
{
heading: "Detected Headers",
content: markdownTableOrNone(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import type { AddressInfo } from "node:net";

import {
getResponseHeaders,
responseHeaderReport,
responseHeadersReport,
type ResponseHeaderType,
} from "../src/security/response-header-report";
} from "../src/security/response-headers-report";
import { expect } from "../src/test/fixtures";

const SERVER_URL = "https://response-header-report.test";
const SERVER_URL = "https://response-headers-report.test";

const COMPLIANT_API_HEADERS = {
"cache-control": "no-store",
Expand All @@ -33,15 +33,15 @@ const COMPLIANT_DOCUMENT_HEADERS = {

test.describe("document", () => {
test("recommends nothing for compliant headers", async () => {
await testResponseHeaderReport(COMPLIANT_DOCUMENT_HEADERS, "document");
await testResponseHeadersReport(COMPLIANT_DOCUMENT_HEADERS, "document");
});

test("recommends all headers for empty headers", async () => {
await testResponseHeaderReport({}, "document");
await testResponseHeadersReport({}, "document");
});

test("accepts stricter values", async () => {
await testResponseHeaderReport(
await testResponseHeadersReport(
{
...COMPLIANT_DOCUMENT_HEADERS,
"strict-transport-security":
Expand All @@ -54,7 +54,7 @@ test.describe("document", () => {
});

test("recommends changing deviating values", async () => {
await testResponseHeaderReport(
await testResponseHeadersReport(
{
...COMPLIANT_DOCUMENT_HEADERS,
"strict-transport-security": "max-age=3600",
Expand All @@ -68,7 +68,7 @@ test.describe("document", () => {
});

test("ignores unrelated headers and masks CSP nonces", async () => {
await testResponseHeaderReport(
await testResponseHeadersReport(
{
...COMPLIANT_DOCUMENT_HEADERS,
"content-security-policy":
Expand All @@ -81,7 +81,7 @@ test.describe("document", () => {
});

test("recommends removing discouraged headers", async () => {
await testResponseHeaderReport(
await testResponseHeadersReport(
{
...COMPLIANT_DOCUMENT_HEADERS,
"Access-Control-Allow-Origin": "*",
Expand All @@ -94,15 +94,15 @@ test.describe("document", () => {

test.describe("api", () => {
test("recommends nothing for compliant headers", async () => {
await testResponseHeaderReport(COMPLIANT_API_HEADERS, "api");
await testResponseHeadersReport(COMPLIANT_API_HEADERS, "api");
});

test("recommends all headers for empty headers", async () => {
await testResponseHeaderReport({}, "api");
await testResponseHeadersReport({}, "api");
});

test("recommends changing deviating values", async () => {
await testResponseHeaderReport(
await testResponseHeadersReport(
{
...COMPLIANT_API_HEADERS,
"cross-origin-opener-policy": "unsafe-none",
Expand All @@ -114,7 +114,7 @@ test.describe("api", () => {
});

test("recommends removing discouraged headers", async () => {
await testResponseHeaderReport(
await testResponseHeadersReport(
{
...COMPLIANT_API_HEADERS,
"Access-Control-Allow-Origin": "*",
Expand All @@ -126,7 +126,7 @@ test.describe("api", () => {
});

test("compares header values ignoring case and whitespace", async () => {
await testResponseHeaderReport(
await testResponseHeadersReport(
{
...COMPLIANT_API_HEADERS,
"cross-origin-opener-policy": " Same-Origin ",
Expand Down Expand Up @@ -179,11 +179,11 @@ test("falls back to empty headers if the response is missing", async () => {
expect(await getResponseHeaders(null)).toEqual({});
});

async function testResponseHeaderReport(
async function testResponseHeadersReport(
headers: Record<string, string>,
type: ResponseHeaderType,
): Promise<void> {
await expect
.soft(responseHeaderReport(headers, { type }))
.soft(responseHeadersReport(headers, { type }))
.toMatchTextFile({ fileExtension: "md" });
}
Loading