Skip to content
Security
Skill

/sast-ssti

Detect Server-Side Template Injection (SSTI) vulnerabilities in a codebase using a three-phase approach: recon (find template rendering sites that use dynamic strings), batched verify (trace user input to those sites in parallel subagents, 3 candidates each), and merge

From plugin
sast-skills
1.3k16 skills
Install
$ npx -y skills add utkusen/sast-skills --skill sast-ssti --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/sast-ssti

Context preview

The summary Claude sees to decide when to auto-load this skill.

Detect Server-Side Template Injection (SSTI) vulnerabilities in a codebase using a three-phase approach: recon (find template rendering sites that use dynamic strings), batched verify (trace user input to those sites in parallel subagents, 3 candidates each), and merge

SKILL.md

sast-ssti.SKILL.md
name: sast-ssti
description: >-
  Detect Server-Side Template Injection (SSTI) vulnerabilities in a codebase
  using a three-phase approach: recon (find template rendering sites that use
  dynamic strings), batched verify (trace user input to those sites in parallel
  subagents, 3 candidates each), and merge (consolidate batch results). Requires
  sast/architecture.md (run sast-analysis first). Outputs findings to
  sast/ssti-results.md. Use when asked to find SSTI or template injection bugs.

Server-Side Template Injection (SSTI) Detection

You are performing a focused security assessment to find Server-Side Template Injection vulnerabilities in a codebase. This skill uses a three-phase approach with subagents: **recon** (find candidate rendering sites where the template string is dynamic), **batched verify** (trace whether user input reaches each site's template argument, in parallel batches of 3), and **merge** (consolidate batch results into the final report).

**Prerequisites**: `sast/architecture.md` must exist. Run the analysis skill first if it doesn't.

---

What is SSTI

Server-Side Template Injection occurs when user-supplied input is embedded directly into a template string that is then evaluated by a template engine. Unlike passing user data as *context variables* to a static template, SSTI means the user can write template syntax that the engine will execute — leading to arbitrary code execution, file read, or full server compromise.

The core pattern: *unvalidated user input is used as the template string passed to a template engine's render/compile/evaluate function.*

What SSTI IS

  • Passing user input as the template string to be compiled or rendered:
  • `Template(user_input).render()` — Jinja2
  • `env.from_string(user_input).render()` — Jinja2
  • `render_template_string(user_input)` — Flask
  • `ejs.render(user_input, ctx)` — EJS (Node.js)
  • `nunjucks.renderString(user_input, ctx)` — Nunjucks
  • `Handlebars.compile(user_input)(ctx)` — Handlebars
  • `pug.render(user_input, ctx)` — Pug/Jade
  • `_.template(user_input)(ctx)` — Lodash/Underscore
  • `Velocity.evaluate(ctx, user_input)` — Apache Velocity (Java)
  • `new Template("anon", new StringReader(user_input), cfg).process(...)` — FreeMarker (Java)
  • `new ST(user_input).render()` — StringTemplate4 (Java)
  • `thymeleafEngine.process(user_input, ctx)` — Thymeleaf (Java)
  • `\Twig\Environment::createTemplate(user_input)->render(ctx)` — Twig (PHP)
  • `$smarty->fetch("string:" . user_input)` — Smarty (PHP)
  • `Liquid::Template.parse(user_input).render(ctx)` — Liquid (Ruby)
  • `ERB.new(user_input).result(binding)` — ERB (Ruby)
  • `t, _ := template.New("x").Parse(user_input); t.Execute(w, data)` — Go `text/template`
  • `Template.fromString(user_input).render(ctx)` — Pebble (Java)
  • Dynamic template name construction where the name itself comes from user input and the engine resolves arbitrary files:
  • `render_template(user_input)` (Flask) where `user_input` is not validated against a safe list
  • `res.render(req.query.template)` (Express) where the template name is user-controlled

What SSTI is NOT

Do not flag these patterns:

  • **User input as context data** (safe — the template is static, only the data changes):
  render_template("profile.html", name=request.args.get("name"))
  env.get_template("report.html").render(user=user_obj)
  res.render("dashboard", { title: req.body.title })
  • **XSS via template output**: If the template outputs unsanitized user data that is then rendered in a browser — that's XSS, not SSTI
  • **Static templates with dynamic filenames validated against an allowlist**: If the template name comes from user input but is strictly validated against a hardcoded set of allowed template names, it's not SSTI
  • **Sandboxed template engines configured with a restricted environment**: Liquid, Mustache, and similar logic-less engines cannot execute arbitrary code even if the template string comes from user input — but still flag them as "Needs Manual Review" unless you can confirm the engine is logic-less

Patterns That Prevent SSTI

When you see these patterns, the code is likely **not vulnerable**:

**1. Static template file with dynamic context (most common safe pattern)**

# Flask — static template, user input only in context dict
return render_template("user_profile.html", username=request.args.get("name"))

# Express — static view name
res.render("dashboard", { user: req.user })

**2. Allowlist validation for template names**

ALLOWED_TEMPLATES = {"invoice.html", "receipt.html", "summary.html"}
template_name = request.args.get("tmpl", "invoice.html")
if template_name not in ALLOWED_TEMPLATES:
    abort(400)
return render_template(template_name)

**3. Logic-less / sandboxed engines that don't support code execution**

// Mustache — logic-less, cannot execute arbitrary code even if template is user-supplied
const output = Mustache.render(userTemplate, ctx);  // lower risk, but still flag for review

---

Vulnerable vs. Secure Examples

Python — Flask / Jinja2

# VULNERABLE: user input rendered as template string
@app.route('/greet')
def greet():
    name = request.args.get('name', '')
    template = f"<h1>Hello {name}!</h1>"
    return render_template_string(template)
    # Payload: ?name={{7*7}} → renders "49"
    # RCE:    ?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}

# SECURE: user input passed as context variable to a static template
@app.route('/greet')
def greet():
    name = request.args.get('name', '')
    return render_template("greet.html", name=name)
# VULNERABLE: env.from_string with user-controlled template
@app.route('/preview')
def preview():
    tmpl = request.form.get('template')
    return Environment().from_string(tmpl).render()

# SECURE: load template from trusted file, pass user data as context
@app.route('/preview')
def preview
Read more
Ships withsast-skills

A collection of agent skills that turn your LLM coding assistant into a fully functional SAST scanner to find vulnerabilities in your codebase. Works natively with Claude Code, Codex, Opencode, Cursor and any other assistant that supports agent skills.

Get the whole plugin
Stats
1,266
Stars
61
Forks
Maintained
Maintenance
MIT
License
4mo ago
Last commit
4mo ago
Created

Repo: utkusen/sast-skills

Other skills on sast-skills.