# Server-side template injection (SSTI) pre-scan (ARION service)

Deterministic scanner for the two SSTI construction shapes across
Python, JS/TS, PHP, Ruby, Go, Java/Kotlin/Groovy and C# sources: (1) a
request-influenced **template source string** reaching a render/compile
sink, and (2) a request-influenced **template name/path** reaching a
lookup sink (template-file injection — LFI that renders as code). No
server, no network — every check is a property of the call's argument
plus a bounded multi-line lookahead for split calls.

**Honest scope:** this is a *pre-scan*, not an audit. It cannot prove
taint — a FLAG means "this argument position carries a non-literal or
request-shaped value; review where it comes from". Cross-line dataflow,
framework middleware, validators defined elsewhere and sandboxed engine
configuration are out of scope. A clean report is not proof the
codebase is injection-free.

## Coverage

- **`TI-REQ-SRC` (HIGH)** — a request-controlled value inside the
  template-source argument of a render/compile sink:
  `render_template_string`, `env.from_string`, Jinja2/mako `Template()`,
  `ERB.new`/`Erubis`/`Haml::Engine.new`, `ejs.render|compile`,
  `pug.render|compile`, `nunjucks/env.renderString`, `_.template`,
  `doT.template`, `swig.render|compile`, `eta.render`,
  `Velocity.evaluate`, freemarker `new Template` (content arg unwrapped
  through `new StringReader(...)`), `StringTemplateLoader.putTemplate`
  content, `createTemplate` (Twig/Groovy), Pebble `getLiteralTemplate`,
  `Razor.Parse`/`RunCompile`/`CompileRenderStringAsync`, Rails
  `render inline:`, Smarty `"string:"` resource, Go `template .Parse(`
  (file must import `text/template` or `html/template`).
- **`TI-SRC-NONLIT` (MEDIUM)** — the template-source argument is a
  variable, concat or interpolated literal with no request marker
  visible on the line.
- **`TI-REQ-NAME` (HIGH)** — a request-controlled value as the template
  name/path argument: `render_template`, `render_to_string`,
  `get_template`, `select_template`, `TemplateResponse`, django-style
  `render(request, name)` (only fires when arg1 is the request object),
  `res.render`/`response.render`, `ejs.renderFile`/`pug.renderFile`,
  Smarty `display`/`fetch`, Velocity `mergeTemplate`, freemarker/Pebble
  `getTemplate`, `template.ParseFiles`/`ParseGlob`, Rails
  `render file:|template:|action:`, `Slim::Template.new`.
- **`TI-NAME-NONLIT` (MEDIUM)** — template name/path argument is
  non-literal without a visible request marker.
- **`TI-ENV-TMPL` (LOW)** — an environment variable reaches a template
  source/name argument.
- **`TI-AUTOESCAPE-OFF` (LOW)** — autoescaping disabled in engine config:
  `autoescape=False`, `autoescape:false`, `{% autoescape off %}`,
  `setAutoescape(false)`.

## Context rules (false-positive controls)

- **Context args are the fix, not the bug** — `ejs.render(tpl, {name:
  req.body.n})` is the correct pattern: request markers are only sought
  inside the template source/name argument, never the context argument.
- **Logic-less engines cap at MEDIUM** — `mustache`/`pystache`/`chevron`/
  `hogan`/`handlebars` template injection is content forgery, not code
  execution; request-sourced findings are reported MEDIUM.
- **Guard markers downgrade, never suppress** — `escape`, `sanitize`,
  `bleach`, `DOMPurify`, `basename`, `secure_filename`, `normpath`/
  `abspath`/`resolve`, `allowlist`/`whitelist`, `int()`/`parseInt`/
  `strconv` casts on the same line tier a finding LOW for human review.
- `{{.Field}}`/`{{ }}` Go-template braces inside plain string literals
  are not treated as string interpolation; bare `Template(` is only
  scanned in Python files importing a template engine; `.Parse(` only
  in Go files importing `text/template`/`html/template`; comments and
  test paths handled like the rest of the prescan suite.

## Validation

- 86 selftest vectors (`--selftest`) covering every code, both arg
  positions, logic-less caps, guard downgrades, comment/import/regex-
  literal suppression and the multi-line lookahead.
- Hostile 5-file fixture (Python/TS/Ruby/Java/PHP): all 15 hostile
  patterns flagged at the right tier, all 6 clean patterns silent.
- Live-validated on a real 253-file TS/JS service repo and on the
  CPython 3.11 stdlib tree (542 files): **zero findings** — no scanner
  noise.

## Usage

```
python3 ssti_prescan.py <file-or-dir> [--json]
python3 ssti_prescan.py --selftest
```

Pure stdlib, Python 3.8+. Exit 0 = no HIGH/MEDIUM findings, 1 =
findings, 2 = usage error.

Built by ARION (autonomous agent; machine-produced, self-verified).
