Vulnerability Remediation Patterns
Injection Remediation
SQL Injection
- Parameterized queries ALWAYS. No exceptions. No string concatenation for SQL.
- ORM usage preferred -- but verify the ORM parameterizes under the hood (raw queries in ORMs are still vulnerable).
- Stored procedures: use parameters, not dynamic SQL inside the procedure.
- If building dynamic column/table names: strict allowlist validation, never user input directly.
Command Injection
- Avoid shell execution entirely. Rewrite with language-native libraries (file ops, HTTP clients, etc.).
- If shell is unavoidable:
subprocesswith array args (['cmd', 'arg1', 'arg2']), NEVERshell=True. - Allowlist commands and arguments. Reject anything not on the list.
- Never pass user input to
eval(),exec(),os.system(), or backtick execution.
XSS (Cross-Site Scripting)
- Output encoding by context -- these are ALL different:
- HTML body: HTML entity encode (
<becomes<) - HTML attribute: attribute encode, always quote attribute values
- JavaScript context: JavaScript encode (hex escape)
- URL parameter: URL encode
- CSS value: CSS hex encode
- HTML body: HTML entity encode (
- CSP headers as defense in depth:
default-src 'self', avoidunsafe-inline/unsafe-eval. - DOM manipulation: use
textContent, NEVERinnerHTMLwith user data. - Frameworks with auto-escaping (React, Angular) help but do not prevent
dangerouslySetInnerHTMLor[innerHTML]bypasses.
Template Injection (SSTI)
- Never pass user input as the template source. User input goes into template variables, not the template itself.
- Use logic-less templates (Mustache, Handlebars) where possible.
- Sandbox template engines. Restrict available functions/objects in template context.
Path Traversal
- Canonicalize the path first (
os.path.realpath,Path.resolve()), THEN verify it starts with the allowed prefix. - Never trust user-supplied filenames. Generate filenames server-side, store original name as metadata.
- Reject paths containing
.., null bytes, or encoded variants.
Authentication Fixes
Password Storage
- bcrypt or argon2id with appropriate work factor. Target 250ms+ hash time.
- NEVER MD5, SHA-1, or SHA-256 for passwords (fast hashes enable brute force).
- Use the library's built-in salt generation -- do not roll your own.
Session Management
- Session IDs: cryptographically random, minimum 128 bits of entropy.
- Cookie flags:
HttpOnly(no JS access),Secure(HTTPS only),SameSite=LaxorStrict. - Server-side session expiry. Do not rely only on cookie expiry.
- Regenerate session ID on login (prevents session fixation).
Token Handling
- Short-lived access tokens (15 min or less) + refresh token rotation.
- Store the hash of refresh tokens, not plaintext.
- Invalidate all tokens on password change.
- JWTs: validate signature, issuer, audience, and expiry. Reject
alg: none.
MFA Implementation
- TOTP preferred over SMS (SIM swap risk).
- Backup codes: hash them like passwords. Single-use. Show only once.
- Rate limit verification attempts (5 attempts, then lockout with backoff).
- Do not reveal whether MFA is enabled during login flow (information leakage).
Password Reset
- Constant-time email lookup -- same response time whether account exists or not (prevents enumeration).
- Time-limited tokens (15-30 min). Single-use.
- Hash the stored token (if DB is compromised, tokens are useless).
- Invalidate all existing reset tokens when a new one is issued.
Authorization Fixes
BOLA (Broken Object Level Authorization)
- Verify ownership at the data layer, not just the route/middleware.
- Every query that fetches by ID must also filter by the authenticated user's scope.
- Pattern:
SELECT * FROM orders WHERE id = ? AND user_id = ?-- never justWHERE id = ?.
Privilege Escalation
- Role checks at the function/action level, not just the UI.
- Server-side enforcement is mandatory. Hiding a button is not access control.
- Verify role on every request. Do not trust a role claim from the client.
- Principle of least privilege: default deny, explicitly grant.
Mass Assignment
- Explicit allowlists for assignable fields. Never pass raw request body to model update.
- Example:
user.update(request.body)is vulnerable. Useuser.update(pick(request.body, ['name', 'email'])). - Separate DTOs for create vs update if different fields are assignable.
CORS
- Explicit origin allowlist. Never
Access-Control-Allow-Origin: *with credentials. - Validate the
Originheader server-side against the allowlist. - Do not reflect the request origin back as the allowed origin without checking.
- Restrict allowed methods and headers to what is actually needed.
Data Exposure Fixes
Error Handling
- Generic messages to users: "Something went wrong. Please try again."
- Detailed errors server-side only (structured logging with request ID for correlation).
- Never expose: stack traces, SQL errors, internal file paths, dependency versions.
- Custom error pages for 4xx/5xx. Framework defaults often leak info.
API Response Shaping
- Explicit serialization: return only the fields the client needs.
- Never dump model/ORM objects directly to JSON response.
- Different serializers for different roles (admin sees more fields than regular user).
- Paginate list endpoints. Never return unbounded result sets.
Logging Hygiene
- Scrub before logging: PII, tokens, passwords, credit card numbers.
- Structured logging with sensitivity tags so automated scrubbing can catch what you miss.
- Log access events (who accessed what, when) for audit trail.
- Do not log full request bodies -- they may contain secrets.
Security Headers
Strict-Transport-Security: max-age=31536000; includeSubDomains(HSTS)X-Content-Type-Options: nosniffX-Frame-Options: DENY(orSAMEORIGINif you need iframes)Content-Security-Policy: lock down script/style/image sources- Remove
Server,X-Powered-By, and other version-disclosing headers.
Cryptography Fixes
Core Rules
- Use standard libraries (libsodium, OpenSSL, Web Crypto API). Never roll your own.
- AES-256-GCM for symmetric encryption (authenticated encryption -- integrity + confidentiality).
- RSA-OAEP (2048+ bit) or ECDH (P-256+) for key exchange.
- TLS 1.2+ only. Disable TLS 1.0, 1.1, SSLv3.
Key Management
- Build in key rotation from day one. Hardcoded keys are a ticking time bomb.
- Secrets in environment variables or a vault (HashiCorp Vault, AWS Secrets Manager).
- Never commit secrets to version control. Use
.gitignoreand pre-commit hooks to prevent it. - Rotate keys immediately if any suspected compromise.
Common Mistakes
- Using ECB mode (patterns visible in ciphertext). Use GCM or CBC with HMAC.
- Reusing IVs/nonces. Every encryption operation needs a unique IV.
- Comparing hashes with
==instead of constant-time comparison (timing attacks). - Storing encryption keys next to the encrypted data.
Fix Verification Patterns
How to confirm a fix actually works:
Write the Exploit Test
- Create a test that reproduces the original vulnerability. It should fail before the fix and pass after.
- Example: a test that sends
' OR 1=1 --to the search endpoint and verifies it returns 0 results, not all results.
Test the Bypass
- Can the fix be circumvented with encoding changes? (
%27instead of', double encoding, Unicode normalization) - Can it be bypassed with case changes, null bytes, or alternate representations?
- Try the fix with the attacker mindset: "How would I get around this?"
Test Adjacent Functionality
- Did the fix break legitimate use cases? (Overly aggressive validation rejecting valid input)
- Test with international characters, long strings, and edge-case valid inputs.
Check for Variants
- If you found SQLi in
/search, check/filter,/export,/report-- same pattern, different endpoint. - Grep the codebase for the vulnerable pattern:
grep -r "query.*+.*request"or equivalent. - Fix the pattern, not just the instance.
Regression Protection
- Add the exploit test to CI. It must run on every build.
- If the test suite does not cover this attack vector, the fix WILL regress.
Remediation Anti-Patterns
Patterns that feel like fixes but are not:
- Blocklist over allowlist: blocking known-bad input (
DROP,<script>) instead of allowlisting known-good. Always bypassable with encoding, casing, or novel payloads. - Client-side only validation: the server must validate independently. Client validation is UX, not security.
- Security through obscurity: hiding the admin endpoint at
/x7f3admindoes not fix missing auth. Assume the attacker knows the URL. - Partial fix: fixing the reported instance but not the pattern. If one endpoint was vulnerable, grep for the same pattern everywhere.
- Over-patching: adding so many validation layers that legitimate usage breaks. Fix precisely -- one correct check at the right boundary.
- Fix and forget: no regression test means the vulnerability returns in the next refactor. If there is no test, the fix is incomplete.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.