input-validation-defense
verified144da4f0-6390-4b68-94c5-793ca564cb0e
Validate and sanitize untrusted input to prevent injection (SQLi, XSS, command injection, path traversal). Use on any code that accepts user or external input.
Metadata
Skill file
# Input Validation Defense
Use when writing or reviewing code that accepts user input, external API data, file uploads, or any data from an untrusted source — to prevent injection attacks.
## The Injection Matrix
### SQL Injection (SQLi)
```python
# ❌ VULNERABLE — string formatting
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# ✅ FIXED — parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
```
### Cross-Site Scripting (XSS)
```html
<!-- ❌ VULNERABLE — raw output -->
<div>{{ user_content }}</div>
<!-- ✅ FIXED — context-aware escaping -->
<div>{{ user_content | e }}</div> <!-- Jinja2 autoescape -->
```
```jsx
// ❌ VULNERABLE — dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{__html: userInput}} />
// ✅ FIXED — text content only
<div>{userInput}</div>
```
### Command Injection
```python
# ❌ VULNERABLE — shell=True with user input
subprocess.run(f"convert {user_filename} output.pdf", shell=True)
# ✅ FIXED — list args, no shell
subprocess.run(["convert", user_filename, "output.pdf"])
```
### Path Traversal
```python
# ❌ VULNERABLE — no path sanitization
file_path = os.path.join("/var/data", user_input)
# ✅ FIXED — canonicalize and verify
safe_path = os.path.realpath(os.path.join("/var/data", user_input))
if not safe_path.startswith(os.path.realpath("/var/data")):
raise ValueError("Path traversal detected")
```
### XXE (XML External Entity)
```python
# ✅ FIXED — disable external entities
import defusedxml.ElementTree as ET
tree = ET.parse(user_xml) # defusedxml is safe by default
```
## Defense-in-Depth Strategy
| Layer | Technique | Example |
|---|---|---|
| 1. Validate at boundary | Schema/type/allowlist validation | Pydantic, JSON Schema, regex allowlist |
| 2. Parameterize | Use parameterized queries, not string building | Prepared statements, ORM safe methods |
| 3. Encode at output | Context-aware escaping (HTML, JS, URL, SQL) | Template autoescaping, `encodeURIComponent` |
| 4. Restrict capabilities | Least privilege DB user, no shell, chroot | DB user with SELECT-only where possible |
| 5. Canonicalize | Resolve paths, normalize Unicode | `os.path.realpath`, NFC normalization |
## Validation vs Sanitization
| | Validation | Sanitization |
|---|---|---|
| **When** | At input boundary | At output boundary |
| **What** | Reject invalid; accept only known-good | Encode/escape for the output context |
| **Rule** | Allowlist over blocklist | Always encode, never strip silently |
### Concrete Validation Patterns
```python
# Allowlist validation (preferred)
import re
ALLOWED_USERNAME = re.compile(r'^[a-zA-Z0-9_-]{3,32}$')
if not ALLOWED_USERNAME.match(username):
raise ValueError("Invalid username")
# Pydantic schema validation (preferred for APIs)
from pydantic import BaseModel, Field, validator
class UserInput(BaseModel):
email: str = Field(max_length=254)
age: int = Field(ge=0, le=150)
@validator('email')
def validate_email(cls, v):
if '@' not in v:
raise ValueError('Invalid email')
return v.lower()
```
## Guardrails
- **Never** trust client-side validation alone — it's bypassable with curl.
- **Never** use blocklists for injection defense — attackers find bypasses (e.g., `UNI/**/ON` for SQLi).
- **Never** "sanitize" SQL inputs with string replacement — use parameterized queries only.
- **Never** pass user input directly to `eval()`, `exec()`, `os.system()`, or template engines without strict sandboxing.
- **Always** validate on the server side, even if the client also validates.
## Pitfalls
- **Blacklisting specific strings**: Attackers bypass with encoding tricks (`OR 1=1` → `OORR 1=1` after naive filter).
- **Sanitizing instead of parameterizing for SQL**: Even "good" sanitizers miss edge cases. Parameterized queries are the only correct fix.
- **Trusting `Content-Type` headers**: Validate actual content, not the claimed type.
- **Silently truncating or stripping bad input**: Can create worse bugs. Reject explicitly.
- **Forgetting to validate on the read path**: Input injected via DB and rendered later still needs output encoding.
## Verify / Checklist
- [ ] Every user/external input has a schema/allowlist validation at the boundary
- [ ] All SQL uses parameterized queries (grep for string formatting in execute/raw calls)
- [ ] All HTML templates use autoescaping (verify in framework config)
- [ ] All shell commands use list args, never `shell=True` with user input
- [ ] All file paths are canonicalized and checked with `startswith()` guard
- [ ] No `eval()`, `exec()`, or dynamic code execution on user input
- [ ] Integration test for each injection vector (send malicious input, verify rejection or safe rendering)
Attached files
No attached files.