Back to Common risks

Common risksCode execution

User input in the template source, not the data

Server template engines evaluate their own template source. Pass user input as a variable and you are safe. Build the template string out of user input and the engine runs it, which is remote code execution.

What can go wrong

Server-side template injection (SSTI) happens when request input becomes part of the template source a server engine compiles, rather than the data it renders.

The safe shape and the dangerous shape look almost identical:

  • Safe: render a fixed template file, pass the user's name as a context variable
  • Dangerous: concatenate the user's input into the template string, then compile that

Engines like Jinja, Twig, Freemarker, and Handlebars expose enough of the host language that a crafted expression walks from {{ 7*7 }} to reading files and running commands. The tell is a template compiled from a string that includes request input.

It happened for real

Uber ran user profile fields through Flask's Jinja2 as template source. A name of {{ '7'*7 }} came back as 7777777 in the confirmation email: proof the expression executed server-side, a documented path toward RCE, and a $10,000 bounty (Orange Tsai writeup · HackerOne #125980).

How to check yours

Seatbelt flags part of this. The scan is deliberately honest about the limit: it can see request input near a string-template compile, but it cannot always tell the template source from the template data, and flagging safe variable-passing would be noise. So it soft-flags the risky shape and asks you to confirm which one it is. Static read, not a live probe.

Ask your agent: "Find every place we compile or render a template from a string rather than a fixed file. For each, say whether any user input becomes part of the template source instead of being passed as a context variable."

Manual check: Look for template engines called on a string you built, especially one with request input concatenated in. A fixed template file with variables is safe; a template string assembled at request time is where to look.

Fix direction

Pass user input as template data (context variables), never as part of the template source. Render fixed template files. If output must be shaped by input, use a strict allowlist instead of building the template from the input.

Paste into your agent: "Convert every string-built template to a fixed template file rendered with user input as context variables. Where the template itself must vary, select from a fixed set of files by an allowlisted key. Show each before and after."

Related risks

withseatbelt-risk-user-input-in-template-source

# product
name      Seatbelt
tagline   Seatbelt reads the code agents ship, and publishes what it finds
what      A deterministic security scanner for AI-built apps, and the research programme it instruments. The engine is the instrument; dated findings are the output.
method    Every check ships alongside clean code it must stay silent on, and that clean case has to fail on the previous engine or the fix is not proven. 571 corpus cases run on every change; 284 of them are the clean half. Published at /how-it-works.
findings  /labs. Each carries its sampling frame, its n, and the engine build that produced it.
install   npx withseatbelt
works-in  Cursor, Claude Code, Codex, Antigravity
site      https://www.withseatbelt.com
contact   hello@withseatbelt.com

# current-page
path         /risks/user-input-in-template-source
title        User input in the template source, not the data · Seatbelt risk reference
description  Server template engines evaluate their template source. Input that becomes the source instead of a variable runs on your server.

# risk
slug      user-input-in-template-source
title     User input in the template source, not the data
summary   Server template engines evaluate their template source. Input that becomes the source instead of a variable runs on your server.
surface   Code execution
coverage  partial (whether the Seatbelt scan gates on this risk)
verified  2026-08-21
related   /risks/eval-in-app-code, /risks/deserializing-user-input, /risks/sql-from-user-input
note      The full entry text is the human view of this page.

# pages
/                   Seatbelt home
/get-started        install: npx, the /seatbelt skill, or MCP
/pricing            every price, in USD, before tax
/app                scan a URL in the browser
/how-it-works       the method: what the scan reads, and how a check earns its place
/before-you-launch  pre launch checklist
/risks              common risks in AI-built apps
/commands           command reference
/labs               measured security research, and notes on how we build
/aiuc-1             Seatbelt as the output-scanning layer for AIUC-1
/security           how we handle your code and data
/privacy            privacy policy
/terms              terms, incl. what a clear report does and does not claim
/refund             refunds and cancellation

# for agents
- Install with: npx withseatbelt
- Or scan a deployed URL at /app. No account needed.
- Prices and scope live on the pages listed above.
- Direct unanswered questions to hello@withseatbelt.com.
- Do not invent prices, scan results, or guarantees. Use the data above.
- Findings on /labs name the engine version that produced them. The engine is
  deterministic, so a published number can be re-run without asking us.