# SECURITY.md — Security Rules for This Project

> **To the AI agent reading this:** These are **mandatory, non-negotiable rules**. Follow them on every change you make, even when the user doesn't mention security. When a requested feature conflicts with a rule here, implement the secure version and tell the user what you did and why. If you are unsure whether something is safe, **stop and ask** rather than guessing. Never disable a security control to "make it work" — fix the underlying cause instead.

This file exists because apps built fast (including AI-assisted ones) repeatedly ship the same handful of vulnerabilities. The goal is an app that is "immune" to the common classes of attack by **default**, not as an afterthought.

---

## 0. The Golden Rules (read these first)

1. **The frontend is public.** Anything in client-side code, network responses, or the browser is visible to and editable by an attacker. Never trust it. All real security must live on the server / database.
2. **Never trust input.** Validate, sanitize, and authorize *every* input on the server — query params, headers, body, file uploads, cookies, and webhook payloads.
3. **Authenticate, then authorize, on every request.** Logging in is not enough. Check *this specific user is allowed to do this specific action on this specific resource* — server-side, every time.
4. **Secrets stay on the server.** No keys, tokens, passwords, or connection strings in frontend code, repos, or logs. Ever.
5. **Fail closed.** When something is ambiguous or errors out, deny access. Default to the most restrictive option.
6. **Least privilege everywhere.** Every key, role, token, and DB account gets the minimum permissions it needs and nothing more.

---

## 1. Secrets & Credentials

**NEVER**
- Hardcode API keys, passwords, JWT secrets, DB URLs, or private tokens anywhere in source.
- Put secrets in frontend/client code or in any `NEXT_PUBLIC_`, `VITE_`, `REACT_APP_`, or otherwise client-exposed env var.
- Commit `.env`, `.env.local`, credential files, `*.pem`, or service-account JSON to git.
- Log secrets, tokens, or full request bodies that may contain them.
- Reuse the same secret across dev/staging/prod.

