On this page
What Is Server-Side Template Injection (SSTI)?
Template injection happens when user input is rendered as template code rather than as data, letting an attacker run expressions and often reach remote code execution.
Quick answer
- SSTI occurs when user input is concatenated into a template and then rendered, so the template engine evaluates it as template syntax instead of treating it as text.
- It is subtler than cross-site scripting: the attacker’s code runs on the server and affects other users, not just the one who submitted it.
- Impact ranges from data exposure to remote code execution, because template engines can reach language features through their expression syntax.
- The fix is structural — render a template that is fixed in code and pass user input as a variable — not a filter or a blocklist on characters.
- Generated code is exposed when it builds template strings from request data, especially in email templates and previews.
What is SSTI?
Template engines take a template with placeholders and a set of variables, and produce output. The vulnerability arises when the template itself — not just the variables — contains user input. If a request parameter is concatenated into the template source and then rendered, the engine parses that input as template syntax, so expressions the attacker wrote are evaluated with the application’s privileges.
This is a different bug from escaping output, which is the cross-site scripting fix. Escaping prevents injected markup from executing in a browser; it does nothing about injected template syntax, because the dangerous evaluation happens on the server before any HTML exists. That distinction is why an application with correct output escaping can still be fully exploitable through SSTI.
Why the impact is usually severe
Template engines are not sandboxes. Their expression languages are designed to be useful, which means they can reach objects, call methods, and access module internals through chains of attributes. Attackers use documented techniques to walk from a value they control to a function that executes a command or reads a file, which is why SSTI commonly escalates to remote code execution rather than stopping at data disclosure.
The exposure is also not limited to the attacker’s own session. Server-side rendering means the injected code runs once, on the server, with access to the context the template has — including other users’ data if the template renders it. A single crafted request can therefore read or modify data across accounts, which makes this a high-severity class rather than a self-inflicted one.
# VULNERABLE: user input becomes template source.
from jinja2 import Template
def render_greeting(name: str) -> str:
return Template(f'Hello {name}!').render()
# A request with name="{{ 7*7 }}" renders "Hello 49!" — proof of evaluation.
# Real payloads chain attributes to reach functions; assume code execution.
# SAFE: the template is fixed; user input is data.
TEMPLATE = Template('Hello {{ name }}!')
def render_greeting_safe(name: str) -> str:
return TEMPLATE.render(name=name)How to fix it, and what does not fix it
The fix is to make the template a constant in your code and pass user input as a context variable, so the engine only ever substitutes values. Where a template genuinely must be user-editable — a CMS block, an email template — use a sandboxed rendering mode if the engine offers one, restrict the template to a validated allow-list of constructs, and render it in a process without access to secrets or the database. Allow-listing constructs is far safer than block-listing dangerous ones, because the expression languages are large.
What does not fix it: escaping the characters that look like template syntax, since escaping is a best-effort exercise against a language you did not design; validating the input as “safe” text, which cannot distinguish template code from ordinary text; and relying on the input being short. None of these are controls. The structural change is the control, and if the requirement is genuinely to let users author templates, that is a product decision to make with the sandbox and the isolation designed in.
Where to look in a codebase
Search for places where a template is built with string concatenation or interpolation of request data — f-strings and template literals passed into a render call are the clearest signal. Then look at the features most likely to do it: email and notification templates, subject-line construction, “preview” endpoints, custom error pages, and any feature that lets a user save a message with placeholders.
Client-side template injection is the related case worth checking in the same pass. A front-end framework that compiles a template from user input has the same bug in the browser, where the impact is usually cross-site scripting rather than code execution. The shape of the fix is identical: keep the template fixed and pass data.
Where this bites vibecoders
AI-generated code reaches for string interpolation whenever a feature involves a greet-message, an email subject, or a preview, and it frequently passes the result to a renderer. The dangerous version looks clean: a single line, no obvious input handling. Asking “is any part of this template string derived from user input?” is the entire review, and the fix is usually to move a variable into the render call rather than to change the feature.
Where AI coding assistants get this wrong
- Building a template by interpolating request data into the template string.
- Assuming output escaping also protects against server-side template injection.
- Block-listing template characters instead of restructuring to a fixed template.
- Rendering user-editable templates without a sandbox or an allow-list.
- Exposing a preview endpoint that renders arbitrary user input.
- Treating client-side template compilation from user input as harmless.
Checklist
- Find every render call and check whether its template source contains user input.
- Fix any that do by making the template constant and passing values as variables.
- For user-editable templates, use a sandbox and isolate the render process.
- Remove or restrict preview endpoints that render arbitrary input.
- Check front-end template compilation for the same pattern.
- Add a regression test asserting that template syntax in input is rendered literally.
FAQ
How is SSTI different from XSS?
XSS executes in a victim’s browser and is prevented by escaping output; SSTI executes on the server, before the response exists, and is prevented by not putting user input into the template source. Output escaping does not help against SSTI because the evaluation has already happened by the time anything is escaped.
Does this affect all template engines?
The pattern applies to any engine that distinguishes template syntax from data, which is essentially all of them; the specific payloads differ. Some engines offer a sandboxed mode that restricts what expressions can reach, which reduces severity without eliminating the need to keep user input out of the template source.
How do I test for it safely?
Send an arithmetic expression such as double braces containing a multiplication in the input that you suspect reaches the template, and see whether the output contains the computed number rather than the literal text. That proves evaluation without running anything destructive. Do this against your own application, in a non-production environment.
What if the feature genuinely needs user-authored templates?
Then treat it as a feature requiring isolation rather than a rendering convenience: sandbox the engine, restrict the available constructs to an allow-list, render without access to credentials or the database, and consider a separate service so a compromise is contained. If that cost is unacceptable, changing the feature is usually the right call.
Related topics
- What Is SQL Injection (and Why Does AI-Generated Code Keep Writing It)?
- What Is Cross-Site Scripting (XSS)?
- What Is Prototype Pollution?
- What Is the OWASP Top 10?
- What Is SSRF (Server-Side Request Forgery)?
- The 15 Security Failures Your AI Coding Assistant Ships by Default