# BAD — accepts any algorithm
decoded = jwt.decode(token, secret) # GOOD — explicitly specify allowed algorithms
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
# BAD — accepts any algorithm
decoded = jwt.decode(token, secret) # GOOD — explicitly specify allowed algorithms
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
# BAD — accepts any algorithm
decoded = jwt.decode(token, secret) # GOOD — explicitly specify allowed algorithms
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
GET /api/users/123/invoices → returns YOUR invoices
GET /api/users/456/invoices → returns SOMEONE ELSE'S invoices
GET /api/users/123/invoices → returns YOUR invoices
GET /api/users/456/invoices → returns SOMEONE ELSE'S invoices
GET /api/users/123/invoices → returns YOUR invoices
GET /api/users/456/invoices → returns SOMEONE ELSE'S invoices
# Nginx
client_max_body_size 1m; # Express.js
app.use(express.json({ limit: '1mb' }));
# Nginx
client_max_body_size 1m; # Express.js
app.use(express.json({ limit: '1mb' }));
# Nginx
client_max_body_size 1m; # Express.js
app.use(express.json({ limit: '1mb' }));
{ "email": { "type": "string", "format": "email", "maxLength": 254 }, "age": { "type": "integer", "minimum": 0, "maximum": 150 }, "role": { "type": "string", "enum": ["user", "admin"] }
}
{ "email": { "type": "string", "format": "email", "maxLength": 254 }, "age": { "type": "integer", "minimum": 0, "maximum": 150 }, "role": { "type": "string", "enum": ["user", "admin"] }
}
{ "email": { "type": "string", "format": "email", "maxLength": 254 }, "age": { "type": "integer", "minimum": 0, "maximum": 150 }, "role": { "type": "string", "enum": ["user", "admin"] }
}
# BAD — SQL injection
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # GOOD — parameterized
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# BAD — SQL injection
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # GOOD — parameterized
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# BAD — SQL injection
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # GOOD — parameterized
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
{ "error": "internal_server_error", "message": "An unexpected error occurred", "request_id": "req_abc123"
}
{ "error": "internal_server_error", "message": "An unexpected error occurred", "request_id": "req_abc123"
}
{ "error": "internal_server_error", "message": "An unexpected error occurred", "request_id": "req_abc123"
}
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'none'
Cache-Control: no-store
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'none'
Cache-Control: no-store
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'none'
Cache-Control: no-store
# GitHub Actions example
- name: Security scan run: | -weight: 500;">pip -weight: 500;">install safety && safety check -weight: 500;">npm audit --audit-level=high trivy fs --severity HIGH,CRITICAL .
# GitHub Actions example
- name: Security scan run: | -weight: 500;">pip -weight: 500;">install safety && safety check -weight: 500;">npm audit --audit-level=high trivy fs --severity HIGH,CRITICAL .
# GitHub Actions example
- name: Security scan run: | -weight: 500;">pip -weight: 500;">install safety && safety check -weight: 500;">npm audit --audit-level=high trivy fs --severity HIGH,CRITICAL . - Signature using the correct algorithm (RS256, not HS256 with a guessable secret)
- Expiration (exp claim) — tokens should expire in minutes, not days
- Issuer (iss claim) — reject tokens from unknown issuers
- Audience (aud claim) — reject tokens meant for other services - API key or user ID (primary)
- IP address (secondary)
- Combination with sliding window counters - Score your API: Go through all 15 points. Mark pass/fail for each. Be honest — the only person you're fooling is yourself.
- Fix critical first: Items 1-6 are critical. If you fail any of these, -weight: 500;">stop everything and fix them now. These are the vulnerabilities that lead to data breaches.
- Automate checks: Add items 4, 5, 6, 13, and 15 to your CI/CD pipeline so they can't regress. Security that depends on humans remembering to check is security that will fail.
- Schedule quarterly audits: Run through the full checklist every quarter. New code introduces new attack surface. New dependencies introduce new vulnerabilities.
- Test adversarially: Don't just check the box — try to break your own API. Use tools like OWASP ZAP, Burp Suite, or sqlmap against your staging environment. If you're not attacking your own API, someone else will.