Summary
The recipe site does not need a large standalone security program before shipping authenticated features.
It does need a small, explicit baseline for the new risks introduced by login, sessions, household sharing, and user-specific writes.
Context
Several platform-level controls already exist elsewhere in the site architecture, including:
- repository and CI protections
- GitHub/secret-management decisions
- Cloudflare-managed edge controls
What is new here is application security:
- browser sessions
- OAuth callback handling
- CSRF and cookie safety
- rate limiting on auth and write paths
- server-side authorization enforcement
The chosen backend — Cloudflare Workers — provides several security properties by default that reduce the baseline we need to implement ourselves.
Decision
Before authenticated features go live, the application must provide this baseline:
- Server-owned sessions. Session cookies must be
HttpOnly,Secure, and appropriately scoped. - CSRF protection for browser mutations. If the app uses cookie-based auth, write endpoints must not rely on same-origin assumptions alone.
- OAuth integrity checks. Provider flows must use state validation and any provider-specific protections Better Auth expects.
- Server-side authorization on every privileged route. Authz belongs in the runtime, not only in client UI.
- Rate limiting on auth and write-heavy endpoints. Sign-in, invite, and mutation paths need basic abuse protection from day one.
- Input validation at the API boundary. Typed frontend code is not a substitute for runtime validation. Zod schemas at the Hono handler level are consistent with the existing codebase pattern.
- Least-privilege secrets and database access. Reuse the broader platform approach; do not give every runtime token broad administrative scope if narrower credentials are possible.
Workers Security Properties
The Cloudflare Workers runtime provides several security properties for free:
| Property | What it means |
|---|---|
| Managed isolate sandboxing | Worker code runs inside Cloudflare-managed V8 isolates rather than a user-managed server process. Global scope may be reused across requests, so request-scoped data must not be stored there. |
| Automatic HTTPS | All traffic is TLS-terminated at Cloudflare's edge by default. No certificate management. |
| No user-managed server process | There is no VM or container for us to patch, harden, or expose directly. Application state still belongs in explicit stores, not process memory. |
| DDoS protection | Cloudflare's edge network provides baseline DDoS mitigation for all Workers. No configuration needed. |
| No exposed ports | Workers are not addressable by IP. All traffic flows through Cloudflare's reverse proxy. There is no server to port-scan. |
These do not replace the application-level controls above, but they reduce the surface area that needs explicit security engineering.
Rate Limiting Implementation
Rate limiting uses a layered approach:
- Cloudflare rate limiting rules (configured via Terraform in
infra/cloudflare/main.tf) for broad endpoint protection — e.g. limiting requests per IP to auth endpoints. These run at the edge before the Worker is invoked. - Durable Objects or native Cloudflare rate limiting for application-specific limits — e.g. auth attempts per user account, household invite frequency. Durable Objects are included in Workers Paid and provide the strong consistency needed for high-frequency counters. Cloudflare's native rate limiting is preferable where an edge-level rule can express the policy.
- Do not use Cloudflare KV for write-heavy rate limiting. KV is optimized for high-read, low-write workloads, is eventually consistent across edge locations, and has a 1 write/second limit per key. Those properties make it a poor fit for brute-force counters.
- Better Auth's built-in rate limiter should be backed by Durable Objects, native Cloudflare rate limiting, or another strongly consistent store rather than KV.
Deferred
The following can stay deferred to keep the system lean:
- first-party MFA
- custom fraud/anomaly systems
- dedicated SIEM / security analytics
- public API key management
- elaborate WAF tuning beyond sensible Cloudflare defaults
This is intentionally a baseline ADR, not a promise to build security-heavy product features before they are justified.
Consequences
Positive
- The real minimum bar is explicit.
- Security scope stays proportional to a personal-project MVP.
- Workers' built-in isolation properties reduce the surface area that needs manual security work.
Negative
- "Lean" still means some engineering work: rate limiting, session config, and validation cannot be hand-waved away.
- Durable Objects or equivalent strongly consistent rate limiting add implementation work.
- If public signup or friend sharing expands quickly, this baseline may stop being sufficient.
When To Revisit
Revisit if any of the following become true:
- signup becomes broadly public
- the site adds public APIs, API keys, or machine-to-machine access
- moderation, abuse, or account-recovery burden becomes material
- a security review or penetration test reveals gaps in the baseline