User input reaching a template compiler rather than its context: the source-versus-values distinction, double-render pipelines, template names chosen by the caller, why a template sandbox is a mitigation and not a boundary, minimizing what the render context exposes, and template-driven resource exhaustion. Use when rendering templates with user-influenced values, when a template string or template name is assembled from input, or when a product lets users author templates.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add ShieldNet-360/secure-vibe --skill template-injection --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Template Injection?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/shieldnet-360-template-injection)More formats (shields.io, HTML) on the badges page.
---
name: template-injection
description: "User input reaching a template compiler rather than its context: the source-versus-values distinction, double-render pipelines, template names chosen by the caller, why a template sandbox is a mitigation and not a boundary, minimizing what the render context exposes, and template-driven resource exhaustion. Use when rendering templates with user-influenced values, when a template string or template name is assembled from input, or when a product lets users author templates."
---
<!-- Native skill bundle for agent-skills (cross-tool convention). Generated by `secure-vibe dev regenerate`. -->
<!-- Do not edit by hand; the source of truth is skills/template-injection/SKILL.md. -->
# Template Injection (SSTI)
User input reaching a template compiler rather than its context: the source-versus-values distinction, double-render pipelines, template names chosen by the caller, why a template sandbox is a mitigation and not a boundary, minimizing what the render context exposes, and template-driven resource exhaustion. Use when rendering templates with user-influenced values, when a template string or template name is assembled from input, or when a product lets users author templates.
## ALWAYS
- Pass user data as **bound values**, never into the template **source** string. This is the whole skill in one line: `render(tmpl, {"name": name})` is safe, `render("Hello " + name)` is SSTI. The compiler treats its source as code, and the context as data — so the only question that matters about any user value is which of the two it reaches.
- Keep the set of template sources **static and trusted**, loaded from files or constants. A template body read from a database column, an API response, or a request field is attacker-supplied code, whatever the column is called.
- Choose the template **by name from an allowlist**, never by a name the caller supplies. `render_template(user_value)` and `{% include user_value %}` are the same vulnerability as a user-supplied body: the caller picks which code runs, and on many engines the name resolves as a filesystem path, which makes it an arbitrary-file read as well.
- **Minimize the render context.** Pass the specific values the template needs, never the ambient objects — application config, settings, the request, `self`, a module, a live ORM instance. This is the control that still holds when a sandbox is escaped: an engine that cannot execute code can still print whatever it can reach, and configuration objects hold database URLs and signing keys.
- Treat a **second render pass** as compilation of whatever survived the first. If rendered output is parsed again, a user value that was inert data on pass one is `{{ }}` code on pass two. The fix is architectural — stop re-parsing rendered output. Neutralizing metacharacters before the second pass is a fallback that has to enumerate every delimiter of every engine, including ones a custom delimiter configuration introduces, and two adjacent fields can produce a delimiter that neither of them contains.
- **Bound the render**: cap iteration counts, recursion depth, include depth, output size, and wall-clock time. A template is a denial-of-service sink even where it is not a code-execution one, and an unbounded loop count is one request that allocates until the process dies.
- Keep **autoescaping on**, and know that autoescape is an XSS control rather than an SSTI one — it escapes what the template prints, not what the template *is*. `frontend-security` owns output encoding and the raw-output opt-outs (`|safe`, `{{{ }}}`, `template.HTML`) that turn it off per value.
## NEVER
- Build a template's source from user input — string concatenation, an f-string, `String.format`, or a `Template(...)` / `from_string` / `compile` call whose argument contains a value you did not write.
- Store a template body in a user-writable field — a profile name, an email subject, a CMS block — and later hand it to the engine.
- Treat a **sandbox as a trust boundary**. Sandboxed environments restrict attribute and builtin access, and they have a documented history of escapes in every engine that offers one; the escape is usually a way to reach an ordinary-looking attribute that leads back to a callable. A sandbox lowers the probability of RCE and does not make user-authored templates safe. If the product genuinely requires user-authored templates, isolate the renderer — separate process, no network, no filesystem, no credentials, a wall-clock timeout — and keep the engine patched.
- Enumerate forbidden attribute names as the control (`__class__`, `__globals__`, `constructor`, `getClass`). A denylist of literal names is defeated by string concatenation, indexing, and dynamic attribute lookup. Allowlist what the template may reach instead.
- Assume the client-side framework is out of scope when the *template string itself* is user-built. Runtime template compilation from user input — a compiler-included build compiling a user string, or `new Function` — is injection on the client. Interpolating a user value into a normal component template is XSS, which `frontend-security` owns.
## KNOWN FALSE POSITIVES
- User data passed as **context variables** to a static template is the correct pattern. This is the shape the whole skill is pointing at, not a finding.
- A `{{ … }}` or `${ … }` sequence in a string that never reaches a compiler is not SSTI: a client-side template shipped as a server-side string literal, an i18n catalogue, a Helm / Kubernetes / Grafana / Prometheus manifest, a CI expression. The finding requires a render call, not a delimiter.
- A logic-less engine with no user-authored template is not an RCE risk, though it is still subject to the output-size and XSS rules. Note that being *called* logic-less is not the same as being logic-less — engines that support helpers, partials and subexpressions are not, whatever their marketing says.
- Escaping opt-outs applied to content the application itself generated — a rendered Markdown block, a sanitized HTML fragment — are a `frontend-security` question about the sanitizer, not an SSTI finding.
## Reference files
Read these only when the task calls for them.
- `references/engines.md`
- `references/verifying-findings.md`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!