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.
$npx -y skills add affaan-m/everything-claude-code --skill security-reviewInstalls into the current project.
Run `npx skills use "https://github.com/affaan-m/everything-claude-code" --skill "affaan-m/everything-claude-code/security-review"` and follow the generated skill instructions now. Read its complete output, redirecting it to a temporary file first if necessary. Resolve relative paths from the supporting-files directory it provides.
Use the skills in "https://github.com/affaan-m/everything-claude-code" that are relevant to the current task. Run `npx skills add "https://github.com/affaan-m/everything-claude-code"` and select the relevant skills, then follow their instructions.
| 1 | # Security Review Skill |
| 2 | |
| 3 | This skill ensures all code follows security best practices and identifies potential vulnerabilities. |
| 4 | |
| 5 | ## When to Activate |
| 6 | |
| 7 | - Implementing authentication or authorization |
| 8 | - Handling user input or file uploads |
| 9 | - Creating new API endpoints |
| 10 | - Working with secrets or credentials |
| 11 | - Implementing payment features |
| 12 | - Storing or transmitting sensitive data |
| 13 | - Integrating third-party APIs |
| 14 | |
| 15 | ## Security Checklist |
| 16 | |
| 17 | ### 1. Secrets Management |
| 18 | |
| 19 | #### FAIL: NEVER Do This |
| 20 | ```typescript |
| 21 | const apiKey = "sk-proj-xxxxx" // Hardcoded secret |
| 22 | const dbPassword = "password123" // In source code |
| 23 | ``` |
| 24 | |
| 25 | #### PASS: ALWAYS Do This |
| 26 | ```typescript |
| 27 | const apiKey = process.env.OPENAI_API_KEY |
| 28 | const dbUrl = process.env.DATABASE_URL |
| 29 | |
| 30 | // Verify secrets exist |
| 31 | if (!apiKey) { |
| 32 | throw new Error('OPENAI_API_KEY not configured') |
| 33 | } |
| 34 | ``` |
| 35 | |
| 36 | #### Verification Steps |
| 37 | - [ ] No hardcoded API keys, tokens, or passwords |
| 38 | - [ ] All secrets in environment variables |
| 39 | - [ ] `.env.local` in .gitignore |
| 40 | - [ ] No secrets in git history |
| 41 | - [ ] Production secrets in hosting platform (Vercel, Railway) |
| 42 | |
| 43 | ### 2. Input Validation |
| 44 | |
| 45 | #### Always Validate User Input |
| 46 | ```typescript |
| 47 | import { z } from 'zod' |
| 48 | |
| 49 | // Define validation schema |
| 50 | const CreateUserSchema = z.object({ |
| 51 | email: z.string().email(), |
| 52 | name: z.string().min(1).max(100), |
| 53 | age: z.number().int().min(0).max(150) |
| 54 | }) |
| 55 | |
| 56 | // Validate before processing |
| 57 | export async function createUser(input: unknown) { |
| 58 | try { |
| 59 | const validated = CreateUserSchema.parse(input) |
| 60 | return await db.users.create(validated) |
| 61 | } catch (error) { |
| 62 | if (error instanceof z.ZodError) { |
| 63 | return { success: false, errors: error.errors } |
| 64 | } |
| 65 | throw error |
| 66 | } |
| 67 | } |
| 68 | ``` |
| 69 | |
| 70 | #### File Upload Validation |
| 71 | ```typescript |
| 72 | function validateFileUpload(file: File) { |
| 73 | // Size check (5MB max) |
| 74 | const maxSize = 5 * 1024 * 1024 |
| 75 | if (file.size > maxSize) { |
| 76 | throw new Error('File too large (max 5MB)') |
| 77 | } |
| 78 | |
| 79 | // Type check |
| 80 | const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'] |
| 81 | if (!allowedTypes.includes(file.type)) { |
| 82 | throw new Error('Invalid file type') |
| 83 | } |
| 84 | |
| 85 | // Extension check |
| 86 | const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'] |
| 87 | const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0] |
| 88 | if (!extension || !allowedExtensions.includes(extension)) { |
| 89 | throw new Error('Invalid file extension') |
| 90 | } |
| 91 | |
| 92 | return true |
| 93 | } |
| 94 | ``` |
| 95 | |
| 96 | #### Verification Steps |
| 97 | - [ ] All user inputs validated with schemas |
| 98 | - [ ] File uploads restricted (size, type, extension) |
| 99 | - [ ] No direct use of user input in queries |
| 100 | - [ ] Whitelist validation (not blacklist) |
| 101 | - [ ] Error messages don't leak sensitive info |
| 102 | |
| 103 | ### 3. SQL Injection Prevention |
| 104 | |
| 105 | #### FAIL: NEVER Concatenate SQL |
| 106 | ```typescript |
| 107 | // DANGEROUS - SQL Injection vulnerability |
| 108 | const query = `SELECT * FROM users WHERE email = '${userEmail}'` |
| 109 | await db.query(query) |
| 110 | ``` |
| 111 | |
| 112 | #### PASS: ALWAYS Use Parameterized Queries |
| 113 | ```typescript |
| 114 | // Safe - parameterized query |
| 115 | const { data } = await supabase |
| 116 | .from('users') |
| 117 | .select('*') |
| 118 | .eq('email', userEmail) |
| 119 | |
| 120 | // Or with raw SQL |
| 121 | await db.query( |
| 122 | 'SELECT * FROM users WHERE email = $1', |
| 123 | [userEmail] |
| 124 | ) |
| 125 | ``` |
| 126 | |
| 127 | #### Verification Steps |
| 128 | - [ ] All database queries use parameterized queries |
| 129 | - [ ] No string concatenation in SQL |
| 130 | - [ ] ORM/query builder used correctly |
| 131 | - [ ] Supabase queries properly sanitized |
| 132 | |
| 133 | ### 4. Authentication & Authorization |
| 134 | |
| 135 | #### JWT Token Handling |
| 136 | ```typescript |
| 137 | // FAIL: WRONG: localStorage (vulnerable to XSS) |
| 138 | localStorage.setItem('token', token) |
| 139 | |
| 140 | // PASS: CORRECT: httpOnly cookies |
| 141 | res.setHeader('Set-Cookie', |
| 142 | `token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`) |
| 143 | ``` |
| 144 | |
| 145 | #### Authorization Checks |
| 146 | ```typescript |
| 147 | export async function deleteUser(userId: string, requesterId: string) { |
| 148 | // ALWAYS verify authorization first |
| 149 | const requester = await db.users.findUnique({ |
| 150 | where: { id: requesterId } |
| 151 | }) |
| 152 | |
| 153 | if (requester.role !== 'admin') { |
| 154 | return NextResponse.json( |
| 155 | { error: 'Unauthorized' }, |
| 156 | { status: 403 } |
| 157 | ) |
| 158 | } |
| 159 | |
| 160 | // Proceed with deletion |
| 161 | await db.users.delete({ where: { id: userId } }) |
| 162 | } |
| 163 | ``` |
| 164 | |
| 165 | #### Row Level Security (Supabase) |
| 166 | ```sql |
| 167 | -- Enable RLS on all tables |
| 168 | ALTER TABLE users ENABLE ROW LEVEL SECURITY; |
| 169 | |
| 170 | -- Users can only view their own data |
| 171 | CREATE POLICY "Users view own data" |
| 172 | ON users FOR SELECT |
| 173 | USING (auth.uid() = id); |
| 174 | |
| 175 | -- Users can only update their own data |
| 176 | CREATE POLICY "Users update own data" |
| 177 | ON users FOR UPDATE |
| 178 | USING (auth.uid() = id); |
| 179 | ``` |
| 180 | |
| 181 | #### Verification Steps |
| 182 | - [ ] Tokens stored in httpOnly cookies (not localStorage) |
| 183 | - [ ] Authorization checks before sensitive operations |
| 184 | - [ ] Row Level Security enabled in Supabase |
| 185 | - [ ] Role-based access control implemented |
| 186 | - [ ] Session management secure |
| 187 | |
| 188 | ### 5. XSS Prevention |
| 189 | |
| 190 | #### Sanitize HTML |
| 191 | ```typescript |
| 192 | import DOMPurify from 'isomorphic-dompurify' |
| 193 | |
| 194 | // ALWAYS sanitize user-provided HTML |
| 195 | function renderUserContent(html: string) { |
| 196 | const clean = DOMPurify.sanitize(html, { |
| 197 | ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'], |
| 198 | ALLOWED_ATTR: [] |
| 199 | }) |
| 200 | return <div dangerouslySetInnerHTML={{ __html: clean }} /> |
| 201 | } |
| 202 | ``` |
| 203 | |
| 204 | #### Content Security Policy |
| 205 | ```typescript |
| 206 | // next.config.js |
| 207 | const securityHeaders = [ |
| 208 | { |
| 209 | key: 'Content-Security-Policy', |
| 210 | value: ` |
| 211 | default-src 'self'; |
| 212 | script-src 'self' 'unsafe-eval' 'unsafe-inline'; |
| 213 | style-src 'self' 'unsafe-inline'; |
| 214 | img-src 'self' data: https:; |
| 215 | font-src 'self'; |
| 216 | connect-src 'self' https://api.example.com; |
| 217 | `.replace(/\s{2,}/g, ' ').trim() |
| 218 | } |
| 219 | ] |
| 220 | ``` |
| 221 | |
| 222 | #### Verification Steps |
| 223 | - [ ] User-provided HTML sanitized |
| 224 | - [ ] CSP headers configured |
| 225 | - [ ] No unvalidated dynamic content rendering |
| 226 | - [ ] React's built-in XSS protection used |
| 227 | |
| 228 | ### 6. CSRF Protection |
| 229 | |
| 230 | #### CSRF Tokens |
| 231 | ```typescript |
| 232 | import { csrf } from '@/lib/csrf' |
| 233 | |
| 234 | export async function POST(request: Request) { |
| 235 | const token = request.headers.get('X-CSRF-Token') |
| 236 | |
| 237 | if (!csrf.verify(token)) { |
| 238 | return NextResponse.json( |
| 239 | { error: 'Invalid CSRF token' }, |
| 240 | { status: 403 } |
| 241 | ) |
| 242 | } |
| 243 | |
| 244 | // Process request |
| 245 | } |
| 246 | ``` |
| 247 | |
| 248 | #### SameSite Cookies |
| 249 | ```typescript |
| 250 | res.setHeader('Set-Cookie', |
| 251 | `session=${sessionId}; HttpOnly; Secure; SameSite=Strict`) |
| 252 | ``` |
| 253 | |
| 254 | #### Verification Steps |
| 255 | - [ ] CSRF tokens on state-changing operations |
| 256 | - [ ] SameSite=Strict on all cookies |
| 257 | - [ ] Double-submit cookie pattern implemented |
| 258 | |
| 259 | ### 7. Rate Limiting |
| 260 | |
| 261 | #### API Rate Limiting |
| 262 | ```typescript |
| 263 | import rateLimit from 'express-rate-limit' |
| 264 | |
| 265 | const limiter = rateLimit({ |
| 266 | windowMs: 15 * 60 * 1000, // 15 minutes |
| 267 | max: 100, // 100 requests per window |
| 268 | message: 'Too many requests' |
| 269 | }) |
| 270 | |
| 271 | // Apply to routes |
| 272 | app.use('/api/', limiter) |
| 273 | ``` |
| 274 | |
| 275 | #### Expensive Operations |
| 276 | ```typescript |
| 277 | // Aggressive rate limiting for searches |
| 278 | const searchLimiter = rateLimit({ |
| 279 | windowMs: 60 * 1000, // 1 minute |
| 280 | max: 10, // 10 requests per minute |
| 281 | message: 'Too many search requests' |
| 282 | }) |
| 283 | |
| 284 | app.use('/api/search', searchLimiter) |
| 285 | ``` |
| 286 | |
| 287 | #### Verification Steps |
| 288 | - [ ] Rate limiting on all API endpoints |
| 289 | - [ ] Stricter limits on expensive operations |
| 290 | - [ ] IP-based rate limiting |
| 291 | - [ ] User-based rate limiting (authenticated) |
| 292 | |
| 293 | ### 8. Sensitive Data Exposure |
| 294 | |
| 295 | #### Logging |
| 296 | ```typescript |
| 297 | // FAIL: WRONG: Logging sensitive data |
| 298 | console.log('User login:', { email, password }) |
| 299 | console.log('Payment:', { cardNumber, cvv }) |
| 300 | |
| 301 | // PASS: CORRECT: Redact sensitive data |
| 302 | console.log('User login:', { email, userId }) |
| 303 | console.log('Payment:', { last4: card.last4, userId }) |
| 304 | ``` |
| 305 | |
| 306 | #### Error Messages |
| 307 | ```typescript |
| 308 | // FAIL: WRONG: Exposing internal details |
| 309 | catch (error) { |
| 310 | return NextResponse.json( |
| 311 | { error: error.message, stack: error.stack }, |
| 312 | { status: 500 } |
| 313 | ) |
| 314 | } |
| 315 | |
| 316 | // PASS: CORRECT: Generic error messages |
| 317 | catch (error) { |
| 318 | console.error('Internal error:', error) |
| 319 | return NextResponse.json( |
| 320 | { error: 'An error occurred. Please try again.' }, |
| 321 | { status: 500 } |
| 322 | ) |
| 323 | } |
| 324 | ``` |
| 325 | |
| 326 | #### Verification Steps |
| 327 | - [ ] No passwords, tokens, or secrets in logs |
| 328 | - [ ] Error messages generic for users |
| 329 | - [ ] Detailed errors only in server logs |
| 330 | - [ ] No stack traces exposed to users |
| 331 | |
| 332 | ### 9. Blockchain Security (Solana) |
| 333 | |
| 334 | #### Wallet Verification |
| 335 | ```typescript |
| 336 | import { verify } from '@solana/web3.js' |
| 337 | |
| 338 | async function verifyWalletOwnership( |
| 339 | publicKey: string, |
| 340 | signature: string, |
| 341 | message: string |
| 342 | ) { |
| 343 | try { |
| 344 | const isValid = verify( |
| 345 | Buffer.from(message), |
| 346 | Buffer.from(signature, 'base64'), |
| 347 | Buffer.from(publicKey, 'base64') |
| 348 | ) |
| 349 | return isValid |
| 350 | } catch (error) { |
| 351 | return false |
| 352 | } |
| 353 | } |
| 354 | ``` |
| 355 | |
| 356 | #### Transaction Verification |
| 357 | ```typescript |
| 358 | async function verifyTransaction(transaction: Transaction) { |
| 359 | // Verify recipient |
| 360 | if (transaction.to !== expectedRecipient) { |
| 361 | throw new Error('Invalid recipient') |
| 362 | } |
| 363 | |
| 364 | // Verify amount |
| 365 | if (transaction.amount > maxAmount) { |
| 366 | throw new Error('Amount exceeds limit') |
| 367 | } |
| 368 | |
| 369 | // Verify user has sufficient balance |
| 370 | const balance = await getBalance(transaction.from) |
| 371 | if (balance < transaction.amount) { |
| 372 | throw new Error('Insufficient balance') |
| 373 | } |
| 374 | |
| 375 | return true |
| 376 | } |
| 377 | ``` |
| 378 | |
| 379 | #### Verification Steps |
| 380 | - [ ] Wallet signatures verified |
| 381 | - [ ] Transaction details validated |
| 382 | - [ ] Balance checks before transactions |
| 383 | - [ ] No blind transaction signing |
| 384 | |
| 385 | ### 10. Dependency Security |
| 386 | |
| 387 | #### Regular Updates |
| 388 | ```bash |
| 389 | # Check for vulnerabilities |
| 390 | npm audit |
| 391 | |
| 392 | # Fix automatically fixable issues |
| 393 | npm audit fix |
| 394 | |
| 395 | # Update dependencies |
| 396 | npm update |
| 397 | |
| 398 | # Check for outdated packages |
| 399 | npm outdated |
| 400 | ``` |
| 401 | |
| 402 | #### Lock Files |
| 403 | ```bash |
| 404 | # ALWAYS commit lock files |
| 405 | git add package-lock.json |
| 406 | |
| 407 | # Use in CI/CD for reproducible builds |
| 408 | npm ci # Instead of npm install |
| 409 | ``` |
| 410 | |
| 411 | #### Verification Steps |
| 412 | - [ ] Dependencies up to date |
| 413 | - [ ] No known vulnerabilities (npm audit clean) |
| 414 | - [ ] Lock files committed |
| 415 | - [ ] Dependabot enabled on GitHub |
| 416 | - [ ] Regular security updates |
| 417 | |
| 418 | ## Security Testing |
| 419 | |
| 420 | ### Automated Security Tests |
| 421 | ```typescript |
| 422 | // Test authentication |
| 423 | test('requires authentication', async () => { |
| 424 | const response = await fetch('/api/protected') |
| 425 | expect(response.status).toBe(401) |
| 426 | }) |
| 427 | |
| 428 | // Test authorization |
| 429 | test('requires admin role', async () => { |
| 430 | const response = await fetch('/api/admin', { |
| 431 | headers: { Authorization: `Bearer ${userToken}` } |
| 432 | }) |
| 433 | expect(response.status).toBe(403) |
| 434 | }) |
| 435 | |
| 436 | // Test input validation |
| 437 | test('rejects invalid input', async () => { |
| 438 | const response = await fetch('/api/users', { |
| 439 | method: 'POST', |
| 440 | body: JSON.stringify({ email: 'not-an-email' }) |
| 441 | }) |
| 442 | expect(response.status).toBe(400) |
| 443 | }) |
| 444 | |
| 445 | // Test rate limiting |
| 446 | test('enforces rate limits', async () => { |
| 447 | const requests = Array(101).fill(null).map(() => |
| 448 | fetch('/api/endpoint') |
| 449 | ) |
| 450 | |
| 451 | const responses = await Promise.all(requests) |
| 452 | const tooManyRequests = responses.filter(r => r.status === 429) |
| 453 | |
| 454 | expect(tooManyRequests.length).toBeGreaterThan(0) |
| 455 | }) |
| 456 | ``` |
| 457 | |
| 458 | ## Pre-Deployment Security Checklist |
| 459 | |
| 460 | Before ANY production deployment: |
| 461 | |
| 462 | - [ ] **Secrets**: No hardcoded secrets, all in env vars |
| 463 | - [ ] **Input Validation**: All user inputs validated |
| 464 | - [ ] **SQL Injection**: All queries parameterized |
| 465 | - [ ] **XSS**: User content sanitized |
| 466 | - [ ] **CSRF**: Protection enabled |
| 467 | - [ ] **Authentication**: Proper token handling |
| 468 | - [ ] **Authorization**: Role checks in place |
| 469 | - [ ] **Rate Limiting**: Enabled on all endpoints |
| 470 | - [ ] **HTTPS**: Enforced in production |
| 471 | - [ ] **Security Headers**: CSP, X-Frame-Options configured |
| 472 | - [ ] **Error Handling**: No sensitive data in errors |
| 473 | - [ ] **Logging**: No sensitive data logged |
| 474 | - [ ] **Dependencies**: Up to date, no vulnerabilities |
| 475 | - [ ] **Row Level Security**: Enabled in Supabase |
| 476 | - [ ] **CORS**: Properly configured |
| 477 | - [ ] **File Uploads**: Validated (size, type) |
| 478 | - [ ] **Wallet Signatures**: Verified (if blockchain) |
| 479 | |
| 480 | ## Resources |
| 481 | |
| 482 | - [OWASP Top 10](https://owasp.org/www-project-top-ten/) |
| 483 | - [Next.js Security](https://nextjs.org/docs/security) |
| 484 | - [Supabase Security](https://supabase.com/docs/guides/auth) |
| 485 | - [Web Security Academy](https://portswigger.net/web-security) |
| 486 | |
| 487 | --- |
| 488 | |
| 489 | **Remember**: Security is not optional. One vulnerability can compromise the entire platform. When in doubt, err on the side of caution. |