4.4 KiB
name, description
| name | description |
|---|---|
| code-sanitization | Use when writing any code that handles external input (user input, API responses, file uploads, database queries, shell commands). Framework-agnostic injection-prevention baseline. |
Code Sanitization
Framework-agnostic baseline for handling untrusted input safely. Applies regardless of language — the specific function names differ, the principle doesn't.
The core rule: sanitize on input, escape on output, separately
These are two different concerns, both required:
- Sanitize/validate on input: reject or normalize data that doesn't match the expected shape, as early as possible (at the point you first receive it).
- Escape on output: transform data for the specific context it's being placed into (HTML, a SQL query, a shell command, a URL, JSON) — every different output context needs its OWN escaping, applied at the point of output, not once globally.
A common real mistake: sanitizing once on input and assuming that's sufficient for every later output context. It isn't — data that's safe to store isn't automatically safe to interpolate into HTML, and separately isn't automatically safe to interpolate into a shell command.
SQL injection
Parameterized queries / prepared statements, always, no exceptions. Never build a SQL string via concatenation or interpolation with a variable in it, even if you're "sure" the variable is safe (a value that was safe when the code was written is not guaranteed to stay safe as the codebase evolves and new call sites appear).
Cross-site scripting (XSS)
Escape for the exact context:
- HTML body text: HTML-entity-escape (
<,>,&,",'). - HTML attribute: attribute-escape (stricter than body-text escaping).
- JavaScript string embedded in a
<script>block: JS-string-escape (NOT the same as HTML-escaping — a common real mistake). - URL parameter: URL-encode. Use the templating/framework's built-in auto-escaping wherever available rather than hand-rolling escaping — auto-escaping systems have already solved the context-detection problem correctly; hand-rolled escaping is an easy place to get a context wrong.
Command injection
Never build a shell command string by concatenating untrusted input. Use an API that passes arguments as a real array/list (avoiding shell interpretation entirely) rather than a single command string, wherever the language/runtime offers that option. If a single command string is genuinely unavoidable, every untrusted component needs proper shell- escaping for the target shell — and even then, prefer the array-argument API if it exists.
See the remote-shell-quoting-safety skill for the specific, separate
problem of content getting corrupted (not maliciously, just broken) when
passing through multiple nested shell layers — a different concern from
injection, but often encountered in the same code paths.
Path traversal
Never build a filesystem path by directly concatenating user input.
Validate the resolved absolute path stays within the intended base
directory (resolve ../symlinks and check the result, don't just
regex-reject .. in the raw input — that's bypassable). Prefer an
allowlist of permitted filenames/paths over trying to blocklist
dangerous patterns.
File uploads
- Validate actual file content/type (magic bytes), not just the extension or the client-supplied MIME type — both are trivially spoofable.
- Store uploads outside the web-servable document root, or with execution disabled for that directory, so an uploaded file can never be directly requested and executed as code even if a validation gap lets something malicious through.
- Enforce a size limit server-side, not just client-side.
Deserialization
Never deserialize untrusted data with a format/library that can
instantiate arbitrary objects or execute code as a side effect of
deserializing (e.g. PHP's unserialize() on untrusted input, Python's
pickle.loads() on untrusted input). Use a safe, data-only format (JSON)
for anything touching untrusted input.
General principle
When in doubt about whether input is "trusted," treat it as untrusted. Data crossing any trust boundary (a network request, a file upload, a database read of data that was itself written by a less-trusted component, an environment variable in a multi-tenant context) should be validated/escaped as if it were directly attacker-controlled, because in a surprising number of real incidents, eventually it was.