Template injection
Skill ShieldNet-360/secure-vibe/dist/agent-skills/.agents/skills/template-injection
Prevent server-side template injection — user input reaching a template compiler, double-render pipelines, sandbox escapes, and template-driven resource exhaustion — Applies to: when rendering templates with any user-influenced value (email / notification bodies, PDFs, HTML, config); when a template's source string is assembled or stored from user input; when using a template engine (Jinja2, Twig, Freemarker, Velocity, ERB, Handlebars, Go text/html-template, Thymeleaf, Razor, Liquid, Mako, Pug); when reviewing a two-step render / re-parse pipelineFrom its SKILL.md
npx -y skills add ShieldNet-360/secure-vibe --skill template-injectionAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 15 stars15 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
3.6 KB, 715 tokens by cl100k_base, as published. Nobody here has run it
Template Injection (SSTI)
Prevent server-side template injection — user input reaching a template compiler, double-render pipelines, sandbox escapes, and template-driven resource exhaustion
ALWAYS
- Pass user data to a template only as bound values / context variables, never concatenated or interpolated into the template source string.
render(tmpl, {name})is safe;render("Hello " + name)is SSTI. - Keep the set of template sources static and trusted — load them from files or constants the user cannot influence. A template whose text comes from the DB, an API response, or a request field is attacker-controlled code, not data.
- Treat any second render pass as code execution. If the output of one render is parsed and executed as a template again (double render), a user value that survived the first pass as data becomes
{{ }}code on the second. Never re-parse rendered output; if a two-step render is unavoidable, escape/neutralise template metacharacters ({{,{%,${,#{,<%) in user fields before the second pass. - Use a sandboxed / logic-less engine for user-authored templates (Handlebars, Liquid, Jinja2
SandboxedEnvironment) and deny attribute/builtin/internals access (__class__,__globals__,constructor,getClass()). - Keep autoescaping on for the correct context (HTML/JS/URL) — SSTI and XSS share the input path.
- Bound every loop and output driven by user input: cap iteration counts, recursion depth, and total rendered size at the template boundary.
range(n)/{{range n}}with attacker-chosennand no cap is an OOM DoS.
NEVER
- Build a template's source string from user input (
Template("Hi " + user.name), f-strings /String.format/ concat feeding the compiler). Bind values instead. - Store a template body in a user-writable field (profile name, subject line, CMS block) and later feed it to the engine.
- Re-render engine output as a template without neutralising user-supplied metacharacters (double-render / "recursive template" pipelines).
- Expose the raw engine to untrusted authors without a sandbox and an attribute allowlist — SSTI escalates to RCE in most server engines (Jinja2, Twig, Freemarker, Velocity, ERB).
- Render an unbounded user-controlled loop / repetition / include count without a size and iteration cap.
KNOWN FALSE POSITIVES
- User data passed as context variables to a static template is the correct pattern, not SSTI.
- Client-side template binding (React/Vue/Angular interpolation) is XSS-scope, not server SSTI (see frontend-security) — unless the template string itself is user-built (
v-htmlof a template,new Function). - Logic-less engines (Mustache, strict Handlebars) with no user-authored template and autoescape on are low-risk for RCE (still cap output size).
- A template loaded from a trusted file whose values include user data is fine.
What ships with it: 1 file
1.4 KB alongside SKILL.md
- metadata.json1.4 KB