From daffy0208-ai-dev-standards
Implement security best practices across the application stack. Use when securing APIs, implementing authentication, preventing vulnerabilities, or conducting security reviews. Covers OWASP Top 10, auth patterns, input validation, encryption, and security monitoring.
npx claudepluginhub joshuarweaver/cascade-content-creation-misc-1 --plugin daffy0208-ai-dev-standardsThis skill uses the workspace's default tool permissions.
Security is not optional - build it in from day one.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Security is not optional - build it in from day one.
Security is built-in, not bolted-on.
Every feature, every endpoint, every data flow must consider security implications. Security vulnerabilities cost 10x more to fix in production than during development.
Authentication: Who are you? Authorization: What can you do?
JWT (JSON Web Tokens):
// Example: Next.js API with JWT
import { SignJWT, jwtVerify } from 'jose'
const secret = new TextEncoder().encode(process.env.JWT_SECRET!)
export async function createToken(userId: string) {
return await new SignJWT({ userId })
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('15m')
.sign(secret)
}
export async function verifyToken(token: string) {
const { payload } = await jwtVerify(token, secret)
return payload
}
Session-Based:
OAuth 2.0 / OIDC:
RBAC (Role-Based Access Control):
// Define roles
enum Role {
ADMIN = 'admin',
USER = 'user',
GUEST = 'guest'
}
// Check permissions
function requireRole(allowedRoles: Role[]) {
return (req, res, next) => {
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' })
}
next()
}
}
// Usage
app.delete('/api/users/:id', requireRole([Role.ADMIN]), deleteUser)
ABAC (Attribute-Based):
Key Principles:
Never trust user input - validate, sanitize, escape everything.
โ Bad (Vulnerable):
// DON'T DO THIS!
const query = `SELECT * FROM users WHERE email = '${userInput}'`
db.query(query) // SQL injection vulnerability!
โ Good (Parameterized Queries):
// Always use parameterized queries
const query = 'SELECT * FROM users WHERE email = ?'
db.query(query, [userInput]) // Safe - parameterized
// With Prisma
const user = await prisma.user.findUnique({
where: { email: userInput } // Safe - ORM handles it
})
โ Bad (Vulnerable):
// DON'T DO THIS!
<div dangerouslySetInnerHTML={{ __html: userInput }} />
โ Good (Escaped):
// React automatically escapes
;<div>{userInput}</div> // Safe
// If you must render HTML, sanitize first
import DOMPurify from 'isomorphic-dompurify'
;<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
import { z } from 'zod'
const UserSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(8).max(100),
age: z.number().int().min(13).max(120),
website: z.string().url().optional()
})
// Validate
const result = UserSchema.safeParse(req.body)
if (!result.success) {
return res.status(400).json({ errors: result.error.issues })
}
const validData = result.data // Type-safe!
import multer from 'multer'
const upload = multer({
limits: {
fileSize: 5 * 1024 * 1024 // 5MB max
},
fileFilter: (req, file, cb) => {
// Whitelist file types
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
if (!allowedTypes.includes(file.mimetype)) {
return cb(new Error('Invalid file type'))
}
cb(null, true)
}
})
// Generate random filenames (don't trust user input)
const filename = crypto.randomUUID() + path.extname(file.originalname)
Key Principles:
Never commit secrets:
# .env (add to .gitignore!)
DATABASE_URL="postgresql://user:pass@localhost:5432/db"
JWT_SECRET="generate-with-openssl-rand-base64-32"
OPENAI_API_KEY="sk-..."
Access in code:
// Validate env vars on startup
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET is required')
}
const config = {
jwtSecret: process.env.JWT_SECRET,
databaseUrl: process.env.DATABASE_URL
}
Secret Management (Production):
// Next.js middleware
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Prevent clickjacking
response.headers.set('X-Frame-Options', 'DENY')
// Prevent MIME sniffing
response.headers.set('X-Content-Type-Options', 'nosniff')
// XSS Protection
response.headers.set('X-XSS-Protection', '1; mode=block')
// Content Security Policy
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
)
// HTTPS only
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
return response
}
// Configure CORS properly
app.use(
cors({
origin:
process.env.NODE_ENV === 'production' ? 'https://yourdomain.com' : 'http://localhost:3000',
credentials: true, // Allow cookies
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
})
)
Key Principles:
โ Never store passwords in plain text or use MD5/SHA1!
โ Use bcrypt or argon2:
import bcrypt from 'bcrypt'
// Hash password (on signup)
const saltRounds = 12
const hashedPassword = await bcrypt.hash(password, saltRounds)
await db.user.create({
email,
password: hashedPassword // Store hash, never plain text
})
// Verify password (on login)
const user = await db.user.findUnique({ where: { email } })
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) {
throw new Error('Invalid credentials')
}
Sensitive data should be encrypted:
import crypto from 'crypto'
const algorithm = 'aes-256-gcm'
const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex')
function encrypt(text: string) {
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv(algorithm, key, iv)
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()])
const authTag = cipher.getAuthTag()
return {
iv: iv.toString('hex'),
encryptedData: encrypted.toString('hex'),
authTag: authTag.toString('hex')
}
}
function decrypt(encrypted: any) {
const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(encrypted.iv, 'hex'))
decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex'))
return decipher.update(encrypted.encryptedData, 'hex', 'utf8') + decipher.final('utf8')
}
// Encrypt sensitive fields
const ssn = encrypt(user.ssn)
await db.user.update({ where: { id }, data: { ssnEncrypted: JSON.stringify(ssn) } })
Always use HTTPS:
mkcert for local HTTPSVerify external API certificates:
// Don't disable SSL verification in production!
fetch(url, {
// DON'T: agent: new https.Agent({ rejectUnauthorized: false })
})
Key Principles:
Prevent brute force and DDoS:
import rateLimit from 'express-rate-limit'
// Global rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Too many requests from this IP'
})
app.use(limiter)
// Stricter limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts per 15min
skipSuccessfulRequests: true
})
app.post('/api/auth/login', authLimiter, loginHandler)
Log all security-relevant events:
async function auditLog(event: {
userId?: string
action: string
resource: string
ip: string
userAgent: string
success: boolean
metadata?: any
}) {
await db.auditLog.create({
data: {
...event,
timestamp: new Date()
}
})
}
// Usage
await auditLog({
userId: user.id,
action: 'LOGIN',
resource: 'auth',
ip: req.ip,
userAgent: req.headers['user-agent'],
success: true
})
Don't leak sensitive info in errors:
โ Bad:
// Exposes database structure!
catch (error) {
res.status(500).json({ error: error.message })
}
โ Good:
catch (error) {
// Log full error server-side
console.error('Error:', error)
// Send generic message to client
res.status(500).json({
error: 'Internal server error',
requestId: generateRequestId() // For support
})
}
Tools to integrate:
Key Principles:
| # | Vulnerability | Prevention |
|---|---|---|
| 1 | Broken Access Control | Verify permissions server-side, default deny |
| 2 | Cryptographic Failures | Use TLS, hash passwords (bcrypt), encrypt PII |
| 3 | Injection | Parameterized queries, input validation |
| 4 | Insecure Design | Threat modeling, security requirements |
| 5 | Security Misconfiguration | Secure defaults, remove unused features |
| 6 | Vulnerable Components | Keep dependencies updated, use Dependabot |
| 7 | Auth & Session Issues | Strong passwords, MFA, secure session mgmt |
| 8 | Software & Data Integrity | Verify dependencies, sign releases |
| 9 | Logging & Monitoring Failures | Log security events, set up alerts |
| 10 | SSRF | Validate URLs, whitelist allowed domains |
npm audit)โ Wrong:
// Hiding UI doesn't prevent access!
{
user.role === 'admin' && <DeleteButton />
}
โ Right:
// Always verify on server
app.delete('/api/users/:id', requireAdmin, deleteUser)
โ Wrong:
// User can manipulate userId in request!
const userId = req.body.userId
await db.order.create({ data: { userId } })
โ Right:
// Get userId from authenticated session
const userId = req.user.id // From JWT/session
await db.order.create({ data: { userId } })
โ Wrong:
if (password.length < 6) throw new Error('Too short')
โ Right:
const passwordSchema = z
.string()
.min(8, 'At least 8 characters')
.regex(/[A-Z]/, 'Needs uppercase')
.regex(/[a-z]/, 'Needs lowercase')
.regex(/[0-9]/, 'Needs number')
โ Wrong:
// User can access any document by changing ID!
const doc = await db.document.findUnique({ where: { id: req.params.id } })
return res.json(doc)
โ Right:
// Verify ownership
const doc = await db.document.findFirst({
where: {
id: req.params.id,
userId: req.user.id // Ensure user owns this document
}
})
if (!doc) return res.status(404).json({ error: 'Not found' })
return res.json(doc)
// middleware.ts - Protect routes
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')
// Redirect to login if no token
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/api/private/:path*']
}
import helmet from 'helmet'
import mongoSanitize from 'express-mongo-sanitize'
app.use(helmet()) // Security headers
app.use(express.json({ limit: '10mb' })) // Prevent large payloads
app.use(mongoSanitize()) // Prevent NoSQL injection
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
async def get_current_user(token: str = Depends(security)):
try:
payload = verify_token(token.credentials)
return payload['user_id']
except:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/protected")
async def protected_route(user_id: str = Depends(get_current_user)):
return {"user_id": user_id}
' OR '1'='1<script>alert('XSS')</script>../../etc/passwdUse security-engineer skill when:
Skills:
api-designer - API design patterns (pairs with security)testing-strategist - Security testing strategiesdeployment-advisor - Production security configurationPatterns:
/STANDARDS/architecture-patterns/authentication-patterns.md/STANDARDS/best-practices/security-best-practices.mdExternal:
Security is everyone's responsibility. Build it in from day one. ๐