Skip to content

Security Architecture ​

Two stale details, corrected 2026-08-08 — the reasoning below still holds

The layered-enforcement diagram names a React SPA tier-code-splitting layer (that is legacy/; the surfaces are now apps/mobile and apps/web per ADR-0011), and the data-access row says from() is banned in src/ (it is banned in app code; the seam is packages/data).

One addition that is not cosmetic. This page pre-dates category-3 consumers. authenticated no longer means "merchants" — it means the logged-in general public, and it will be the largest population on the platform by orders of magnitude. So no policy may grant access by role alone: using (true) TO authenticated is banned, every merchant-data policy scopes by relationship, and 29 negative pgTAP assertions hold the line. See database & RLS.

Security is by design, validated at every boundary. Defense-in-depth: no single layer is trusted alone.

Layered enforcement ​

LayerResponsibility
CloudflareCSP + security headers, rate limiting on public/auth endpoints, Bot Fight Mode, TLS.
Tier splitAdmin/user code lazy-loaded — a logged-out visitor never downloads privileged code.
ZodValidate + narrow all inputs at the boundary (forms, EF request bodies).
Edge FunctionrequireAuth/requireAdmin/optionalAuth; re-validate server-side; primary write-enforcement. Safe errors only.
RLSPer-row policy on every user-facing table; independent of the EF.
Data-access rulefrom() banned in src/ → table names never on the wire.

Principles [ENFORCED] ​

  • Validate at boundaries (Zod) — never trust client input, even from your own SPA.
  • Least privilege — RPCs REVOKE ALL FROM PUBLIC then GRANT EXECUTE; EFs use the service client only inside the function.
  • No secret/table leakage — errors returned to clients are safe (err() helper); details go to structured logs, not the response.
  • RLS on every table; append-only tables carry no UPDATE/DELETE policy.
  • Rate limiting on public/auth endpoints.
  • CSP / security headers at the edge.

Edge Function auth flow ​

Typed errors from _shared/errors.ts: ValidationError (400), AuthError (401), ForbiddenError (403), NotFoundError (404), ConflictError (409). err() maps anything else to a safe 500 with a structured log — never a stack trace to the client.

Webhooks (when added) ​

HMAC signature + timestamp window + constant-time compare + idempotency via a UNIQUE event id. No webhook receivers exist yet.

PII / GDPR ​

  • Account and data deletion paths (manage-account) — right-to-erasure.
  • profiles and analytics tables hold PII → covered by RLS + owner-scoped access.

Roadmap (from CLAUDE.md, target) ​

  • Dependency / secret / SAST / DAST scanning in CI (SonarQube CE + CodeQL + Dependabot + secret scanning).
  • Observability is the top deferred risk: structured EF JSON logs now → error tracking (Sentry-class) + metrics dashboards + alerting with an on-call path later.

See Backend · Data Access · Database & RLS.