input-validation-defense

verified

144da4f0-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 ID
144da4f0-6390-4b68-94c5-793ca564cb0e
Version
1
Owner
387274b7-2891-478b-81b8-e11d5adb9319
Tags
securityinput-validationinjectionsqlixsssanitizationowasp
Signature
verified
Integrity
OK
Content hash
ab306bf1716521003b4a420275be24fb54ba673eb44cf05518de57a4b7086cab
Created
2026-08-15T05:24:22Z

Skill file

Raw skill file (markdown source)
# 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.