Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features. Provides comprehensive security checklist and patterns for secrets management, input validation, SQL injection prevention, XSS, CSRF, rate limiting, sensitive data exposure, and dependency security.
This skill ensures all code follows security best practices and identifies potential vulnerabilities.
// ❌ NEVER: Hardcoded secrets
const apiKey = "sk-proj-xxxxx"
const dbPassword = "password123"
// ✅ ALWAYS: Environment variables with existence check
const apiKey = process.env.OPENAI_API_KEY
if (!apiKey) throw new Error('OPENAI_API_KEY not configured')
.env.local in .gitignoreimport { z } from 'zod'
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150),
})
export async function createUser(input: unknown) {
const validated = CreateUserSchema.parse(input) // throws ZodError if invalid
return db.users.create(validated)
}
File Upload Validation:
function validateFileUpload(file: File) {
if (file.size > 5 * 1024 * 1024) throw new Error('File too large (max 5MB)')
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
if (!allowedTypes.includes(file.type)) throw new Error('Invalid file type')
}
// ❌ NEVER: String concatenation in SQL
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
// ✅ ALWAYS: Parameterized queries
await db.query('SELECT * FROM users WHERE email = $1', [userEmail])
// ✅ Prisma raw — use tagged template (not $queryRawUnsafe)
await prisma.$queryRaw`SELECT * FROM users WHERE email = ${userEmail}`
// ❌ WRONG: Token in localStorage (vulnerable to XSS)
localStorage.setItem('token', token)
// ✅ CORRECT: httpOnly cookie
res.setHeader('Set-Cookie',
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
Row Level Security (Supabase):
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users view own data"
ON users FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users update own data"
ON users FOR UPDATE USING (auth.uid() = id);
import DOMPurify from 'isomorphic-dompurify'
function renderUserContent(html: string) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
ALLOWED_ATTR: []
})
return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
Content Security Policy (next.config.js):
const securityHeaders = [{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self' https://api.yourdomain.com",
].join('; ')
}]
// SameSite=Strict on all session cookies provides CSRF protection
res.setHeader('Set-Cookie',
`session=${sessionId}; HttpOnly; Secure; SameSite=Strict`)
const authLimiter = rateLimit({
windowMs: 15 * 60_000,
max: 5, // 5 attempts per 15 minutes for auth
message: 'Too many attempts, try again in 15 minutes'
})
app.use('/api/auth', authLimiter)
app.use('/api/', rateLimit({ windowMs: 15 * 60_000, max: 100 }))
// ❌ WRONG: Logging sensitive data
console.log('User login:', { email, password })
// ✅ CORRECT: Redact sensitive data
console.log('User login:', { email, userId })
// ❌ WRONG: Exposing stack traces
return NextResponse.json({ error: error.message, stack: error.stack }, { status: 500 })
// ✅ CORRECT: Generic error with internal logging
console.error('Internal error:', error)
return NextResponse.json({ error: 'An error occurred. Please try again.' }, { status: 500 })
npm audit # Check for vulnerabilities
npm audit fix # Fix automatically fixable issues
npm ci # Use lock file in CI/CD (not npm install)
npm audit clean (no high/critical)npm audit returns no high/critical CVEsCRITICAL GUARDRAIL: ZERO-GUESSING POLICY You are strictly forbidden from guessing, assuming, or hallucinating code architecture, variable names, or logic. If the user asks for a fix, an adjustment, or a review, and has not provided the relevant source file (either via upload, text, or connected repository), you MUST stop immediately. Your only response should be to ask the user to provide the specific file(s) needed before you suggest any code changes.