# Full-Stack Application Security Hardening Playbook This high-level guide provides a reusable, standardized protocol for auditing and securing full-stack applications (Next.js, Node.js, Express, etc.). --- ## Phase 1: Endpoint & Access Control (Preventing IDOR) ### 1. The Multi-Tenant Ownership Rule Every state-changing API request (`POST`, `PUT`, `PATCH`, `DELETE`) and private resource read (`GET`) must establish identity via the server session—never by trusting client parameters. #### Never Trust Client User IDs: ```typescript // ❌ Vulnerable (attacker can pass any userId in body) const { title, userId } = await req.json(); await db.resource.create({ data: { title, userId } }); // ✅ Secure (always bind to verified session) const session = await getServerSession(authOptions); if (!session?.user?.id) return unauthorized(); await db.resource.create({ data: { title, userId: session.user.id } }); ``` #### Verify Resource Ownership Before Mutation: ```typescript // Verify target record exists AND belongs to the caller const item = await db.resource.findUnique({ where: { id } }); if (!item) return notFound(); if (item.userId !== session.user.id) return forbidden(); ``` #### Lock Down Sensitive Data Endpoints: * Strip internal data (emails, password hashes, roles, permissions) from public query endpoints. * Never allow query flags like `?includePrivate=true` unless the caller has verified ownership or administrative authorization for that account. --- ## Phase 2: Input Boundaries & Payload Sanitization ### 1. Enforce Hard Limits on Every Field Unbounded input strings can cause database exhaustion, buffer overruns, or CPU denial-of-service (e.g., Bcrypt hashing huge strings). | Field Type | Recommended Hard Bound | Rationale | | :--- | :--- | :--- | | **Passwords** | Min 8 chars, Max 72–100 chars | Bcrypt truncates or burns excessive CPU beyond 72 bytes | | **Emails** | Max 254 chars | Standard RFC 5321 length; prevents regex DoS | | **Titles / Names** | Max 100–200 chars | Prevents UI overflow and DB memory bloat | | **Descriptions / Content** | Max 2,000–50,000 chars | Prevents payload bombing | | **Pagination limit** | Clamped to `1 <= limit <= 50` | Prevents querying the entire table at once | ### 2. JSON & Batch Array Clamping When accepting array batches (e.g., bulk updating nodes or items), enforce an explicit upper limit (e.g., max 200–500 items per request). --- ## Phase 3: Safe File & Document Upload Architecture If an application accepts images, PDFs, or documents, implement the **Four-Pillar Upload Defense**: ```mermaid flowchart LR A["File Upload"] --> B["1. Size Check"] B --> C["2. Extension & MIME Allowlist"] C --> D["3. Magic Bytes Inspection"] D --> E["4. Filename Sanitization & Storage"] ``` ### 1. Size Caps: * Check file size before buffering: images (max 2–5MB), documents (max 10MB). * Terminate stream early if incoming chunks exceed the configured byte threshold. ### 2. Strict MIME & Extension Allowlists: * **Allowed:** `image/jpeg` (`.jpg`, `.jpeg`), `image/png` (`.png`), `image/webp` (`.webp`), `application/pdf` (`.pdf`), `application/json` (`.json`), `text/markdown` (`.md`). * **Strictly Blocked:** `.exe`, `.bat`, `.sh`, `.php`, `.js`, `.html`, `.svg` (SVGs can embed `