63 lines · 2.6 KB
import { describe, expect, mock, test } from "bun:test";

mock.module("cloudflare:workers", () => ({ env: {}, waitUntil: () => undefined }));
const { ensureSecurityHeaders, hardenScratchResponse, SCRATCH_BANNER } = await import("./harden");
const { SECURITY_HEADERS } = await import("./static");

describe("hardenScratchResponse", () => {
  test("forces scratch headers over whatever a user worker set", async () => {
    const response = hardenScratchResponse(
      new Response("{}", {
        headers: {
          "content-type": "application/json",
          "content-security-policy": "form-action *",
          "x-robots-tag": "all",
        },
      }),
    );
    const csp = response.headers.get("content-security-policy") ?? "";
    expect(csp).toContain("form-action 'self'");
    expect(csp).not.toContain("https:");
    expect(response.headers.get("x-robots-tag")).toContain("noindex");
    expect(response.headers.get("x-content-type-options")).toBe("nosniff");
    expect(await response.text()).toBe("{}");
  });

  test("injects the banner into HTML bodies and drops the stale length", async () => {
    const body = "<html><body><form action='https://evil.test'></form></body></html>";
    const response = hardenScratchResponse(
      new Response(body, { status: 404, headers: { "content-type": "text/html", "content-length": "65" } }),
    );
    expect(response.status).toBe(404);
    expect(response.headers.get("content-length")).toBeNull();
    const text = await response.text();
    expect(text).toContain(SCRATCH_BANNER);
    expect(text.indexOf(SCRATCH_BANNER)).toBeLessThan(text.indexOf("</body>"));
  });

  test("appends the banner to HTML fragments without a body element", async () => {
    const text = await hardenScratchResponse(
      new Response("<h1>hi</h1>", { headers: { "content-type": "text/html; charset=utf-8" } }),