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 = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      "'": '&#39;',
      '"': '&quot;'
    };
    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

  1. Never trust user input - Always validate and sanitize
  2. Use parameterized queries - Prevent SQL injection
  3. Implement proper authentication - Use strong passwords and sessions
  4. Keep dependencies updated - Regular security updates
  5. Use HTTPS everywhere - Encrypt data in transit
  6. Log security events - Monitor for suspicious activity
  7. Follow principle of least privilege - Minimal necessary permissions
  8. 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.