Skip to main content

Security — How it works

Overview

The security engine owns platform security telemetry that has nowhere else to live. Today that is one capability: CSP violation collection and triage (M33-012B), which turns the browser Content Security Policy from a policy that reports into nobody's console into evidence somebody can act on.

It is deliberately small. Authentication, authorization, break-glass and audit belong to access-control and audit; this engine exists for security signals the platform receives rather than enforces.

Data model & ownership

TablePurpose
csp_violation_groupOne row per distinct CSP problem, keyed on (effective_directive, blocked_uri_origin, document_uri_pattern), with first_seen_at, last_seen_at, a counter, and a triage state.

Migrations live under core/src/main/resources/db/changelog/security/.

Key rules & invariants

  • ⚠⚠ No concrete URL is ever stored. A CSP report carries document-uri, and this application puts patient ids in URLs. Every URL is reduced to a route pattern (/patients/:id/allergies) or an origin at ingest, before the row is written — reduce, never redact later.
  • The ingest endpoint is anonymous by necessity. Browsers send violation reports as credential-less beacons; authenticating it would not secure it, it would silently stop the reports. What stands in for RBAC is the per-IP rate limit plus the fact that nothing worth reaching is stored.
  • Aggregate, never one row per occurrence. Extensions, ISP script injection and malware generate large volumes of spurious violations about pages that are fine — the reason naive CSP collectors stop being read within days.
  • Triage states are NEWEXPECTED / MUST_FIX / IGNORED, and reading or triaging requires security.csp-violation.read / .write. Only ingest is anonymous.
  • The purge never touches NEW or MUST_FIX — those are the states M33-013's enforcement gate is counted from.

API

MethodPathAuth
POST/api/v1/security/csp-reportsanonymous, rate-limited; accepts application/csp-report and application/reports+json
GET/api/v1/security/csp-violationssecurity.csp-violation.read
PATCH/api/v1/security/csp-violations/{id}security.csp-violation.write

The ingest endpoint always answers 204 and never echoes input, so it cannot be used as a reflector and cannot report whether a payload was understood.

  • CSP violation collection — the operational detail: how the reducer works, both browser wire formats, retention, and what FE-368 still owes.
  • Infrastructure security requirements — the controls this repo verifies but does not implement. ⚠ Deliberately not a link: that page arrives with M33-015 (!531), and a link to a file on another branch fails the Docusaurus build here with broken links, which is how this doc first went red.