100 lines
4.4 KiB
Markdown
100 lines
4.4 KiB
Markdown
---
|
|
name: code-sanitization
|
|
description: 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.
|