Security Review Skill
A comprehensive security review checklist for web applications, covering common vulnerabilities and best practices.
Activation Conditions
- •When implementing authentication/authorization features
- •When handling user input
- •When creating new API endpoints
- •When working with secrets/credentials
- •When implementing payment or sensitive features
- •When handling file uploads
- •Before deploying to production
Security Checklist
1. Secret Management
Never Do This
javascript
// Hardcoded secrets const apiKey = "sk-proj-xxxxx" const dbPassword = "password123" const jwtSecret = "super-secret-key"
Correct Approach
javascript
// Use environment variables
const apiKey = process.env.API_KEY;
const dbUrl = process.env.DATABASE_URL;
// Validate secret existence at startup
if (!apiKey) {
throw new Error('API_KEY not configured');
}
Production Secret Management
Use a dedicated secret management service:
- •AWS Secrets Manager / SSM Parameter Store
- •Google Cloud Secret Manager
- •Azure Key Vault
- •HashiCorp Vault
- •Doppler, 1Password Secrets, etc.
Verification Items
- • No hardcoded API keys, tokens, or passwords
- • All secrets use environment variables or secret manager
- •
.env*files are in .gitignore - • No secrets in git history
- • Production secrets stored in secure vault
2. Input Validation
Server-Side Validation (Required)
javascript
// Always validate on the server side
function validateUserInput(input) {
// Type validation
if (typeof input.email !== 'string') {
throw new ValidationError('Email must be a string');
}
// Format validation
if (!isValidEmail(input.email)) {
throw new ValidationError('Invalid email format');
}
// Length limits
if (input.name.length > 100) {
throw new ValidationError('Name too long');
}
// Range validation
if (input.age < 0 || input.age > 150) {
throw new ValidationError('Invalid age');
}
// Array size limits
if (input.items.length > 100) {
throw new ValidationError('Too many items');
}
}
Common Validation Rules
| Input Type | Validation |
|---|---|
| String | Type check, min/max length, format (regex) |
| Number | Type check, min/max value, integer check |
| Format validation, length limit | |
| URL | Format validation, allowed protocols |
| Array | Type of elements, max size |
| File | Size limit, allowed types, extension check |
Verification Items
- • All user inputs validated on server side
- • String length limits applied
- • Number range limits applied
- • Array size limits applied
- • Error messages don't expose sensitive info
3. SQL Injection Prevention
Safe: Parameterized Queries
javascript
// Using parameterized queries (prepared statements)
const user = await db.query(
'SELECT * FROM users WHERE id = $1',
[userId]
);
// Using ORM query builders
const users = await db.users.findMany({
where: { status: userStatus }
});
Dangerous: String Concatenation
javascript
// NEVER do this - SQL injection vulnerability
const query = `SELECT * FROM users WHERE id = ${userId}`;
const query = "SELECT * FROM users WHERE name = '" + userName + "'";
Dynamic Queries (Safe Approach)
javascript
// Building dynamic conditions safely
const conditions = [];
const params = [];
if (filter.status) {
conditions.push('status = $' + (params.length + 1));
params.push(filter.status);
}
if (filter.category) {
conditions.push('category = $' + (params.length + 1));
params.push(filter.category);
}
const whereClause = conditions.length > 0
? 'WHERE ' + conditions.join(' AND ')
: '';
await db.query(`SELECT * FROM products ${whereClause}`, params);
Verification Items
- • All queries use parameterized statements
- • ORM query builders used where available
- • No string concatenation for SQL queries
- • Dynamic queries still use parameters
4. Authentication & Authorization
Token Validation
javascript
// JWT validation essentials
function validateToken(token) {
// 1. Verify signature
const payload = jwt.verify(token, SECRET_KEY);
// 2. Check expiration
if (payload.exp < Date.now() / 1000) {
throw new Error('Token expired');
}
// 3. Validate issuer/audience if applicable
if (payload.iss !== EXPECTED_ISSUER) {
throw new Error('Invalid issuer');
}
return payload;
}
Authorization Checks
javascript
// Resource-level authorization
async function getResource(resourceId, currentUser) {
const resource = await db.resources.findById(resourceId);
if (!resource) {
throw new NotFoundError();
}
// Check ownership or permission
if (resource.ownerId !== currentUser.id && !currentUser.isAdmin) {
throw new ForbiddenError();
}
return resource;
}
// Role-based access control
function requireRole(allowedRoles) {
return (req, res, next) => {
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
Multi-Tenancy Data Isolation
javascript
// Always filter by tenant/organization
async function getProducts(filters, currentUser) {
return db.products.findMany({
where: {
...filters,
// Force tenant isolation
organizationId: currentUser.organizationId
}
});
}
Verification Items
- • Authentication applied to all protected endpoints
- • Token signature and expiration verified
- • Authorization checked for each resource access
- • Multi-tenant data properly isolated
- • Session timeout implemented
- • Password requirements enforced
5. Rate Limiting
Implementation Guidelines
javascript
// Global rate limiting // Example: 100 requests per minute per IP // Stricter limits for sensitive endpoints // - Login: 5 attempts per minute // - Password reset: 3 requests per hour // - SMS/Email verification: 3 requests per minute // - File upload: 10 per hour // - Search/expensive operations: 30 per minute
Verification Items
- • Global rate limiting configured
- • Stricter limits on auth endpoints
- • Limits on expensive operations
- • IP-based and user-based limiting
- • Proper 429 response with Retry-After header
6. Sensitive Data Exposure Prevention
Logging Best Practices
javascript
// WRONG - Logging sensitive data
console.log('User login:', { email, password });
console.log('Payment:', { cardNumber, cvv });
console.log('Request:', req.headers.authorization);
// CORRECT - Redact sensitive fields
console.log('User login:', { email, userId });
console.log('Payment:', { last4: card.slice(-4), userId });
console.log('Request:', { method: req.method, path: req.path });
Error Response Handling
javascript
// WRONG - Exposing internal details
res.status(500).json({
error: err.message,
stack: err.stack,
query: failedQuery
});
// CORRECT - Generic error to client, detailed log server-side
logger.error('Database error', { error: err, query: failedQuery });
res.status(500).json({
error: 'An internal error occurred',
requestId: req.id // For support reference
});
Response Data Filtering
javascript
// Define what to expose explicitly
function toUserResponse(user) {
return {
id: user.id,
name: user.name,
email: user.email,
// Exclude: password, passwordHash, internalId, etc.
};
}
Verification Items
- • No passwords/tokens/secrets in logs
- • No stack traces in error responses
- • Response DTOs exclude sensitive fields
- • Debug endpoints disabled in production
7. XSS (Cross-Site Scripting) Prevention
Output Encoding
javascript
// Always encode user content before rendering
const safeHtml = escapeHtml(userInput);
// Use framework's built-in protection
// React: automatically escapes by default
// Vue: use {{ }} not v-html
// Angular: automatically sanitizes
// Be careful with dangerouslySetInnerHTML / v-html
// Only use with sanitized content
Content Security Policy
javascript
// Set CSP headers
res.setHeader('Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
);
Verification Items
- • User content properly escaped
- • CSP headers configured
- • No inline event handlers with user data
- • HttpOnly flag on sensitive cookies
8. File Upload Security
javascript
function validateFileUpload(file) {
const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];
const maxFileSize = 5 * 1024 * 1024; // 5MB
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'];
// Size validation
if (file.size > maxFileSize) {
throw new Error('File too large');
}
// MIME type validation
if (!allowedMimeTypes.includes(file.mimetype)) {
throw new Error('Invalid file type');
}
// Extension validation (prevent MIME spoofing)
const ext = path.extname(file.originalname).toLowerCase();
if (!allowedExtensions.includes(ext)) {
throw new Error('Invalid file extension');
}
// Generate safe filename
const safeFilename = `${uuid()}_${Date.now()}${ext}`;
return safeFilename;
}
Verification Items
- • File size limits enforced
- • MIME type validated
- • File extension validated
- • Filename sanitized
- • Files stored outside web root
- • Antivirus scanning for uploads (if applicable)
9. HTTPS and Transport Security
javascript
// Force HTTPS in production
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
return res.redirect(`https://${req.header('host')}${req.url}`);
}
next();
});
}
// Security headers
app.use(helmet()); // or set manually:
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
Verification Items
- • HTTPS enforced in production
- • HSTS header configured
- • Secure flag on cookies
- • Security headers applied
10. Dependency Security
bash
# Check for known vulnerabilities npm audit yarn audit pip-audit bundle audit # Keep dependencies updated npm update yarn upgrade
Verification Items
- • No known vulnerabilities in dependencies
- • Dependencies regularly updated
- • Lock files committed
- • Automated vulnerability scanning in CI
Pre-Deployment Security Checklist
Before every production deployment:
- • Secrets: No hardcoded secrets, using secret manager
- • Input Validation: All user inputs validated server-side
- • SQL Injection: Parameterized queries only
- • Authentication: Token validation on protected endpoints
- • Authorization: Resource-level access control
- • Rate Limiting: Applied to all endpoints
- • HTTPS: Enforced in production
- • Error Handling: No sensitive info exposed
- • Logging: No secrets/passwords in logs
- • Dependencies: No known vulnerabilities
- • Debug Code: console.log/debugger removed
Security Scan Commands
bash
# Search for hardcoded secrets grep -rn "api_key\|apikey\|secret\|password\s*=" --include="*.js" --include="*.ts" src/ grep -rn "sk-\|pk_\|Bearer " --include="*.js" --include="*.ts" src/ # Search for console.log (excluding tests) grep -rn "console.log" --include="*.js" --include="*.ts" src/ | grep -v ".test.\|.spec." # Check for .env files in git git ls-files | grep -E "\.env" # Dependency audit npm audit
Related Resources
Related Agents
- •security-reviewer: Deep security analysis agent
- •code-reviewer: General code quality review
Security is not optional. When in doubt, always choose the safer approach.