Skip to content

VNX-1336: Server-side template injection: template source built from request data

Reports Python and Node.js code that compiles a template whose source comes from request data (render_template_string, Jinja2 from_string/Template, Django and Mako Template, Handlebars.compile, ejs/pug render and compile, nunjucks renderString, lodash _.template), directly or through one or two assignments. Literal or file templates with request data passed as context are not reported.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-1336
Severity Medium
SARIF level warning
Kind sast
Languages JavaScript / TypeScript, Python
CWE CWE-1336
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:M/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags template-injection ssti rce cwe-1336
Source internal/sast/rules/vnx-1336.rego

Overview

VNX-1336 maps to CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine. When request data becomes part of the template source, and not just the values the template renders, an attacker can write template expressions ({{ config }}, {{ ''.__class__.__mro__ }}, #{7*7}) that the engine evaluates on the server.

Why This Matters

Server-side template injection in Jinja2, Mako, Pug, EJS, Handlebars and lodash templates regularly escalates to remote code execution, because those engines give templates access to the host language’s objects.

What Gets Flagged

name = request.args.get("name", "guest")
template = f"<h1>Hello {name}</h1>"
return render_template_string(template)              # FLAGGED

tpl = env.from_string(request.get_json()["body"])    # FLAGGED
t = Template(request.GET["q"])                       # FLAGGED (django.template)
const src = req.body.template
const fn = Handlebars.compile(src)                   // FLAGGED
res.send(pug.render(`p Hello ${req.query.name}`))    // FLAGGED
res.send(nunjucks.renderString('Hello ' + req.query.name, {}))  // FLAGGED

What Is Not Flagged

  • A literal template with request data passed only as render context: render_template_string("<h1>{{ name }}</h1>", name=request.args["name"]), ejs.render('<%= name %>', { name: req.query.name }).
  • Templates loaded from files: render_template, res.render, env.get_template, a template read from disk.
  • Python’s string.Template, which substitutes values and evaluates no expressions.
  • Comments and docstrings.
  • Templates built from stored data (a value read from the database) are not followed.

Remediation

  1. Keep template source constant or load it from files under your control.
  2. Pass user input only as context variables, which the engine escapes.
  3. If users must author templates, use a sandboxed engine (jinja2.sandbox.SandboxedEnvironment) or a logic-less format, and never expose host objects to it.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-1336
// nosec vnx-1336

Or record an ignore rule: vulnetix ignore add --rule vnx-1336 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-1336.rego, cli/internal/sast/docs/vnx-1336.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.