$npx -y skills add affaan-m/ECC --skill security-reviewUse 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.
| 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 DOM |