| name | security-standards |
| description | Security best practices for application development. Use when handling user input, authentication, secrets, or reviewing code for vulnerabilities. |
Security Standards
Guidelines for writing secure code and avoiding common vulnerabilities.
Core Principles
- Never trust user input - Validate and sanitize everything
- Least privilege - Grant minimum necessary permissions
- Defense in depth - Multiple layers of protection
- Fail securely - Errors should not expose sensitive data
- Keep secrets secret - Never commit credentials
Input Validation
Validate All Input
const userId = req.params.id;
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
const userId = parseInt(req.params.id, 10);
if (isNaN(userId) || userId < 0) {
throw new ValidationError('Invalid user ID');
}
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
Allowlist Over Denylist
const sanitized = input.replace(/<script>/gi, '');
const ALLOWED_PATTERN = /^[a-zA-Z0-9_-]+$/;
if (!ALLOWED_PATTERN.test(input)) {
throw new ValidationError('Invalid characters');
}
Type Coercion
if (req.body.admin == true) { ... }
if (req.body.admin === true && typeof req.body.admin === 'boolean') { ... }
SQL Injection Prevention
Always Use Parameterized Queries
db.query(`SELECT * FROM users WHERE email = '${email}'`);
db.query('SELECT * FROM users WHERE email = $1', [email]);
User.findOne({ where: { email } });
XSS Prevention
Escape Output
element.innerHTML = userComment;
element.textContent = userComment;
element.innerHTML = DOMPurify.sanitize(userComment);
React/JSX
<div>{userInput}</div>
<div dangerouslySetInnerHTML={{ __html: userInput }} />
Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
}));
Authentication
Password Handling
user.password = req.body.password;
const bcrypt = require('bcrypt');
user.passwordHash = await bcrypt.hash(req.body.password, 12);
const valid = await bcrypt.compare(inputPassword, user.passwordHash);
Session Management
app.use(session({
secret: process.env.SESSION_SECRET,
name: 'sessionId',
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000,
},
resave: false,
saveUninitialized: false,
}));
JWT Best Practices
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m', algorithm: 'HS256' }
);
jwt.verify(token, secret, { algorithms: ['HS256'] });
Authorization
Check Permissions Server-Side
if (user.role === 'admin') {
showAdminPanel();
}
app.delete('/users/:id', requireAuth, async (req, res) => {
if (req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
await deleteUser(req.params.id);
});
Avoid IDOR (Insecure Direct Object Reference)
app.get('/documents/:id', async (req, res) => {
const doc = await Document.findById(req.params.id);
res.json(doc);
});
app.get('/documents/:id', requireAuth, async (req, res) => {
const doc = await Document.findOne({
_id: req.params.id,
ownerId: req.user.id,
});
if (!doc) return res.status(404).json({ error: 'Not found' });
res.json(doc);
});
Secrets Management
Never Commit Secrets
# .gitignore
.env
.env.local
*.pem
*.key
credentials.json
secrets/
Environment Variables
const apiKey = process.env.API_KEY;
if (!apiKey) {
throw new Error('API_KEY environment variable required');
}
console.log('Connecting with key:', apiKey);
console.log('Connecting to API...');
Rotate Compromised Secrets Immediately
If a secret is ever committed:
- Revoke/rotate the secret immediately
- Remove from git history (if possible)
- Audit for unauthorized access
CSRF Protection
const csrf = require('csurf');
app.use(csrf({ cookie: true }));
<input type="hidden" name="_csrf" value="<%= csrfToken %>" />
headers: { 'X-CSRF-Token': csrfToken }
Rate Limiting
const rateLimit = require('express-rate-limit');
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
}));
app.use('/auth', rateLimit({
windowMs: 60 * 1000,
max: 5,
}));
Error Handling
Don't Leak Information
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack,
query: err.sql,
});
});
app.use((err, req, res, next) => {
console.error('Error:', err);
res.status(500).json({
error: 'An unexpected error occurred',
requestId: req.id,
});
});
Dependency Security
npm audit
pip install safety && safety check
npm update
npm outdated
npm ci
Security Headers
const helmet = require('helmet');
app.use(helmet());
Logging for Security
Log Security Events
logger.info('Login attempt', { email, success: true, ip: req.ip });
logger.warn('Failed login', { email, attempts: failedCount, ip: req.ip });
logger.warn('Unauthorized access attempt', {
userId: req.user.id,
resource: req.path,
ip: req.ip,
});
Never Log Sensitive Data
logger.info('User created', { user });
logger.info('User created', { userId: user.id, email: user.email });