Imported from buildpad-ai/skills (
security-and-hardening/SKILL.md). Install upstream withnpx skills add buildpad-ai/skills --skill security-and-hardening. Copyright stays with the author.
Security and Hardening
Overview
Security-first development practices for web applications. Treat every external input as hostile, every secret as sacred, and every authorization check as mandatory. Security isn't a phase — it's a constraint on every line of code that touches user data, authentication, or external systems.
When to Use
- Building anything that accepts user input
- Implementing authentication or authorization
- Storing or transmitting sensitive data
- Integrating with external APIs or services
- Adding file uploads, webhooks, or callbacks
- Handling payment or PII data
The Three-Tier Boundary System
Always Do (No Exceptions)
- Validate all external input at the system boundary (API routes, form handlers)
- Parameterize all database queries — never concatenate user input into SQL
- Encode output to prevent XSS (use framework auto-escaping, don't bypass it)
- Use HTTPS for all external communication
- Hash passwords with bcrypt/scrypt/argon2 (never store plaintext)
- Set security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options)
- Use httpOnly, secure, sameSite cookies for sessions
- Run
npm audit(orpnpm audit) before every release
Ask First (Requires Human Approval)
- Adding new authentication flows or changing auth logic
- Storing new categories of sensitive data (PII, payment info)
- Adding new external service integrations
- Changing CORS configuration
- Adding file upload handlers
- Modifying rate limiting or throttling
- Granting elevated permissions or roles
Never Do
- Never commit secrets to version control (API keys, passwords, tokens)
- Never log sensitive data (passwords, tokens, full credit card numbers)
- Never trust client-side validation as a security boundary
- Never disable security headers for convenience
- Never use
eval()orinnerHTMLwith user-provided data - Never store sessions in client-accessible storage (localStorage for auth tokens)
- Never expose stack traces or internal error details to users
DaaS-Specific Security
Proxy Pattern Enforcement
❌ Browser → DaaS Backend (CORS issues, token exposure)
❌ Browser → supabase.auth.signIn() (cookie issues)
✅ Browser → /api/auth/login → Supabase Auth
✅ Browser → /api/items/* → DaaS Backend
Environment Variables
# Server-side only (NEVER prefix with NEXT_PUBLIC_)
SUPABASE_SERVICE_ROLE_KEY=...
# Client-safe (only non-sensitive values)
NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
NEXT_PUBLIC_BUILDPAD_DAAS_URL=...
CORS Configuration
// DaaS CORS must use explicit origins when credentials: 'include' is used
// NEVER use cors_origins: ["*"] with credentials
{
cors_origins: [
"http://localhost:3000",
"https://main.d1234abcde.amplifyapp.com"
],
cors_allow_credentials: true,
cors_max_age: 0 // bust cached preflight during changes
}
Input Validation
import { z } from 'zod';
const CreateTaskSchema = z.object({
title: z.string().min(1).max(200).trim(),
description: z.string().max(2000).optional(),
priority: z.enum(['low', 'medium', 'high']).default('medium'),
dueDate: z.string().datetime().optional(),
});
// Validate at the route handler
export async function POST(req: NextRequest) {
const result = CreateTaskSchema.safeParse(await req.json());
if (!result.success) {
return NextResponse.json(
{ error: { code: 'VALIDATION_ERROR', details: result.error.flatten() } },
{ status: 422 }
);
}
// result.data is now typed and validated
}
Security Review Checklist
Authentication
- Passwords hashed with bcrypt/scrypt/argon2 (salt rounds ≥ 12)
- Session tokens are httpOnly, secure, sameSite
- Login has rate limiting
- Password reset tokens expire
Authorization
- Every endpoint checks user permissions
- Users can only access their own resources
- Admin actions require admin role verification via DaaS RBAC
Input
- All user input validated at the boundary
- SQL queries are parameterized
- HTML output is encoded/escaped
Data
- No secrets in code or version control
- Sensitive fields excluded from API responses
- PII encrypted at rest (if applicable)
Infrastructure
- Security headers configured (CSP, HSTS, etc.)
- CORS restricted to known origins
- Dependencies audited for vulnerabilities
- Error messages don't expose internals
Common Rationalizations
| Rationalization | Reality |
|---|---|
| "This is an internal tool, security doesn't matter" | Internal tools get compromised. Attackers target the weakest link. |
| "We'll add security later" | Security retrofitting is 10x harder than building it in. Add it now. |
| "No one would try to exploit this" | Automated scanners will find it. Security by obscurity is not security. |
| "The framework handles security" | Frameworks provide tools, not guarantees. You still need to use them correctly. |
| "It's just a prototype" | Prototypes become production. Security habits from day one. |
Red Flags
- User input passed directly to database queries, shell commands, or HTML rendering
- Secrets in source code or commit history
- API endpoints without authentication or authorization checks
- Missing CORS configuration or wildcard (
*) origins with credentials - No rate limiting on authentication endpoints
- Stack traces or internal errors exposed to users
- Dependencies with known critical vulnerabilities
Verification
After implementing security-relevant code:
-
pnpm auditshows no critical or high vulnerabilities - No secrets in source code or git history
- All user input validated at system boundaries
- Authentication and authorization checked on every protected endpoint
- Security headers present in response (check with browser DevTools)
- Error responses don't expose internal details
- Rate limiting active on auth endpoints
See Also
For detailed checklists, see references/security-checklist.md.