Safe Coding Practices Guide
Summary Overview
Security-first checklist from the legacy docs that explains validation, secrets handling, and secure deployment routines.
Updated: 2025-11-24
securitybest-practicessecure-coding
Safe Coding Practices Guide
Overview
Secure coding practices are essential for protecting applications and systems from vulnerabilities and attacks. This guide covers fundamental security principles that every developer should follow.
Core Security Principles
1. Input Validation and Sanitization
Always Validate User Input
// Bad - No validation
function processUserData(userData) {
database.query(`SELECT FROM users WHERE id = ${userData.id}`);
}
// Good - Proper validation
function processUserData(userData) {
if (!userData || typeof userData.id !== 'number') {
throw new Error('Invalid user data');
}
const sanitizedId = parseInt(userData.id);
if (sanitizedId <= 0) {
throw new Error('Invalid user ID');
}
database.query('SELECT FROM users WHERE id = ?', [sanitizedId]);
}
Sanitize HTML Content
// Use libraries like DOMPurify
import DOMPurify from 'dompurify';
function displayUserContent(htmlContent) {
const cleanHTML = DOMPurify.sanitize(htmlContent);
document.getElementById('content').innerHTML = cleanHTML;
}
2. Authentication and Authorization
Strong Password Policies
function validatePassword(password) {
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&(),.?":{}|<>]/.test(password);
return password.length >= minLength &&
hasUpperCase &&
hasLowerCase &&
hasNumbers &&
hasSpecialChar;
}
Secure Session Management
// Use secure session configuration
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // Prevent XSS
maxAge: 1000 60 60 24 // 24 hours
}
}));
3. SQL Injection Prevention
Use Parameterized Queries
// Bad - SQL Injection vulnerable
const query = `SELECT FROM users WHERE username = '${username}'`;
// Good - Parameterized query
const query = 'SELECT FROM users WHERE username = ?';
database.query(query, [username]);
Use ORM/Query Builders
// Using an ORM like Sequelize
const user = await User.findOne({
where: {
username: username
}
});
4. Cross-Site Scripting (XSS) Prevention
Escape Output
// Bad - Direct output
document.innerHTML = userInput;
// Good - Escaped output
document.textContent = userInput;
// Or use template literals with escaping
function escapeHTML(str) {
return str.replace(/[&<>'"]/g, function(tag) {
const charsToReplace = {
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
};
return charsToReplace[tag] || tag;
});
}
Content Security Policy (CSP)
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;">
5. Secure Data Storage
Environment Variables
// Never hardcode sensitive data
// Bad
const API_KEY = "sk-1234567890abcdef";
// Good - Use environment variables
const API_KEY = process.env.API_KEY;
// Validate environment variables
if (!API_KEY) {
throw new Error('API_KEY environment variable is required');
}
Encryption for Sensitive Data
const crypto = require('crypto');
function encryptSensitiveData(data) {
const algorithm = 'aes-256-gcm';
const key = crypto.scryptSync(process.env.ENCRYPTION_KEY, 'salt', 32);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(algorithm, key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
encrypted: encrypted,
iv: iv.toString('hex'),
tag: cipher.getAuthTag().toString('hex')
};
}
HTTPS and Transport Security
Always Use HTTPS
// Express.js HTTPS redirect middleware
function requireHTTPS(req, res, next) {
if (!req.secure && req.get('x-forwarded-proto') !== 'https') {
return res.redirect('https://' + req.get('host') + req.url);
}
next();
}
app.use(requireHTTPS);
HTTP Security Headers
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
Error Handling and Logging
Secure Error Messages
// Bad - Exposes internal information
function loginUser(username, password) {
try {
const user = database.findUser(username);
if (!user) {
throw new Error(`User ${username} not found in database table users`);
}
// ... rest of login logic
} catch (error) {
res.status(500).json({ error: error.message });
}
}
// Good - Generic error messages
function loginUser(username, password) {
try {
const user = database.findUser(username);
if (!user) {
throw new Error('Invalid credentials');
}
// ... rest of login logic
} catch (error) {
logger.error('Login attempt failed', { username, error: error.message });
res.status(401).json({ error: 'Invalid credentials' });
}
}
Proper Logging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// Log security events
function logSecurityEvent(event, details) {
logger.warn('Security Event', {
event: event,
details: details,
timestamp: new Date().toISOString(),
ip: req.ip,
userAgent: req.get('User-Agent')
});
}
Code Review and Testing
Security Code Review Checklist
- [ ] Input validation implemented
- [ ] SQL injection prevention measures
- [ ] XSS prevention measures
- [ ] Authentication and authorization checks
- [ ] Sensitive data properly handled
- [ ] Error messages don't expose internal info
- [ ] HTTPS enforced
- [ ] Security headers configured
- [ ] Dependencies up to date
- [ ] No hardcoded secrets
Security Testing
// Example security test
describe('Security Tests', () => {
test('should reject SQL injection attempts', async () => {
const maliciousInput = "'; DROP TABLE users; --";
const response = await request(app)
.post('/api/users')
.send({ username: maliciousInput });
expect(response.status).toBe(400);
expect(response.body.error).toBe('Invalid input');
});
test('should sanitize XSS attempts', async () => {
const xssPayload = '<script>alert("XSS")</script>';
const response = await request(app)
.post('/api/comments')
.send({ comment: xssPayload });
expect(response.body.comment).not.toContain('<script>');
});
});
Development Tools and Practices
Static Code Analysis
# Install security linting tools
npm install --save-dev eslint-plugin-security
npm install --save-dev semgrep
# Run security analysis
npx eslint --ext .js,.ts src/
npx semgrep --config=auto .
Dependency Security Scanning
# Check for vulnerable dependencies
npm audit
npm audit fix
# Use tools like Snyk
npx snyk test
npx snyk monitor
Environment Separation
# Different configurations for different environments
# .env.development
NODE_ENV=development
DEBUG=true
API_URL=http://localhost:3000
# .env.production
NODE_ENV=production
DEBUG=false
API_URL=https://api.example.com
Security Resources
Essential Reading
Security Tools
- Static Analysis: ESLint Security Plugin, SonarQube, Semgrep
- Dependency Scanning: npm audit, Snyk, WhiteSource
- Runtime Protection: Helmet.js, express-rate-limit
- Testing: OWASP ZAP, Burp Suite, Nmap
Best Practices Summary
- Never trust user input - Always validate and sanitize
- Use parameterized queries - Prevent SQL injection
- Implement proper authentication - Use strong passwords and sessions
- Keep dependencies updated - Regular security updates
- Use HTTPS everywhere - Encrypt data in transit
- Log security events - Monitor for suspicious activity
- Follow principle of least privilege - Minimal necessary permissions
- Regular security reviews - Code reviews and penetration testing
Remember: Security is not a one-time implementation but an ongoing process that requires constant attention and updates.