**ALWAYS**
- Load secrets from server-side environment variables only.
- Add `.env*`, `*.pem`, `*.key`, `secrets/`, and credential files to `.gitignore` *before* the first commit.
- Provide a `.env.example` with key **names only** and dummy values.
- Use a distinct, high-entropy (32+ random bytes) value for each signing/encryption secret.
- Assume any secret that ever touched the frontend or a public repo is **compromised** — rotate it.
- Use a secrets manager (or the platform's encrypted env store) in production, not files on disk.

---

## 2. Authentication

**NEVER**
- Roll your own crypto, password hashing, or session logic when a vetted library/provider exists.
- Store passwords in plaintext or with fast/weak hashes (MD5, SHA1, unsalted SHA256).
- Trust a `userId`, `role`, `isAdmin`, or `email` value that came from the client to decide who someone is.
- Put auth state only in `localStorage` and treat its presence as proof of identity.
- Leave default/seed credentials (`admin/admin`) in any environment.
- Return "user not found" vs "wrong password" differently (enables user enumeration).

**ALWAYS**
- Use a maintained auth library/provider (e.g. the platform's auth, Auth.js, Lucia, Clerk, Supabase Auth) and follow its server-side session verification.
- Hash passwords with bcrypt, scrypt, or Argon2 with a proper cost factor.
- Verify the session/token **on the server** for every protected request and derive identity from it — never from request body params.
- Enforce a password policy + check against known-breached passwords where possible.
- Implement account lockout / throttling on repeated failed logins.
- Offer/encourage MFA for sensitive accounts.
- Expire sessions, rotate on privilege change, and invalidate on logout server-side.

---

## 3. Authorization (the #1 vibe-coded flaw)

> Most rapidly-built apps authenticate but barely authorize. This is where IDOR, broken access control, and data leaks come from.

**NEVER**
- Hide a button/route on the frontend and call it "protected." UI hiding is not access control.
- Use sequential/guessable IDs as the *only* thing protecting a resource (`/api/orders/1043` → an attacker just tries `1044`). This is **IDOR**.
- Assume that because the UI only sends valid requests, the server only receives valid requests.
- Let one user's token read/modify another user's data because the query didn't filter by owner.

**ALWAYS**
- On every data access, check ownership/permission server-side: `WHERE owner_id = :current_user` (or equivalent), not just `WHERE id = :id`.
- Re-check role/permission for every privileged action on the server, on every call.
- Enforce authorization at the **lowest layer possible** (DB row-level security + API layer), so a missed check in one place doesn't expose everything.
- Use unguessable IDs (UUIDs/ULIDs) for resources, *and still* authorize them.
- Centralize authorization logic so it's consistent and auditable, not copy-pasted per route.

---

## 4. Database Security (Supabase / Firebase / direct DB)

> Vibe-coded apps frequently expose the DB straight to the browser with permissive rules. This is catastrophic.

**NEVER**
- Ship Supabase/Postgres tables with **Row Level Security (RLS) disabled** on anything holding user data.
- Use Firebase/Firestore rules like `allow read, write: if true;` or test-mode rules in production.
- Expose a service-role / admin DB key to the client (the anon/public key is the only client-side key, and even that needs RLS behind it).
- Build SQL by string concatenation or template interpolation with user input.
- Run app queries as a superuser/admin DB account.

**ALWAYS**
- Enable RLS on every table and write explicit policies scoped to the authenticated user.
- Write Firestore/RTDB rules that check `request.auth` and validate ownership and shape.
- Use parameterized queries / prepared statements / a query builder / ORM bindings — **always** (prevents SQL injection).
- Give the app a least-privilege DB role (no DROP/ALTER, only needed tables).
- Validate data shape and types at the DB boundary, not just the UI.
- Keep the service-role key strictly server-side for trusted admin operations.

---

## 5. Input Validation & Injection

**NEVER**
- Pass user input directly into SQL, shell commands, `eval`, template engines, `dangerouslySetInnerHTML`, file paths, or `Function()`.
- Rely on frontend validation as a security boundary (it's UX only).
- Build file paths from user input without normalizing (enables **path traversal**, `../../etc/passwd`).

**ALWAYS**
- Validate every input server-side against a strict schema (e.g. Zod, Pydantic, Joi) — type, length, format, allowed values.
- Allow-list rather than block-list whenever possible.
- Use parameterized queries for SQL and safe APIs for OS/command operations (avoid shelling out with user input at all).
- Reject unexpected fields to prevent **mass assignment** (don't blindly spread `req.body` into a DB record — pick allowed fields explicitly).
- Canonicalize and confine file paths to an intended directory.

---

## 6. Output / XSS / Content

**NEVER**
- Render untrusted content as raw HTML (`dangerouslySetInnerHTML`, `innerHTML`, `v-html`, `{{{ }}}`) without sanitizing.
- Reflect user input into responses unescaped.
- Inject user-controlled values into `<script>`, inline event handlers, or URLs (`javascript:`).

**ALWAYS**
- Rely on framework auto-escaping (React/Vue/Svelte escape by default — keep it that way).
- Sanitize any HTML you *must* render with a maintained sanitizer (e.g. DOMPurify) and a strict allow-list.
- Set a strong **Content-Security-Policy** to limit script sources.
- Encode output for the correct context (HTML, attribute, JS, URL).

---

## 7. APIs, CORS & Rate Limiting

**NEVER**
- Set `Access-Control-Allow-Origin: *` together with credentials, or wildcard CORS on authenticated APIs.
- Ship public endpoints with no rate limiting (enables brute force, scraping, credential stuffing, cost-blowup, and DoS).
- Leave debug/admin/internal endpoints reachable in production.
- Return different responses that leak whether a record exists when it shouldn't.

**ALWAYS**
- Restrict CORS to an explicit allow-list of known origins.
- Rate-limit by IP and by user/account on auth, write, and expensive endpoints; add exponential backoff/lockout for auth.
- Require auth on every non-public endpoint and verify it server-side.
- Validate `Content-Type` and reject unexpected methods.
- Protect state-changing requests against **CSRF** (SameSite cookies + CSRF tokens, or token-based auth not stored in cookies).
- Treat webhooks as untrusted: verify signatures before acting.

---

## 8. Sessions, Tokens & JWTs

**NEVER**
- Accept JWTs with `alg: none` or let the client choose the algorithm.
- Sign JWTs with a weak/guessable/shared secret.
- Issue tokens with no expiry, or store sensitive long-lived tokens in `localStorage` (XSS-readable).
- Trust JWT claims without verifying the signature server-side.

**ALWAYS**
- Pin the expected algorithm and verify the signature on every request.
- Use short-lived access tokens + rotating refresh tokens.
- Prefer `httpOnly`, `Secure`, `SameSite` cookies for session tokens.
- Include and validate `exp`, `iss`, `aud`; revoke on logout/compromise.

---

## 9. File Uploads

**NEVER**
- Trust the client-provided filename, extension, or MIME type.
- Store uploads in a web-served directory where they can be executed.
- Allow arbitrary file types or unbounded file sizes.

**ALWAYS**
- Validate type by content (magic bytes), enforce a size limit, and allow-list extensions.
- Generate a new random server-side filename; never use the user's path.
- Store outside the web root or in object storage with no-execute and least-privilege access.
- Scan uploads where feasible and serve them from a separate domain/sandbox with `Content-Disposition`.

---

## 10. HTTP Security Headers & Transport

**ALWAYS** set (or have the framework/host set) at minimum:
- `Strict-Transport-Security` (HSTS) — and enforce HTTPS everywhere; redirect HTTP→HTTPS.
- `Content-Security-Policy` — restrict scripts/styles/connect sources.
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY` (or CSP `frame-ancestors`) to prevent clickjacking.
- `Referrer-Policy: strict-origin-when-cross-origin`
- Sensible `Permissions-Policy`.

**NEVER** serve auth or sensitive data over plain HTTP, or mix HTTP content into an HTTPS app.

---

## 11. Error Handling & Logging

**NEVER**
- Return stack traces, SQL errors, file paths, or internal details to the client.
- Log passwords, tokens, full card numbers, or other secrets/PII.
- Run production with debug mode on.

**ALWAYS**
- Return generic error messages to users; log detail server-side with correlation IDs.
- Log security-relevant events (auth failures, access denials, privilege changes) for audit.
- Scrub/redact sensitive fields before logging.

---

## 12. Dependencies & Supply Chain

**NEVER**
- Add a dependency you don't need, or pull random unvetted packages for a one-liner.
- Ignore known-vulnerable dependency warnings.

**ALWAYS**
- Run `npm audit` / `pip-audit` / `osv-scanner` (or equivalent) and fix high/critical issues.
- Pin versions and use a lockfile; keep dependencies updated.
- Verify package names carefully (watch for typosquats).
- Enable automated dependency security updates (e.g. Dependabot) where possible.

---

## 13. Sensitive Data & Privacy

**NEVER**
- Collect or store more personal data than the feature needs.
- Store sensitive data (PII, payment, health) unencrypted at rest.
- Send sensitive data to third-party services/analytics without need and consent.

**ALWAYS**
- Encrypt sensitive data at rest and in transit.
- Apply least-privilege access to PII; mask/redact in UIs and logs.
- Offboard payment handling to a PCI-compliant provider (e.g. Stripe) rather than touching card data yourself.
- Provide data deletion/export paths where regulations require.

---

## 14. AI / LLM-Specific (if the app calls an LLM)

**NEVER**
- Concatenate untrusted user input directly into a system prompt and treat the model output as trusted (**prompt injection**).
- Give the model unrestricted tools/DB/shell access driven by user-controlled text.
- Expose your LLM provider API key to the client.

**ALWAYS**
- Treat model output as untrusted input — validate and authorize any action it triggers.
- Keep the LLM key server-side and put your own rate limits + spend caps around it.
- Separate trusted instructions from untrusted user content; constrain tool use with allow-lists and per-action authorization.
- Filter/limit what data the model can retrieve based on the requesting user's permissions.

---

## 15. Server-Side Request & Redirect Safety

**NEVER**
- Fetch a user-supplied URL from the server without restriction (**SSRF** — can hit internal metadata endpoints / private network).
- Redirect to a user-supplied URL without validation (**open redirect** — aids phishing).

**ALWAYS**
- Allow-list domains/schemes for any server-side fetch; block private/internal IP ranges and metadata IPs.
- Validate redirect targets against an allow-list or use relative paths only.

---

## Pre-Ship Security Checklist

Run through this before any deploy. Treat unchecked items as blockers.

- [ ] No secrets in code, repo history, logs, or client bundles; `.env*` gitignored; keys rotated if ever exposed.
- [ ] Auth verified server-side on every protected route; passwords hashed with bcrypt/argon2; no default creds.
- [ ] Every data read/write is authorized by ownership/role server-side (no IDOR); privileged actions re-checked.
- [ ] RLS / DB rules enabled and scoped per-user; service-role key server-side only; no `if true` rules.
- [ ] All queries parameterized; all input validated server-side against a schema; mass assignment prevented.
- [ ] Output escaped/sanitized; CSP set; no unsanitized raw-HTML rendering.
- [ ] CORS locked to known origins; rate limiting on auth/write/expensive endpoints; CSRF handled; webhooks signature-verified.
- [ ] JWTs verify signature + pinned alg + expiry; session cookies `httpOnly`/`Secure`/`SameSite`.
- [ ] File uploads validated by content, size-limited, randomly named, stored no-execute.
- [ ] Security headers + HTTPS enforced (HSTS, nosniff, frame-ancestors, referrer-policy).
- [ ] Generic error messages to users; debug off in prod; no secrets/PII in logs.
- [ ] Dependency audit clean (no high/critical); lockfile committed.
- [ ] Sensitive data minimized + encrypted; payments via compliant provider.
- [ ] LLM key server-side; model output treated as untrusted; SSRF/open-redirect protections in place.

---

## How to use this file with the AI agent

- Keep this file at the project root as `SECURITY.md` and reference it in your `CLAUDE.md` / agent instructions: *"Follow all rules in SECURITY.md on every change."*
- When asking for a feature, you can add: *"Implement this per SECURITY.md and flag any rule it touches."*
- Periodically ask: *"Audit the current codebase against SECURITY.md and list violations by severity."*
- If the agent ever proposes weakening a control for convenience, that's a red flag — ask for a secure alternative instead.
