- Add junior-to-senior with research playbook and review rubric - Add fuck-slop with tell catalog and multi-register voice profiles - Add interface-kit with design engineering, motion, and a11y references - Add app-security with 4-pillar upload defense and hardening playbooks - Add debugging-workflow with systematic root cause isolation - Standardize all skills to 5-phase Skill Architect protocol - Sync symlinks to OpenCode, Codex, and Claude Code
6.1 KiB
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:
// ❌ 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:
// 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=trueunless 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:
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<script>tags and execute XSS unless explicitly sanitized).
3. Magic Bytes (Header Signature) Inspection:
Do not trust the client Content-Type header or file extension alone. Inspect the initial bytes of the file buffer:
| Format | Magic Bytes Signature | Hex Representation |
|---|---|---|
| JPEG | \xFF\xD8\xFF |
FF D8 FF |
| PNG | \x89PNG\r\n\x1a\n |
89 50 4E 47 |
| WebP | RIFF....WEBP |
52 49 46 46 ('RIFF') |
%PDF- |
25 50 44 46 ('%PDF') |
4. Filename Sanitization & Non-Executable Storage:
- Strip directory traversal sequences (
../,..\\) and null bytes (\0). - Store files using randomized UUIDs/CUIDs (e.g.,
clx987654321.png) rather than the original user-supplied name. - Serve uploaded files with
Content-Disposition: attachmentor host from dedicated static object storage (S3/GCS) where server script execution is disabled.
Phase 4: Rate Limiting & Abuse Prevention
Protect Sensitive Action Gates:
- Login attempts: Max 5–10 per 15 minutes per IP.
- User registrations: Max 3–5 per hour per IP.
- Password resets: Max 3 per hour per email/IP.
- General API read/write: 60–120 requests per minute per authenticated user / IP.
Implementation Strategy:
- Single Server / Microservice: Use an in-memory sliding-window bucket with automatic timestamp cleanup.
- Distributed / Serverless: Use Redis (e.g., Upstash / Redis token bucket) to share rate limits across multiple instances.
Phase 5: Framework Hardening & HTTP Security Headers
1. Disable Fingerprinting
- In
next.config.ts/next.config.js:const nextConfig = { poweredByHeader: false, }; - In Express:
app.disable('x-powered-by');
2. Standard Security Headers
Inject these headers across all routes (via Next.js headers() config, middleware, or reverse proxy):
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Permissions-Policy: camera=(), microphone=(), geolocation=()
Phase 6: Continuous Dependency & Build Verification
Establish a 4-step security check before every release:
1. Vulnerability Audit:
npm audit
# Apply automated non-breaking security fixes:
npm audit fix
2. Static Code & Lint Analysis:
npm run lint
3. Automated Security Unit Tests:
Maintain unit tests validating that:
- File upload allowlists reject
.exe,.html,.svg, and mismatched magic bytes. - Rate limiters trigger HTTP 429 when thresholds are exceeded.
- Boundary helpers clamp oversized numbers and truncate/reject excessive payload strings.
- Endpoints reject unauthenticated mutations and IDOR resource manipulation.
4. Production Build Verification:
npm run build