Skip to content
Injection Reviewed 2026-09-12

Template Injection

What does this mean ?

Server-side template injection occurs when an application treats untrusted text as template source. Template engines interpret expressions and directives before producing output. Passing a value into a fixed template is different from building a template by concatenating that value into its source.

What can happen ?

The engine may expose data available in its context or execute unexpected operations. Some engines and configurations can lead to code execution. Impact depends on the engine, supplied objects, extensions and process privileges; not every template language has the same capabilities.

Recommendation

Keep template files and source under developer control. Pass request values through the template's data/context API. Use a server-owned allowlist if a request selects among templates, and do not let it choose filesystem paths. Keep automatic output escaping enabled for HTML rendering.

HTML encoding and template isolation solve different problems. Escaping a string for HTML does not make it safe to compile as template source. If a product intentionally offers customer-authored templates, use a narrowly designed template language with explicit data access and separate resource/process isolation. Do not assume a generic in-process “sandbox” is a complete security boundary.

Sample Code

These examples render one untrusted display name in trusted HTML source. A production renderer should also limit input size and avoid placing powerful application objects in the template context.

from jinja2 import Environment

environment = Environment(autoescape=True)

# Unsafe: untrusted text becomes part of the template language.
unsafe_template = environment.from_string("<p>Hello " + name + "</p>")

# Safer: source is fixed; name is data.
template = environment.from_string("<p>Hello {{ name }}</p>")
rendered = template.render(name=name)

Prefer a configured loader with server-owned template files in a real application. autoescape=True protects this HTML context; it does not make the unsafe template source safe.

ERB itself does not apply Rails' automatic escaping. This standalone example explicitly escapes an HTML-text value:

require 'erb'

# Unsafe: ERB expressions in name become source.
unsafe_template = ERB.new('<p>Hello ' + name + '</p>')

# Safer: fixed source and an escaped data value.
template = ERB.new('<p>Hello <%= display_name %></p>')
rendered = template.result_with_hash(display_name: ERB::Util.html_escape(name))

In Rails views, use the framework's normal variable rendering and avoid raw or html_safe for untrusted values.

Nunjucks with HTML autoescaping enabled:

import nunjucks from 'nunjucks';
const environment = new nunjucks.Environment(undefined, { autoescape: true });

// Unsafe: do not render this source when name is untrusted.
const unsafeSource = '<p>Hello ' + name + '</p>';

// Safer
const rendered = environment.renderString('<p>Hello {{ name }}</p>', { name });

Do not use a user-submitted template string as the first renderString argument. The same trust boundary applies even if a TypeScript type says the value is a string.

Regression checks

Use local fixtures containing harmless syntax markers, such as {{ 2 + 3 }} and <%= 2 + 3 %>. The marker supplied as a display name must appear as data, rather than becoming 5. Test a simple HTML marker separately to confirm output escaping. Confirm an unknown template ID is rejected and cannot select another file. These tests require no commands, secrets or external requests.

References