Best Practices
Comprehensive security best practices, guidelines, and implementation strategies for secure application development.
Security Best Practices
This guide provides comprehensive security best practices for developing, deploying, and maintaining secure applications. Following these guidelines helps ensure robust security posture and protection against common threats.
Secure Development Lifecycle (SDL)
Security by Design
// lib/security/secure-design.ts
export class SecureDesign {
static async implementSecurityByDesign() {
return {
threat_modeling: await this.performThreatModeling(),
security_requirements: await this.defineSecurityRequirements(),
secure_architecture: await this.reviewSecureArchitecture(),
security_controls: await this.implementSecurityControls()
}
}
private static async performThreatModeling() {
const threats = [
{
id: 'T001',
name: 'SQL Injection',
category: 'Input Validation',
severity: 'High',
mitigation: 'Parameterized queries, input validation',
status: 'Mitigated'
},
{
id: 'T002',
name: 'Cross-Site Scripting (XSS)',
category: 'Input Validation',
severity: 'High',
mitigation: 'Content Security Policy, output encoding',
status: 'Mitigated'
},
{
id: 'T003',
name: 'Authentication Bypass',
category: 'Authentication',
severity: 'Critical',
mitigation: 'Multi-factor authentication, session management',
status: 'Mitigated'
}
]
return {
total_threats: threats.length,
critical_threats: threats.filter(t => t.severity === 'Critical').length,
mitigated_threats: threats.filter(t => t.status === 'Mitigated').length,
threats: threats
}
}
static getSecurityChecklist() {
return {
design_phase: [
'Conduct threat modeling',
'Define security requirements',
'Design secure architecture',
'Plan security controls',
'Review data flow diagrams'
],
development_phase: [
'Use secure coding practices',
'Implement input validation',
'Apply output encoding',
'Use parameterized queries',
'Implement proper error handling'
],
testing_phase: [
'Perform security testing',
'Conduct code reviews',
'Run vulnerability scans',
'Test security controls',
'Validate threat mitigations'
],
deployment_phase: [
'Configure security settings',
'Enable security monitoring',
'Set up logging',
'Configure firewalls',
'Implement backup procedures'
]
}
}
}
Code Security Guidelines
// lib/security/coding-standards.ts
export class SecureCodingStandards {
// Input Validation Best Practices
static validateInput(input: string, type: 'email' | 'phone' | 'text' | 'number'): boolean {
const patterns = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
phone: /^\+?[\d\s\-\(\)]{10,}$/,
text: /^[a-zA-Z0-9\s\-_.,!?]{1,1000}$/,
number: /^\d+(\.\d+)?$/
}
// Always validate against whitelist
if (!patterns[type].test(input)) {
throw new Error(`Invalid ${type} format`)
}
// Check for common attack patterns
const dangerousPatterns = [
/<script/i,
/javascript:/i,
/vbscript:/i,
/onload=/i,
/onerror=/i,
/\bUNION\b/i,
/\bSELECT\b/i,
/\bINSERT\b/i,
/\bDELETE\b/i,
/\bDROP\b/i
]
for (const pattern of dangerousPatterns) {
if (pattern.test(input)) {
throw new Error('Input contains potentially dangerous content')
}
}
return true
}
// Safe Database Queries
static async safeQuery(query: string, params: any[]) {
// Always use parameterized queries
try {
return await supabase.rpc('safe_query', {
query_text: query,
query_params: params
})
} catch (error) {
// Never expose database errors to users
console.error('Database error:', error)
throw new Error('An error occurred while processing your request')
}
}
// Secure Password Handling
static async hashPassword(password: string): Promise<string> {
const bcrypt = require('bcrypt')
// Validate password strength
if (!this.isPasswordStrong(password)) {
throw new Error('Password does not meet security requirements')
}
// Use high cost factor for bcrypt
const saltRounds = 12
return await bcrypt.hash(password, saltRounds)
}
private static isPasswordStrong(password: string): boolean {
const requirements = [
password.length >= 12, // Minimum length
/[A-Z]/.test(password), // Uppercase letter
/[a-z]/.test(password), // Lowercase letter
/\d/.test(password), // Number
/[!@#$%^&*(),.?":{}|<>]/.test(password), // Special character
!/(.)\1{2,}/.test(password) // No more than 2 consecutive identical characters
]
return requirements.every(req => req === true)
}
// Secure Session Management
static generateSecureSessionId(): string {
const crypto = require('crypto')
return crypto.randomBytes(32).toString('hex')
}
static async validateSession(sessionId: string): Promise<boolean> {
if (!sessionId || typeof sessionId !== 'string') {
return false
}
// Check session format
if (!/^[a-f0-9]{64}$/.test(sessionId)) {
return false
}
// Verify session exists and is valid
const { data: session } = await supabase
.from('user_sessions')
.select('*')
.eq('id', sessionId)
.gte('expires_at', new Date().toISOString())
.single()
return !!session
}
// Secure Error Handling
static handleError(error: Error, context: string): void {
// Log detailed error for debugging
console.error(`Error in ${context}:`, {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
})
// Never expose internal errors to users
const sanitizedMessage = this.getSanitizedErrorMessage(error.message)
throw new Error(sanitizedMessage)
}
private static getSanitizedErrorMessage(originalMessage: string): string {
// Map internal errors to user-friendly messages
const errorMappings = {
'connection refused': 'Service temporarily unavailable',
'timeout': 'Request timed out, please try again',
'unauthorized': 'Access denied',
'forbidden': 'Insufficient permissions',
'not found': 'Resource not found'
}
for (const [internal, external] of Object.entries(errorMappings)) {
if (originalMessage.toLowerCase().includes(internal)) {
return external
}
}
return 'An unexpected error occurred'
}
}
Infrastructure Security
Server Hardening
# server-hardening.sh
#!/bin/bash
# Update system packages
apt update && apt upgrade -y
# Configure firewall (UFW)
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# Disable root login
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
# Configure SSH key-only authentication
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart ssh
# Install and configure fail2ban
apt install fail2ban -y
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# Configure automatic security updates
apt install unattended-upgrades -y
dpkg-reconfigure -plow unattended-upgrades
# Set up log monitoring
apt install logwatch -y
echo "Range = yesterday" >> /etc/logwatch/conf/logwatch.conf
echo "Detail = high" >> /etc/logwatch/conf/logwatch.conf
# Configure system limits
echo "* hard nofile 65536" >> /etc/security/limits.conf
echo "* soft nofile 65536" >> /etc/security/limits.conf
# Enable audit logging
apt install auditd -y
systemctl enable auditd
systemctl start auditd
Container Security
# Dockerfile.secure
FROM node:18-alpine AS base
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Set up app directory
WORKDIR /app
RUN chown nextjs:nodejs /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# Copy application code
COPY . .
# Remove unnecessary packages and files
RUN apk del --purge \
&& rm -rf /var/cache/apk/* \
&& rm -rf /tmp/* \
&& rm -rf /root/.npm
# Switch to non-root user
USER nextjs
# Expose port (non-privileged)
EXPOSE 3000
# Health check
HEALTHCHECK \
CMD curl -f http://localhost:3000/api/health || exit 1
CMD ["npm", "start"]
Kubernetes Security
# k8s-security-config.yaml
apiVersion: v1
kind: SecurityContext
metadata:
name: app-security-context
spec:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
spec:
podSelector:
matchLabels:
app: secure-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 3000
egress:
- to:
- podSelector:
matchLabels:
role: database
ports:
- protocol: TCP
port: 5432
---
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
Application Security
Security Headers Implementation
// middleware/security-headers.ts
export function securityHeaders() {
return {
// Prevent XSS attacks
'X-XSS-Protection': '1; mode=block',
// Prevent MIME type sniffing
'X-Content-Type-Options': 'nosniff',
// Control framing to prevent clickjacking
'X-Frame-Options': 'DENY',
// Enforce HTTPS
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
// Control referrer information
'Referrer-Policy': 'strict-origin-when-cross-origin',
// Permissions policy
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
// Content Security Policy
'Content-Security-Policy': [
"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.supabase.co",
"frame-ancestors 'none'",
"form-action 'self'",
"base-uri 'self'"
].join('; ')
}
}
// Next.js middleware implementation
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Apply security headers
const headers = securityHeaders()
Object.entries(headers).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
API Security Best Practices
// lib/api/security.ts
export class APISession {
// Rate limiting implementation
static async rateLimitCheck(
identifier: string,
limit: number,
windowMs: number
): Promise<boolean> {
const now = Date.now()
const windowStart = now - windowMs
// Clean up old entries
await supabase
.from('rate_limits')
.delete()
.lt('timestamp', new Date(windowStart).toISOString())
// Count recent requests
const { count } = await supabase
.from('rate_limits')
.select('*', { count: 'exact', head: true })
.eq('identifier', identifier)
.gte('timestamp', new Date(windowStart).toISOString())
if ((count || 0) >= limit) {
return false // Rate limit exceeded
}
// Record this request
await supabase
.from('rate_limits')
.insert({
identifier,
timestamp: new Date().toISOString()
})
return true
}
// API key validation
static async validateAPIKey(apiKey: string): Promise<boolean> {
if (!apiKey || typeof apiKey !== 'string') {
return false
}
// Check API key format
if (!/^sk_[a-zA-Z0-9]{32}$/.test(apiKey)) {
return false
}
// Verify API key exists and is active
const { data: key } = await supabase
.from('api_keys')
.select('*')
.eq('key_hash', await this.hashAPIKey(apiKey))
.eq('active', true)
.single()
return !!key
}
private static async hashAPIKey(apiKey: string): Promise<string> {
const crypto = require('crypto')
return crypto.createHash('sha256').update(apiKey).digest('hex')
}
// Request validation
static validateRequest(req: any): void {
// Check content type for POST/PUT requests
if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
const contentType = req.headers['content-type']
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Invalid content type')
}
}
// Validate request size
const contentLength = parseInt(req.headers['content-length'] || '0')
if (contentLength > 10 * 1024 * 1024) { // 10MB limit
throw new Error('Request too large')
}
// Check for required headers
const requiredHeaders = ['user-agent', 'authorization']
for (const header of requiredHeaders) {
if (!req.headers[header]) {
throw new Error(`Missing required header: ${header}`)
}
}
}
// Response sanitization
static sanitizeResponse(data: any): any {
if (typeof data !== 'object' || data === null) {
return data
}
const sensitiveFields = [
'password',
'token',
'secret',
'key',
'hash',
'salt',
'private'
]
const sanitized = { ...data }
for (const field of sensitiveFields) {
if (field in sanitized) {
delete sanitized[field]
}
}
return sanitized
}
}
Security Testing
Automated Security Testing
// tests/security/security.test.ts
describe('Security Tests', () => {
describe('Input Validation', () => {
test('should reject malicious SQL injection attempts', async () => {
const maliciousInputs = [
"'; DROP TABLE users; --",
"1' OR '1'='1",
"admin'/*",
"UNION SELECT * FROM users"
]
for (const input of maliciousInputs) {
await expect(async () => {
await validateInput(input, 'text')
}).rejects.toThrow()
}
})
test('should reject XSS attempts', async () => {
const xssPayloads = [
'<script>alert("xss")</script>',
'javascript:alert(1)',
'<img src=x onerror=alert(1)>',
'<svg onload=alert(1)>'
]
for (const payload of xssPayloads) {
await expect(async () => {
await validateInput(payload, 'text')
}).rejects.toThrow()
}
})
})
describe('Authentication', () => {
test('should require strong passwords', async () => {
const weakPasswords = [
'123456',
'password',
'qwerty',
'admin123',
'password123'
]
for (const password of weakPasswords) {
await expect(async () => {
await hashPassword(password)
}).rejects.toThrow('Password does not meet security requirements')
}
})
test('should validate session tokens', async () => {
const invalidTokens = [
'',
'invalid',
'123',
'not-a-valid-session-id'
]
for (const token of invalidTokens) {
const isValid = await validateSession(token)
expect(isValid).toBe(false)
}
})
})
describe('Rate Limiting', () => {
test('should enforce rate limits', async () => {
const identifier = 'test-user'
const limit = 5
const windowMs = 60000 // 1 minute
// Make requests up to the limit
for (let i = 0; i < limit; i++) {
const allowed = await rateLimitCheck(identifier, limit, windowMs)
expect(allowed).toBe(true)
}
// Next request should be blocked
const blocked = await rateLimitCheck(identifier, limit, windowMs)
expect(blocked).toBe(false)
})
})
})
// Security scanning integration
describe('Vulnerability Scanning', () => {
test('should run OWASP dependency check', async () => {
const { exec } = require('child_process')
const { promisify } = require('util')
const execAsync = promisify(exec)
try {
const { stdout, stderr } = await execAsync('npm audit --json')
const auditResults = JSON.parse(stdout)
// Check for high/critical vulnerabilities
expect(auditResults.metadata.vulnerabilities.high).toBe(0)
expect(auditResults.metadata.vulnerabilities.critical).toBe(0)
} catch (error) {
console.error('Security audit failed:', error)
throw error
}
})
})
Penetration Testing Checklist
// Security testing checklist
export const securityTestingChecklist = {
authentication: [
'Test password complexity requirements',
'Verify account lockout mechanisms',
'Test multi-factor authentication',
'Check session management',
'Verify logout functionality',
'Test password reset process'
],
authorization: [
'Test role-based access control',
'Verify privilege escalation prevention',
'Check resource-level permissions',
'Test API endpoint authorization',
'Verify admin panel security'
],
input_validation: [
'Test SQL injection prevention',
'Verify XSS protection',
'Check file upload security',
'Test command injection prevention',
'Verify input sanitization'
],
session_management: [
'Test session timeout',
'Verify session invalidation',
'Check concurrent session limits',
'Test session fixation prevention',
'Verify secure session storage'
],
data_protection: [
'Test data encryption at rest',
'Verify data encryption in transit',
'Check sensitive data handling',
'Test data masking',
'Verify secure data deletion'
],
error_handling: [
'Test error message sanitization',
'Verify stack trace prevention',
'Check logging security',
'Test exception handling',
'Verify error monitoring'
],
infrastructure: [
'Test network security',
'Verify server hardening',
'Check firewall configuration',
'Test SSL/TLS configuration',
'Verify security headers'
]
}
Security Monitoring
Continuous Security Monitoring
// lib/security/monitoring.ts
export class SecurityMonitoring {
static async initializeMonitoring() {
// Set up real-time security monitoring
setInterval(async () => {
await this.checkSecurityMetrics()
}, 60000) // Check every minute
// Set up daily security reports
setInterval(async () => {
await this.generateDailyReport()
}, 24 * 60 * 60 * 1000) // Daily
// Set up weekly vulnerability scans
setInterval(async () => {
await this.runVulnerabilityScans()
}, 7 * 24 * 60 * 60 * 1000) // Weekly
}
private static async checkSecurityMetrics() {
const metrics = {
failed_logins: await this.getFailedLoginCount(),
suspicious_ips: await this.getSuspiciousIPs(),
error_rate: await this.getErrorRate(),
security_events: await this.getSecurityEventCount()
}
// Alert on anomalies
if (metrics.failed_logins > 100) {
await this.sendSecurityAlert('High number of failed logins detected', 'high')
}
if (metrics.error_rate > 0.05) { // 5% error rate
await this.sendSecurityAlert('High error rate detected', 'medium')
}
return metrics
}
private static async runVulnerabilityScans() {
try {
// Run automated vulnerability scans
const { exec } = require('child_process')
const { promisify } = require('util')
const execAsync = promisify(exec)
// Run npm audit
const npmAudit = await execAsync('npm audit --json')
// Run OWASP ZAP if available
// const zapScan = await execAsync('zap-baseline.py -t http://localhost:3000')
// Process and report results
await this.processVulnerabilityResults(npmAudit.stdout)
} catch (error) {
console.error('Vulnerability scan failed:', error)
}
}
}
This comprehensive set of security best practices provides a solid foundation for building and maintaining secure applications while following industry standards and guidelines.