Security Review
Overview
Security vulnerabilities are not bugs to fix later - they are design failures to prevent now. Every line of code that touches untrusted data is a potential attack vector.
Core principle: ASSUME ALL INPUT IS HOSTILE. Validate, sanitize, and escape at every boundary.
The cost equation: 1 hour of security review saves 100 hours of incident response.
When to Use
ALWAYS use this skill when:
- •Handling ANY user input (forms, URLs, headers, files)
- •Implementing authentication or authorization
- •Working with databases or data storage
- •Making external API calls
- •Processing files or file paths
- •Handling secrets, tokens, or credentials
- •Implementing cryptographic operations
- •Working with session management
- •Before committing security-sensitive code
- •During code review
Use ESPECIALLY when:
- •Building public-facing features
- •Handling payment or financial data
- •Processing personal/sensitive information
- •Implementing admin functionality
- •Creating APIs consumed by others
- •Working in unfamiliar security domains
The Security Mindset
NEVER TRUST: ├── User input (forms, URLs, headers, cookies) ├── Data from external APIs ├── File contents or filenames ├── Database values (could be previously injected) ├── Environment variables (in shared environments) ├── URL parameters and fragments └── Anything not generated by YOUR code in THIS request
Think like an attacker:
- •"What if I send 10MB of data here?"
- •"What if this string contains SQL/HTML/shell commands?"
- •"What if I access this without authentication?"
- •"What if I tamper with this client-side value?"
- •"What if I call this API endpoint directly?"
The Three Phases
Phase 1: Threat Assessment
Before writing code, identify threats:
- •
Data Flow Analysis
codeFor each feature, map: ├── INPUT: Where does data enter? │ └── User form? API? File upload? URL? ├── PROCESS: What operations occur? │ └── Database query? File operation? Shell command? ├── OUTPUT: Where does data go? │ └── HTML page? API response? Log file? └── STORAGE: What persists? └── Database? Session? Cookie? File? - •
Attack Surface Identification
codeList every: ├── Public endpoint ├── Input field ├── File upload ├── API parameter ├── Authentication point └── Authorization check
- •
STRIDE Threat Modeling
Threat Question Example Spoofing Can attacker pretend to be someone else? Session hijacking Tampering Can attacker modify data? Parameter manipulation Repudiation Can attacker deny actions? Missing audit logs Information Disclosure Can attacker access secrets? Error message leaks Denial of Service Can attacker crash/slow system? Resource exhaustion Elevation of Privilege Can attacker gain higher access? Broken access control
See: references/threat-modeling.md
Phase 2: Secure Implementation
Apply defense-in-depth at every layer:
Input Validation (First Line of Defense)
VALIDATE EARLY, VALIDATE STRICTLY: 1. Type checking - Is it the expected type? 2. Length limits - Is it within acceptable bounds? 3. Format validation - Does it match expected pattern? 4. Range checking - Is numeric value in valid range? 5. Allowlist - Is it in the set of allowed values?
Validation patterns:
// GOOD: Strict validation with allowlist
const ALLOWED_ROLES = ['user', 'editor', 'admin'] as const;
type Role = typeof ALLOWED_ROLES[number];
function setUserRole(role: string): Role {
if (!ALLOWED_ROLES.includes(role as Role)) {
throw new ValidationError('Invalid role');
}
return role as Role;
}
Output Encoding (Prevent Injection)
ENCODE FOR CONTEXT: HTML body → HTML entity encoding (< > &) HTML attr → Attribute encoding (additional chars) JavaScript → JavaScript encoding (\xNN) URL → URL encoding (%XX) CSS → CSS encoding SQL → Parameterized queries (NEVER string concat) Shell → Avoid entirely, or use execFile with args array
Context-aware encoding:
// HTML context - use textContent or sanitize with DOMPurify
const safeText = document.createTextNode(userInput);
element.appendChild(safeText);
// URL context - encode for URL
const userUrl = encodeURIComponent(userInput);
const url = `/search?q=${userUrl}`;
// SQL context - ALWAYS parameterize
const result = await db.query(
'SELECT * FROM users WHERE id = $1',
[userId] // Parameterized, never concatenated
);
Authentication Security
AUTHENTICATION CHECKLIST: □ Passwords hashed with bcrypt/argon2 (cost factor >= 10) □ No password in logs, errors, or responses □ Rate limiting on login attempts □ Account lockout after N failures □ Secure session generation (crypto random) □ Session invalidation on logout □ Session timeout (idle and absolute) □ HTTPS only for credentials □ No credentials in URL parameters □ Multi-factor where possible
Authorization Security
AUTHORIZATION CHECKLIST: □ Check permissions on EVERY request □ Deny by default, explicitly allow □ Verify ownership for resource access □ Re-validate on sensitive operations □ No client-side only authorization □ Audit log authorization failures □ Principle of least privilege □ Time-limited access tokens
Authorization patterns:
// GOOD: Explicit ownership check
async function getDocument(userId: string, docId: string) {
const doc = await db.documents.findById(docId);
if (!doc) throw new NotFoundError();
if (doc.ownerId !== userId && !doc.sharedWith.includes(userId)) {
throw new ForbiddenError(); // Don't reveal existence
}
return doc;
}
See: references/secure-coding-patterns.md
Phase 3: Verification
Verify security controls are effective:
- •
Manual Testing
codeFor each input: □ Test with malicious payloads (XSS, SQLi, etc.) □ Test boundary conditions (empty, max length, overflow) □ Test type confusion (array vs string, int vs float) □ Test encoding bypass attempts
- •
Automated Scanning
codeRun before commit: □ SAST (Static Analysis) - semgrep, CodeQL, bandit □ Dependency audit - npm audit, pip-audit □ Secret scanning - gitleaks, trufflehog
- •
Security Unit Tests
typescriptdescribe('Input Validation', () => { it('rejects SQL injection attempts', () => { const malicious = "'; DROP TABLE users; --"; expect(() => validateUsername(malicious)).toThrow(); }); it('rejects XSS payloads', () => { const malicious = '<script>alert(1)</script>'; expect(() => validateDisplayName(malicious)).toThrow(); }); it('enforces length limits', () => { const tooLong = 'a'.repeat(10001); expect(() => validateBio(tooLong)).toThrow(); }); });
OWASP Top 10 Quick Reference
| # | Vulnerability | Prevention |
|---|---|---|
| 1 | Broken Access Control | Deny by default, verify ownership, server-side checks |
| 2 | Cryptographic Failures | TLS everywhere, strong algorithms, no hardcoded secrets |
| 3 | Injection | Parameterized queries, input validation, output encoding |
| 4 | Insecure Design | Threat modeling, secure design patterns, defense in depth |
| 5 | Security Misconfiguration | Secure defaults, minimal permissions, disable unused features |
| 6 | Vulnerable Components | Keep updated, audit dependencies, monitor CVEs |
| 7 | Auth Failures | MFA, strong passwords, rate limiting, secure sessions |
| 8 | Data Integrity Failures | Verify signatures, validate sources, CI/CD security |
| 9 | Logging Failures | Log security events, protect logs, monitor alerts |
| 10 | SSRF | Allowlist URLs, validate/sanitize, no raw user URLs |
See: references/owasp-top-10.md
Security Checklists
Pre-Code Checklist
Before writing security-sensitive code:
- • What data is untrusted? (List all sources)
- • What's the worst that could happen? (Impact assessment)
- • How will I validate input? (Specific strategy)
- • How will I encode output? (Context-specific)
- • What authentication is required?
- • What authorization checks are needed?
- • What should be logged? What should NOT be logged?
- • What secrets are involved? How are they stored?
During-Code Checklist
While coding, continuously verify:
- • Is every user input validated?
- • Is output encoded for its context?
- • Are database queries parameterized?
- • Are authorization checks on the server?
- • Are secrets out of code? (env vars, secret managers)
- • Are errors safe? (No stack traces to users)
- • Is sensitive data encrypted at rest and in transit?
- • Are dependencies up-to-date and audited?
Post-Code Checklist
After implementation:
- • Run SAST scanner (semgrep, CodeQL)
- • Run dependency audit
- • Run secret scanner
- • Test with malicious inputs manually
- • Verify error messages don't leak info
- • Check logs don't contain secrets
- • Review authentication flow end-to-end
- • Verify authorization at every endpoint
- • Test as different user roles
- • Document security controls for reviewers
Red Flags - STOP and Fix
Immediate security issues (see references for details):
- •SQL string concatenation with user input
- •Setting innerHTML with unsanitized user data
- •Dynamic code execution with untrusted data
- •Shell command construction with string interpolation
- •Hardcoded credentials, API keys, or secrets
- •Missing authentication on sensitive endpoints
- •Client-side only authorization
- •Disabled security features (CSRF, CSP, etc.)
- •Sensitive data in URL parameters
- •Passwords stored in plaintext or weak hash
Design smells:
- •"We'll add security later"
- •"Users won't do that"
- •"It's internal, so it's safe"
- •"The framework handles that"
- •"We trust this third-party data"
Language-Specific Guidance
JavaScript/TypeScript
XSS Prevention:
- •Use
textContentinstead ofinnerHTMLfor user data - •Use DOMPurify to sanitize if HTML is required
- •Use
Object.hasOwn()for property checks on user-controlled keys
Prototype Pollution Prevention:
- •Validate JSON structure before merging
- •Use
Object.freeze()on sensitive objects - •Avoid recursive merge with untrusted data
Python
SQL Injection Prevention:
# ALWAYS use parameterized queries
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
Command Execution:
# Use subprocess with array args, shell=False subprocess.run(["ls", user_path], shell=False)
Path Traversal Prevention:
# Use basename to strip directory components
safe_path = os.path.join("/data", os.path.basename(user_filename))
SQL
-- Always use parameterized queries via your ORM or driver -- Never concatenate user input into query strings -- Limit SELECT to needed fields only
Integration with Other Skills
Use alongside:
- •systematic-debugging - When investigating security issues
- •solid - Security patterns often follow SOLID principles
- •test-driven-development - Write security tests first
- •verification-before-completion - Security verification before claiming done
Security is not a feature - it's a property of ALL features.
Quick Reference Card
INPUT: Validate type, length, format, range, allowlist OUTPUT: Encode for context (HTML, URL, SQL, Shell) AUTH: Strong passwords, MFA, rate limiting AUTHZ: Deny default, check ownership, server-side SECRETS: Never in code, use secret managers ERRORS: Safe messages, no stack traces, log securely DEPENDS: Audit, update, monitor CVEs ENCRYPT: TLS in transit, AES at rest, strong algorithms
Remember
"Security is not a product, but a process." - Bruce Schneier
"Attackers think in graphs. Defenders think in lists." - John Lambert
Your code will be attacked. Write it assuming the attacker is skilled, patient, and motivated.