Skip to content

Security: rate-limit the CSP report sink per IP - #79

Merged
Deicyde merged 3 commits into
mainfrom
claude/csp-report-rate-limit
Oct 19, 2026
Merged

Deicyde merged 3 commits into
mainfrom
claude/csp-report-rate-limit

Conversation

@Deicyde

@Deicyde Deicyde commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Problem

  • The CSP report sink has no rate limit. POST /api/csp-report (wiki: security headers on Worker responses + report-only CSP #47) is unauthenticated by necessity, since browsers send the reports. It caps the body size and the violations per log line, but not the request rate, so anyone can flood the Worker's logs. Every other unauthenticated write has a limiter: /api/flag uses FLAG_LIMITER.
  • The header comment in wiki/src/security.ts gives a misleading next step. It says to "tighten it, then enforce it, once /api/csp-report stays quiet". The report-only policy allows 'unsafe-inline' scripts, so enforcing it as written would add little XSS protection.

Change

How tested

  • New test in wiki/test/security-headers.test.ts. It captures the limiter key from a request with CF-Connecting-IP: 203.0.113.7 and asserts csp:203.0.113.7. It sends malformed JSON while the limiter refuses, and expects 429 (not the 400 malformed JSON gets), the security headers, and no log line. It fails if the key prefix changes or the check moves after parsing. The existing sink tests use the fixture's always-allow limiter and are unchanged.
  • test/security-headers.test.ts passes locally (13 tests); CI on this PR runs the full Worker suite.

Risks

  • A page with many violations from one IP loses the reports past 5 a minute. The sink exists to show that violations happen, not to count each one.

Release

  • Release-affecting (per site/ops/brain_release_authority.py): wiki/src/security.ts, wiki/src/env.ts and wiki/wrangler.jsonc. It ships only through a Brain release and promotion. Release B is being built at the current main (Attest the non-Brain public assets for Brain release B #82), so merge this after B is promoted; it rides the next release.

What Jack must do

  • Nothing beyond the release.

…e-quiet note

POST /api/csp-report is unauthenticated by necessity and had no limiter, so anyone could flood the Worker's logs. It now takes 5 reports a minute per IP from FLAG_LIMITER's budget, under its own csp:<ip> keys, checked before the body is read; past that it answers 429 and logs nothing. The header comment no longer says to enforce the report-only CSP once the sink stays quiet: the policy allows inline scripts, so enforcing it as written adds little XSS protection.
@Deicyde

Deicyde commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner Author

Recommendation: merge, timed against release B (at 6b09390c, CI green; the review's fixes are in, rebased onto #84):

Release-affecting. Release B now has to be built at eaa5950b or later, because #83 and #84 are release-affecting too. Merging this before that build includes it in B; otherwise merge it after B is promoted. Never merge it between the build and the promotion.

Posted by the WikiLean PR swarm (lead); updated 2026-10-18.

# Conflicts:
#	wiki/src/security.ts
… shared limiter; test the key and order

Re-applies the per-IP limit on top of #84's sink: FLAG_LIMITER under csp:<ip> keys, checked before readBodyCapped, 429 with no log line past 5 requests a minute. The header comment no longer says to enforce the report-only CSP once the sink stays quiet, since the policy allows inline scripts. FLAG_LIMITER's comments in env.ts and wrangler.jsonc say it also caps the sink, so retuning one retunes both. The test captures the limiter key with a CF-Connecting-IP header and sends malformed JSON while the limiter refuses: it fails if the prefix changes or the check moves after parsing.
@Deicyde
Deicyde merged commit ba16808 into main Oct 19, 2026
4 checks passed
@Deicyde
Deicyde deleted the claude/csp-report-rate-limit branch October 19, 2026 01:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant