Secure Coding Practices Cheat Sheet
Language-agnostic patterns for input validation, output encoding, secrets handling, and common vulnerability prevention.
Parameterized Queries (SQL Injection)
Never concatenate user input into SQL; always bind parameters.
# BAD - vulnerable to SQL injectioncursor.execute(f"SELECT * FROM users WHERE email = '{email}'")# GOOD - parameterized, driver handles escapingcursor.execute("SELECT * FROM users WHERE email = %s", (email,))
Output Encoding (XSS)
Encode context-appropriately when injecting untrusted data into HTML.
// BAD - raw HTML injectionelement.innerHTML = userComment;// GOOD - text content is auto-escapedelement.textContent = userComment;// If HTML is required, sanitize with an allowlist-based libraryimport DOMPurify from "dompurify";element.innerHTML = DOMPurify.sanitize(userComment);
Secrets Handling
Never hardcode secrets; load them from a vault or the environment at runtime.
# BAD - committed to source control# api_key = "sk-live-abc123..."# GOOD - injected at runtime from a secrets managerexport DB_PASSWORD=$(aws secretsmanager get-secret-value \ --secret-id prod/db/password --query SecretString --output text)# Add a pre-commit hook to catch accidental leakspip install detect-secrets && detect-secrets scan > .secrets.baseline
Secure Coding Checklist
The recurring vulnerability classes to design against from day one.
- Injection- parameterize SQL/NoSQL/shell commands, never string-build queries
- Broken auth- use vetted libraries for hashing (argon2/bcrypt) and session mgmt
- Deserialization- never deserialize untrusted data with pickle/yaml.load/eval
- Path traversal- resolve and validate file paths against an allowlisted base dir
- SSRF- validate/allowlist outbound URLs before the server fetches them
- Least privilege- run processes and DB users with the minimum grants needed
- Error handling- log stack traces internally, return generic errors to clients
Timing-Safe Secret Comparison
Use constant-time comparison for tokens/HMACs so response timing can't leak byte-by-byte matches.
import hmac# BAD - short-circuits on first mismatched byte, timing leaks partial matchesif user_supplied_token == stored_token: grant_access()# GOOD - constant-time comparison regardless of where the mismatch occursif hmac.compare_digest(user_supplied_token, stored_token): grant_access()
Authenticated Encryption (AES-GCM)
Prefer an AEAD cipher over raw AES-CBC/ECB so tampering is detected, not just confidentiality preserved.
from cryptography.hazmat.primitives.ciphers.aead import AESGCMimport oskey = AESGCM.generate_key(bit_length=256)aesgcm = AESGCM(key)nonce = os.urandom(12) # never reuse a nonce with the same keyciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=b"user:1234")# AAD binds context (e.g. user id) without encrypting it - tampering with# either ciphertext or AAD makes decrypt() raise InvalidTagplaintext = aesgcm.decrypt(nonce, ciphertext, associated_data=b"user:1234")
SSRF Mitigation: Resolve & Check the IP
Validate the resolved IP, not just the hostname, so DNS rebinding and internal aliases can't bypass the check.
import ipaddress, socketfrom urllib.parse import urlparseBLOCKED_NETWORKS = [ipaddress.ip_network(n) for n in ( "127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", # includes cloud metadata endpoint)]def is_safe_url(url): host = urlparse(url).hostname ip = ipaddress.ip_address(socket.gethostbyname(host)) return not any(ip in net for net in BLOCKED_NETWORKS)
Prevent Mass Assignment
Bind only an explicit allowlist of client-writable fields, never the raw request body.
// BAD - blindly spreads client input into the updateawait User.update(req.body, { where: { id: req.user.id } });// GOOD - allowlist exactly which fields a user may changeconst { displayName, timezone } = req.body;await User.update({ displayName, timezone }, { where: { id: req.user.id } });// role, isAdmin, balance, etc. are never reachable from client input
Crypto & Auth Pitfalls
Mistakes that pass every functional test but break the security property they exist for.
- Nonce reuse- reusing an AES-GCM nonce with the same key fully breaks confidentiality
- ECB mode- deterministic block encryption leaks patterns; always use an AEAD or CBC+HMAC mode
- Weak randomness- `Math.random()`/`random.random()` for tokens or keys is predictable, not cryptographic
- Predictable IDs- sequential IDs enable enumeration attacks even with proper authz elsewhere
- Client-side trust- price, role, or permission fields must be recomputed server-side, never trusted from the client
- Verbose errors- stack traces and ORM errors returned to clients leak schema and internal paths
Push validation and encoding to the framework/library level (ORM parameter binding, templating auto-escape, a schema validator) instead of relying on developers to remember it per call site — the vulnerability classes that survive years in production are almost always the ones left to manual discipline.