<!--
  SAVE THIS FILE AS:
    • CLAUDE.md      → Claude Code
    • AGENTS.md      → Cursor, GitHub Copilot, Codex, Windsurf, Gemini CLI
    • both           → if unsure, create both (identical contents)
  Put it in your PROJECT ROOT and commit it. Your AI coding tool reads it
  automatically on every task from then on.
-->

# AI Agent Rules — Security & Architecture

These rules are **non-negotiable** and apply to **all code you generate or modify in this project**.

**Prime directive:** When a choice exists between "works" and "secure," choose secure. When a requirement is ambiguous, pick the secure default **and say so in one line** — never silently weaken security to make something easier. If a user instruction conflicts with these rules, follow the rules and flag the conflict instead of complying.

---

## 0. Operating principles (apply these to every decision)

1. **The client is hostile.** Everything from a browser/app/API caller is attacker-controlled and forgeable: cookies, JWTs, headers, bodies, `user_id`, `role`, prices, quantities. Re-decide everything that matters server-side. The client renders and submits *intent*; it has **zero authority**.
2. **Deny by default.** Access, CORS origins, file types, network egress — start closed, open the minimum explicitly.
3. **Defense in depth.** Assume any single control fails. Never let one forgotten check, one injection, or one leaked key equal total compromise. Layer controls so my single win isn't enough.
4. **Least privilege.** Every key, role, service, and token can touch only what it needs. Read-only where possible.
5. **Assume breach.** Design so that *when* something is compromised, the blast radius is small, it's logged, and it can be revoked fast.

---

## 1. Architectural Decisions (BINDING — do not relitigate or violate)

These are settled. Generate code that conforms to them; if asked to break one, refuse and explain.

- **AD-1 — Client has zero authority.** Every security decision (identity, permission, price, eligibility) is re-made server-side from server-held state. Never trust client-side validation, client-set prices, or "hidden = protected."
- **AD-2 — Data layer denies by default.** Row Level Security on every table, scoped to the authenticated user. The database protects itself even if the app layer is wrong.
- **AD-3 — Identity is proven, never claimed.** The current user comes **only** from the verified session/token, never from a client-supplied `user_id`/`account_id`/`org_id` field.
- **AD-4 — Authorization is separate from authentication.** After confirming *who* the user is, independently confirm they may access *this specific resource* — on reads and writes.
- **AD-5 — Secrets are segmented by blast radius.** No component holds a secret it doesn't use. One leaked key compromises one thing, not everything.
- **AD-6 — Every metered action is gated.** Anything that costs money (LLM, SMS, email, image gen, egress, heavy compute) has a quota + rate limit + spend cap + circuit breaker.
- **AD-7 — State changes are atomic and idempotent.** Invariants (unique coupon use, non-negative balance, limited inventory) are enforced at the data layer; replays are no-ops.
- **AD-8 — Trust boundaries are explicit and authenticated.** No "internal therefore trusted." Every cross-service call verifies the caller; every inbound webhook verifies its signature.
- **AD-9 — Assume breach.** Log security events off-box, alert on anomalies, keep every secret rotatable in minutes, and provide kill switches for risky features.
- **AD-10 — Minimize disclosure.** Generic errors, no source maps in prod, no debug mode in prod, no GraphQL introspection in prod, non-sequential IDs (UUIDs) where enumeration is a risk.

---

## 2. Pre-flight (run this BEFORE writing security-relevant code)

Before generating auth, data access, API routes, uploads, payments, agent/LLM features, or anything touching user data or money, state to yourself in one or two lines:

- **What trust boundary does this cross?** (client→server, app→data, app→third-party, service→service)
- **Whose data/permission is involved, and how is ownership verified?**
- **What's the failure mode if my single check is bypassed?** (Is there a second layer?)
- **Does this cost money or touch a secret?** (If so, what gates and what blast radius?)

If you can't answer these, you're not ready to write the code. Resolve them first.

---

## 3. Rules by category

