Output encoding¶
The same string needs different encoding depending on where it is placed. A value safe in HTML is not necessarily safe in a JavaScript string literal, a URL query parameter, a SQL query, or a shell command. Each context has its own syntax and its own injection risk.
HTML context¶
Template auto-escaping converts <, >, ", ', and & to their HTML entity
equivalents. Django, Jinja2, Handlebars, and most modern templating engines do this by
default. The risk is explicitly bypassing it:
<!-- safe: auto-escaped in Django and Jinja2 -->
<p>{{ user_comment }}</p>
<!-- unsafe: |safe disables escaping -->
<p>{{ user_comment|safe }}</p>
|safe (Django) or |safe (Jinja2) marks a value as pre-escaped. Using it on
user-controlled content introduces XSS. Reserve it for HTML generated by the application
itself.
JavaScript string context¶
A value embedded in a JavaScript string literal needs different escaping from HTML. Quotes, backslashes, and line terminators break out of the string:
<!-- unsafe: if user_value contains quotes or </script>, it breaks out -->
<script>var x = "{{ user_value }}";</script>
<!-- safe: Django's |escapejs escapes for JavaScript string context -->
<script>var x = "{{ user_value|escapejs }}";</script>
The safest approach is to pass values from the server to the page via a data attribute or a JSON endpoint rather than inlining them in script tags.
URL context¶
User-controlled values in URL query strings or path segments need percent-encoding:
from urllib.parse import quote, urlencode
# single value in a path segment
path = f"/files/{quote(filename, safe='')}"
# multiple values in a query string
params = urlencode({"q": search_term, "page": page_number})
url = f"https://example.com/search?{params}"
safe='' in quote() encodes / and other characters that would otherwise be treated as
path separators.
In JavaScript:
const encoded = encodeURIComponent(userValue);
const url = `https://example.com/files/${encoded}`;
encodeURIComponent() encodes everything except unreserved characters. encodeURI() leaves
URL structural characters (:, /, ?, #, &) unencoded and is not appropriate for
individual parameter values.
SQL context¶
Parameterised queries are the encoding mechanism for SQL. See parameterised queries for examples across Python, Java, and Go. String interpolation into SQL is not a valid alternative.
Shell context¶
Constructing shell commands from user input is the source of command injection. The
preferred solution is not encoding but restructuring: pass arguments as a list to
subprocess.run() (Python) or execFile() (Node.js) to avoid shell invocation entirely.
When a shell is unavoidable, shlex.quote() in Python encloses the value in single quotes
and escapes any single quotes within it:
import shlex, subprocess
safe_arg = shlex.quote(user_value)
subprocess.run(f"grep {safe_arg} /var/log/app.log", shell=True)
There is no reliable equivalent in Node.js for arbitrary shell arguments. The answer in
Node.js is execFile().