Claude-skill-registry engineering-security-review
Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features. It provides a comprehensive security checklist and patterns.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/engineering-security-review" ~/.claude/skills/majiayu000-claude-skill-registry-engineering-security-review && rm -rf "$T"
manifest:
skills/data/engineering-security-review/SKILL.mdsource content
Security Review Skill
This skill ensures that all code follows security best practices and identifies potential vulnerabilities.
When to Activate
- Implementing authentication or authorization
- Handling user input or file uploads
- Creating new API endpoints
- Working with secrets or credentials
- Implementing payment features
- Storing or transmitting sensitive data
- Integrating third-party APIs
Security Checklist
1. Secrets Management
❌ NEVER Do This
const apiKey = "sk-proj-xxxxx"; // Hardcoded secret const dbPassword = "password123"; // In source code
✅ ALWAYS Do This
const apiKey = process.env.OPENAI_API_KEY; const dbUrl = process.env.DATABASE_URL; // Verify if secrets exist if (!apiKey) { throw new Error("OPENAI_API_KEY not configured"); }
Verification Steps
- No hardcoded API keys, tokens, or passwords
- All secrets in environment variables
-
in .gitignore.env.local - No secrets in git history
- Production secrets set on hosting platform (Vercel, Railway)
2. Input Validation
Always Validate User Input
import { z } from "zod"; // Define validation schema const CreateUserSchema = z.object({ email: z.string().email(), name: z.string().min(1).max(100), age: z.number().int().min(0).max(150), }); // Validate before processing export async function createUser(input: unknown) { try { const validated = CreateUserSchema.parse(input); return await db.users.create(validated); } catch (error) { if (error instanceof z.ZodError) { return { success: false, errors: error.errors }; } throw error; } }
File Upload Validation
function validateFileUpload(file: File) { // Size check (max 5MB) const maxSize = 5 * 1024 * 1024; if (file.size > maxSize) { throw new Error("File too large (max 5MB)"); } // Type check const allowedTypes = ["image/jpeg", "image/png", "image/gif"]; if (!allowedTypes.includes(file.type)) { throw new Error("Invalid file type"); } // Extension check const allowedExtensions = [".jpg", ".jpeg", ".png", ".gif"]; const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]; if (!extension || !allowedExtensions.includes(extension)) { throw new Error("Invalid file extension"); } return true; }
Verification Steps
- All user inputs validated with schemas
- File uploads restricted (size, type, extension)
- No direct use of user input in queries
- Whitelist validation (not blacklist)
- Error messages do not leak sensitive information
3. SQL Injection Prevention
❌ NEVER Concatenate SQL
// DANGEROUS - SQL Injection vulnerability const query = `SELECT * FROM users WHERE email = '${userEmail}'`; await db.query(query);
✅ ALWAYS Use Parameterized Queries
// Safe - parameterized query const { data } = await supabase .from("users") .select("*") .eq("email", userEmail); // Or with raw SQL await db.query("SELECT * FROM users WHERE email = $1", [userEmail]);
Verification Steps
- All database queries use parameterized queries
- No string concatenation in SQL
- ORM/query builder used correctly
- Supabase queries properly sanitized
4. Authentication & Authorization
JWT Token Handling
// ❌ WRONG: localStorage (vulnerable to XSS) localStorage.setItem("token", token); // ✅ CORRECT: httpOnly cookies res.setHeader( "Set-Cookie", `token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`, );
Authorization Checks
export async function deleteUser(userId: string, requesterId: string) { // ALWAYS verify authorization first const requester = await db.users.findUnique({ where: { id: requesterId }, }); if (requester.role !== "admin") { return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); } // Proceed with deletion await db.users.delete({ where: { id: userId } }); }
Row Level Security (Supabase)
-- Enable RLS on all tables ALTER TABLE users ENABLE ROW LEVEL SECURITY; -- Users can only see their own data CREATE POLICY "Users view own data" ON users FOR SELECT USING (auth.uid() = id); -- Users can only update their own data CREATE POLICY "Users update own data" ON users FOR UPDATE USING (auth.uid() = id);
Verification Steps
- Tokens stored in httpOnly cookies (not localStorage)
- Authorization checks before sensitive operations
- Row Level Security enabled in Supabase
- Role-based access control implemented
- Secure session management
5. XSS Prevention
Sanitize HTML
import DOMPurify from 'isomorphic-dompurify' // ALWAYS sanitize user-provided HTML 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' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.example.com; ` .replace(/\s{2,}/g, " ") .trim(), }, ];
Verification Steps
- User-provided HTML sanitized
- CSP headers configured
- No rendering of unvalidated dynamic content
- React's native XSS protection utilized
6. CSRF Protection
CSRF Tokens
import { csrf } from "@/lib/csrf"; export async function POST(request: Request) { const token = request.headers.get("X-CSRF-Token"); if (!csrf.verify(token)) { return NextResponse.json({ error: "Invalid CSRF token" }, { status: 403 }); } // Process request }
SameSite Cookies
res.setHeader( "Set-Cookie", `session=${sessionId}; HttpOnly; Secure; SameSite=Strict`, );
Verification Steps
- CSRF tokens in state-changing operations
- SameSite=Strict on all cookies
- Double-submit cookie pattern implemented
7. Rate Limiting
API Rate Limiting
import rateLimit from "express-rate-limit"; const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // 100 requests per window message: "Too many requests", }); // Apply to routes app.use("/api/", limiter);
Expensive Operations
// Aggressive rate limiting for searches const searchLimiter = rateLimit({ windowMs: 60 * 1000, // 1 minute max: 10, // 10 requests per minute message: "Too many search requests", }); app.use("/api/search", searchLimiter);
Verification Steps
- Rate limiting on all API endpoints
- Stricter limits on expensive operations
- IP-based rate limiting
- User-based (authenticated) rate limiting
8. Sensitive Data Exposure
Logging
// ❌ WRONG: Logging sensitive data console.log("User login:", { email, password }); console.log("Payment:", { cardNumber, cvv }); // ✅ CORRECT: Redact sensitive data console.log("User login:", { email, userId }); console.log("Payment:", { last4: card.last4, userId });
Error Messages
// ❌ WRONG: Exposing internal details catch (error) { return NextResponse.json( { error: error.message, stack: error.stack }, { status: 500 } ) } // ✅ CORRECT: Generic error messages catch (error) { console.error('Internal error:', error) return NextResponse.json( { error: 'An error occurred. Please try again.' }, { status: 500 } ) }
Verification Steps
- No passwords, tokens, or secrets in logs
- Generic error messages for users
- Detailed errors only in server logs
- No stack traces exposed to users
9. Blockchain Security (Solana)
Wallet Verification
import { verify } from "@solana/web3.js"; async function verifyWalletOwnership( publicKey: string, signature: string, message: string, ) { try { const isValid = verify( Buffer.from(message), Buffer.from(signature, "base64"), Buffer.from(publicKey, "base64"), ); return isValid; } catch (error) { return false; } }
Transaction Verification
async function verifyTransaction(transaction: Transaction) { // Verify recipient if (transaction.to !== expectedRecipient) { throw new Error("Invalid recipient"); } // Verify amount if (transaction.amount > maxAmount) { throw new Error("Amount exceeds limit"); } // Verify if user has sufficient balance const balance = await getBalance(transaction.from); if (balance < transaction.amount) { throw new Error("Insufficient balance"); } return true; }
Verification Steps
- Wallet signatures verified
- Transaction details validated
- Balance verification before transactions
- No blind signing of transactions
10. Dependency Security
Regular Updates
# Check for vulnerabilities npm audit # Fix automatically fixable issues npm audit fix # Update dependencies npm update # Check for outdated packages npm outdated
Lock Files
# ALWAYS commit lock files git add package-lock.json # Use in CI/CD for reproducible builds npm ci # Instead of npm install
Verification Steps
- Dependencies up to date
- No known vulnerabilities (clean npm audit)
- Lock files committed
- Dependabot enabled on GitHub
- Regular security updates
Security Testing
Automated Security Tests
// Test authentication test("requires authentication", async () => { const response = await fetch("/api/protected"); expect(response.status).toBe(401); }); // Test authorization test("requires admin role", async () => { const response = await fetch("/api/admin", { headers: { Authorization: `Bearer ${userToken}` }, }); expect(response.status).toBe(403); }); // Test input validation test("rejects invalid input", async () => { const response = await fetch("/api/users", { method: "POST", body: JSON.stringify({ email: "not-an-email" }), }); expect(response.status).toBe(400); }); // Test rate limiting test("enforces rate limits", async () => { const requests = Array(101) .fill(null) .map(() => fetch("/api/endpoint")); const responses = await Promise.all(requests); const tooManyRequests = responses.filter((r) => r.status === 429); expect(tooManyRequests.length).toBeGreaterThan(0); });
Pre-Deploy Security Checklist
Before ANY production deployment:
- Secrets: No hardcoded secrets, all in env vars
- Input Validation: All user inputs validated
- SQL Injection: All queries parameterized
- XSS: User content sanitized
- CSRF: Protection enabled
- Authentication: Proper token handling
- Authorization: Role checks implemented
- Rate Limiting: Enabled on all endpoints
- HTTPS: Enforced in production
- Security Headers: CSP, X-Frame-Options configured
- Error Handling: No sensitive data in errors
- Logging: No sensitive data logged
- Dependencies: Updated, no vulnerabilities
- Row Level Security: Enabled on Supabase
- CORS: Correctly configured
- File Upload: Validated (size, type)
- Wallet Signatures: Verified (if blockchain)
Resources
Remember: Security is not optional. A single vulnerability can compromise the entire platform. When in doubt, err on the side of caution.