The most cost-effective time to fix a security vulnerability is before the code is written. Security vulnerabilities found in production cost 100x more to fix than those caught in code review. This guide gives developers 10 concrete, actionable rules for writing more secure code — with real examples in Python, JavaScript, and other languages.
Rule 1: Never Trust User Input
# Treat ALL external input as potentially malicious:
# - HTTP request parameters
# - HTTP headers (User-Agent, Referer, Cookie, X-Forwarded-For)
# - File uploads
# - Database data (already stored data may have been injected)
# - Third-party API responses
# Validation should be:
# 1. Type validation: is this the expected data type?
# 2. Length validation: is it within acceptable bounds?
# 3. Format validation: does it match the expected pattern?
# 4. Allowlist validation: is it in the allowed set of values?
# Python example:
import re
from typing import Optional
def validate_username(username: str) -> Optional[str]:
if not isinstance(username, str):
return None
if len(username) < 3 or len(username) > 50:
return None
if not re.match(r'^[a-zA-Z0-9_]+$', username):
return None
return username
Rule 2: Use Parameterized Queries for All Database Operations
# Already covered in SQL injection guide, but worth repeating:
# NEVER use string concatenation for SQL queries
# BAD (all languages):
query = f"SELECT * FROM users WHERE id = {user_id}" # Python f-string
query = "SELECT * FROM users WHERE id = " + userId; # JavaScript concatenation
# GOOD (parameterized):
# Python psycopg2:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# JavaScript (mysql2):
connection.execute("SELECT * FROM users WHERE id = ?", [userId]);
# PHP PDO:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $userId]);
Rule 3: Output Encoding Prevents XSS
# Every piece of untrusted data displayed in HTML must be encoded
# BAD (Python/Jinja2 without auto-escaping):
return f"<p>Welcome, {username}!</p>" # XSS if username contains <script>
# GOOD: Use framework auto-escaping:
# Jinja2 - auto-escaping enabled:
# <p>Welcome, {{ username }}!</p> # {{ }} auto-escapes in Jinja2
# React - auto-escapes by default:
# return <p>Welcome, {username}!</p>; // Safe - React escapes
# If you MUST write raw HTML:
# ONLY do this after proper sanitization
from markupsafe import escape
safe_output = f"<p>Welcome, {escape(username)}!</p>"
Rule 4: Implement Proper Authentication
# Authentication checklist:
# 1. Hash passwords with bcrypt/Argon2 (never MD5, never plaintext)
# 2. Enforce strong password policies
# 3. Rate limit authentication attempts
# 4. Implement account lockout (but don't reveal if account exists)
# 5. Generate unpredictable session tokens (use crypto.randomBytes)
# 6. Set proper session cookie flags
# Session cookie settings (Express.js):
app.use(session({
secret: process.env.SESSION_SECRET, // 256-bit random secret from env var
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // JavaScript cannot access this cookie
secure: true, // Only sent over HTTPS
sameSite: 'strict' // Prevents CSRF
}
}));
Rule 5: Secure File Handling
# File upload vulnerabilities:
# - Upload webshell (malicious PHP/ASPX file executed by server)
# - Path traversal (upload to ../../etc/cron.d/)
# - Zip bombs (1 MB zip = 1 TB extracted)
# Secure file upload (Python):
import os
import magic # python-magic library
from pathlib import Path
ALLOWED_TYPES = {'image/jpeg', 'image/png', 'image/gif'}
MAX_SIZE = 5 * 1024 * 1024 # 5MB
UPLOAD_DIR = Path('/var/uploads') # Outside web root
def secure_upload(file_data: bytes, filename: str) -> str:
# 1. Verify file size:
if len(file_data) > MAX_SIZE:
raise ValueError("File too large")
# 2. Verify MIME type by content (not extension!):
mime_type = magic.from_buffer(file_data, mime=True)
if mime_type not in ALLOWED_TYPES:
raise ValueError(f"File type not allowed: {mime_type}")
# 3. Generate safe filename (ignore user-provided name):
import uuid
safe_name = str(uuid.uuid4()) + '.jpg' # Never use user's filename
# 4. Save outside web root:
dest = UPLOAD_DIR / safe_name
dest.write_bytes(file_data)
return safe_name
Rules 6-10: Quick Reference
# Rule 6: Use HTTPS everywhere, enforce HSTS:
# Flask:
from flask_talisman import Talisman
Talisman(app, force_https=True, strict_transport_security=True)
# Rule 7: Principle of least privilege in code:
# Database user for web app: SELECT, INSERT only (not DROP, ALTER)
# File system: read-only where possible
# API keys: scoped to minimum needed permissions
# Rule 8: Log security events (but not sensitive data):
import logging
logger = logging.getLogger('security')
logger.warning(f"Failed login for user: {username} from IP: {ip}")
# NEVER log: passwords, credit cards, SSNs, session tokens
# Rule 9: Keep dependencies updated:
# Python: pip install safety && safety check
# Node: npm audit && npm audit fix
# Set up Dependabot for automated alerts
# Rule 10: Use security headers:
# Content-Security-Policy: prevent XSS
# X-Frame-Options: DENY - prevent clickjacking
# X-Content-Type-Options: nosniff
# Strict-Transport-Security: enforce HTTPS
Wrap Up
Secure coding isn’t about learning exotic tricks — it’s about consistently applying a small set of principles: validate input, encode output, use parameterized queries, hash passwords properly, and keep dependencies updated. Add a security-focused code review checklist to your PR process and you’ll catch most vulnerabilities before they ever reach production.