| name | security-audit |
| description | Checklist for identifying and fixing common web security vulnerabilities (OWASP Top 10). Use when reviewing code for security issues. |
Security Audit
Follow this checklist to identify and fix common security vulnerabilities:
1. Cross-Site Scripting (XSS)
✅ Check:
- Never use
dangerouslySetInnerHTML without sanitization
- Sanitize user input before rendering
- Use Content Security Policy (CSP) headers
- Escape output in templates
<div dangerouslySetInnerHTML={{ __html: userInput }} />
<div>{userInput}</div>
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(userInput)
}} />
2. Cross-Site Request Forgery (CSRF)
✅ Check:
- Use CSRF tokens for state-changing requests
- Verify Origin/Referer headers
- Use SameSite cookie attribute
- Require authentication for sensitive actions
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });
app.post('/api/transfer', csrfProtection, (req, res) => {
});
<form>
<input type="hidden" name="_csrf" value={csrfToken} />
{/* ... */}
</form>
3. SQL Injection
✅ Check:
- Always use parameterized queries
- Never concatenate SQL strings with user input
- Use ORM with proper escaping (Prisma)
- Validate and sanitize all inputs
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;
const user = await prisma.user.findUnique({
where: { email: userEmail }
});
const users = await prisma.$queryRaw`
SELECT * FROM users WHERE email = ${userEmail}
`;
4. Authentication & Session Management
✅ Check:
- Hash passwords with bcrypt (>= 10 rounds)
- Use HTTPS only
- Set secure, HTTP-only cookies
- Implement session timeout
- Require re-authentication for sensitive actions
- Implement account lockout after failed attempts
res.cookie('token', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
5. Sensitive Data Exposure
✅ Check:
- Never log passwords, tokens, or credit cards
- Don't expose API keys in frontend code
- Use environment variables for secrets
- Encrypt sensitive data at rest
- Use HTTPS for all data transmission
- Remove sensitive data from error messages
const apiKey = "sk_live_abc123";
const apiKey = process.env.API_KEY;
console.log("User login:", { email, password });
console.log("User login:", { email });
6. Security Headers
✅ Check:
- Content-Security-Policy (CSP)
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Strict-Transport-Security (HSTS)
- X-XSS-Protection: 1; mode=block
import helmet from 'helmet';
app.use(helmet());
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('Content-Security-Policy', "default-src 'self'");
next();
});
7. Input Validation
✅ Check:
- Validate all user inputs (client AND server)
- Use allowlist, not blocklist
- Validate data types, lengths, formats
- Sanitize before use
- Reject invalid input
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email().max(255),
age: z.number().int().min(0).max(150),
role: z.enum(['user', 'admin']),
});
try {
const validData = userSchema.parse(req.body);
} catch (error) {
return res.status(400).json({ error: 'Invalid input' });
}
8. Rate Limiting
✅ Check:
- Implement rate limiting on all endpoints
- Stricter limits on authentication endpoints
- Rate limit by IP and/or user
- Return 429 Too Many Requests
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests, please try again later',
});
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
});
app.use('/api/', limiter);
app.use('/api/auth/login', authLimiter);
9. Dependency Security
✅ Check:
- Regular
npm audit or pnpm audit
- Keep dependencies updated
- Use Dependabot or Renovate
- Review dependency licenses
- Minimize dependencies
pnpm audit
pnpm audit --fix
pnpm update --latest
10. Authorization
✅ Check:
- Verify user permissions on every request
- Never trust client-side authorization
- Implement least privilege principle
- Check ownership before modifying resources
async function requireOwnership(req, res, next) {
const post = await prisma.post.findUnique({
where: { id: req.params.id }
});
if (!post || post.userId !== req.userId) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
}
app.delete('/api/posts/:id', authenticate, requireOwnership, async (req, res) => {
});
11. Error Handling
✅ Check:
- Don't expose stack traces to users
- Log errors securely
- Use generic error messages
- Don't reveal system information
res.status(500).json({
error: error.stack,
query: sqlQuery
});
res.status(500).json({
error: 'Internal server error'
});
logger.error('Database error', {
error: error.message,
userId: req.userId
});
12. File Upload Security
✅ Check:
- Validate file types (magic bytes, not just extension)
- Limit file size
- Scan for malware
- Store outside webroot
- Generate random filenames
const upload = multer({
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png'];
if (!allowedTypes.includes(file.mimetype)) {
return cb(new Error('Invalid file type'));
}
cb(null, true);
},
});
13. Security Checklist