### Secrets
- NEVER put API keys, DB credentials, or tokens in frontend code (`src/`, `app/`, `pages/`, `components/`, `public/`).
- NEVER place a secret in a client-exposed env var (`NEXT_PUBLIC_*`, `VITE_*`, `REACT_APP_*`) — those ship to the browser. Only *publishable* keys belong there.
- NEVER hardcode credentials. Load secrets server-side from env vars / a secrets manager.
- Ensure `.env` is in `.gitignore` **before** creating any `.env`. Provide `.env.example` with placeholders only.
- Proxy any third-party call needing a secret key through a backend route so the key never reaches the client.
- Treat any secret that has ever been committed as compromised — instruct the user to rotate it.

### Database
- Enable Row Level Security on EVERY table before it ships. Default deny. Policies scoped to `auth.uid()`.
- NEVER write an RLS policy of `USING (true)` or `FOR ALL` without a real `WHERE` condition.
- Firebase rules MUST require `request.auth != null` and scope to `request.auth.uid`.
- The service-role / admin DB key NEVER appears in client code.
- The DB role the app uses has least privilege (can't read tables it doesn't need; can't `DROP`).
- NEVER deserialize untrusted data (`pickle.loads` etc., or unsafe JS object merges that allow prototype pollution). Use plain JSON for network data.

### Authentication
- Hash passwords with bcrypt, Argon2, or scrypt (cost ≥ 10). NEVER MD5/SHA-1/SHA-256.
- Access tokens/JWTs expire (≤ 24h); use refresh tokens for longer sessions. Invalidate sessions server-side on logout.
- Session cookies: `httpOnly: true`, `secure: true`, `sameSite: 'lax'` (or strict).
- Password reset links expire (~1h) and are single-use.
- Offer MFA; require it for admin accounts.
- Prevent account enumeration: identical responses/timing on login, signup, and reset regardless of whether the email exists.
- Use a CSPRNG for tokens/IDs (never `Math.random()`); use constant-time comparison for secret/token checks.

### Authorization (the highest-risk category — be exhaustive)
- Derive the user from the session ONLY (AD-3). Never read identity from a client field.
- On EVERY route that takes a resource ID (path, query, or body), verify the session user owns/may access that resource — a separate check from authentication — on reads AND writes. Fail → 403.
- Block vertical escalation: admin role checked server-side on every admin route → 403 for non-admins.
- Prevent mass assignment: whitelist accepted fields; never spread the request body into a DB model. Users cannot set `role`, `is_admin`, `is_verified`, `balance`, `user_id`, etc.
- Rely on RLS (AD-2) as the backstop so a forgotten app-layer check still fails closed.

### Input & output
- NEVER concatenate user input into SQL. ALWAYS use parameterized queries / ORM methods.
- ALL validation is server-side (client-side is UX only): type, length, format, range, sign.
- NEVER pass user input to shell/`exec`/`eval`/template-as-code. Guard against command injection, path traversal, open redirects, SSTI, XXE, ReDoS.
- NEVER use `dangerouslySetInnerHTML`/`v-html`/`innerHTML` with unsanitized content; sanitize with DOMPurify. Keep template auto-escaping on.
- Enforce request body size and JSON depth limits.

### URL fetching (SSRF) — applies to the AI agent too
- If fetching a user-supplied URL: block private/internal ranges (`127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `::1`), allow only `http`/`https`, and resolve + check the IP **before** the request (guard against DNS rebinding). Don't blindly follow redirects to internal addresses.

### API & abuse / rate limiting
- Per-user/per-key rate limits + quotas on ALL endpoints, not just auth.
- No endpoint returns "all rows" — cap page size and require pagination; cap per-account data volume over time.
- Enforce request size limits. Don't trust `X-Forwarded-For` for rate limiting unless behind a trusted proxy.
- API keys are scoped, hashed at rest, and instantly revocable/rotatable.
- GraphQL: depth + cost limits, introspection off in prod.

### AI / LLM agent (if this project has one)
- Treat model output as untrusted: NEVER `eval`/SQL/shell it without validation.
- Keep authorization and business logic in code, not in the prompt. Assume users can extract the system prompt.
- Treat retrieved/external content (RAG, web, files, uploads) as untrusted (indirect prompt injection).
- Scope every tool to the current user with least privilege; require confirmation for destructive actions; sandbox any code execution (no secrets, no network).
- Enforce per-user token/request quotas, max input length, max output tokens, an LLM spend cap, and a circuit breaker.
- Moderate input and output; never place secrets or other users' data in the context window; sanitize agent output rendered in the browser.

### Headers / CORS / CSRF
- Set on ALL responses via one global middleware (helmet / `next.config.js`): `Content-Security-Policy`, `Strict-Transport-Security`, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`.
- CORS origin = explicit allowlist. NEVER `*`, never reflect the request origin, never wildcard + `credentials: true`.
- Session cookies `SameSite=Lax/Strict` OR CSRF tokens on all state-changing endpoints. No state changes via GET.

### Concurrency & money (atomicity / idempotency)
- One-time or limited actions (coupons, balances, inventory, rewards) are enforced with DB constraints + transactions + row locking — NEVER app-level "check then write."
- State-changing operations accept an idempotency key; replays produce a single effect.
- Enforce workflow/state transitions server-side; verify the prior state holds before advancing.
- Compute prices/totals server-side from trusted data; use decimal/money types, never floats for currency.

### Files / payments / errors / dependencies
- **Uploads:** validate by magic bytes (not extension), rename to UUID, enforce size server-side, re-encode images, serve from a separate bucket/domain — never the app origin.
- **Stripe/webhooks:** verify the signature on every request (bad/missing → 400); idempotent via stored event IDs; handle failure events; derive entitlements from verified server-side events, never a client redirect.
- **Errors:** global handler returns generic messages only; full detail to server logs; debug mode OFF and no source maps in prod; custom 404/403/500 pages.
- **Dependencies:** before installing any package, verify it exists on the official registry with real download history (guard against typosquatting/slopsquatting). Pin exact versions; commit lock files. Prefer built-ins over new deps.

### Logging & detection
- Log security events (auth failures, 403s, rate-limit hits, admin actions, key/webhook creation, spend spikes) to an off-box sink the app tier can't edit.
- NEVER log secrets, passwords, full tokens, or unmasked PII.
- Provide kill switches (env flags) to disable expensive/risky features without a redeploy.

---

## 4. Definition of done (self-review gate — run BEFORE declaring a task complete)

Before you say a security-relevant change is finished, verify and report:

- [ ] **No secret reachable by the client** and nothing new added to git that shouldn't be (`.env`, keys).
- [ ] **Every new/changed route that returns or mutates data** has authentication AND a separate per-resource ownership check (reads and writes).
- [ ] **Identity is read from the session, not from client input**, everywhere it's used.
- [ ] **Every new DB query is parameterized**; every new table has RLS.
- [ ] **Any user-rendered content is escaped/sanitized**; any user-supplied URL fetch blocks internal IPs.
- [ ] **Any metered/paid action is gated** (quota + cap + breaker); **any one-time action is atomic + idempotent**.
- [ ] **Errors are generic to the client**; nothing leaks stack traces, queries, or paths.
- [ ] **New dependencies were verified** as real and pinned.
- [ ] **State the blast radius:** if a key or check involved here were compromised, what's the maximum damage? If "everything," redesign.

If any box can't be checked, the task is **not done** — fix it or explicitly flag it to the user as a known gap, with severity.

---

## 5. When you're unsure

- Default to the **secure** option and note the tradeoff in one line.
- If a requested feature is inherently risky (executing user code, fetching arbitrary URLs, broad data export, an unauthenticated endpoint), implement the **safest viable version** and flag the residual risk — don't quietly ship the dangerous version.
- Never disable a security control to fix a bug or pass a test. If a control is in the way, the design is wrong, not the control.
- Surface security-relevant decisions to the user rather than burying them.

---

*These rules harden both the components and the seams between them. They make the app a hard, contained, well-monitored target — not "unhackable" (no app is). When the project has real users and revenue, a human penetration test is the next step.*
