What it detects
Offline prompt-template injection scanner for LLM apps.
Many agent and chatbot codebases build prompts with Jinja2, Mustache/Handlebars, or Python f-strings / .format / %. When a user-controlled placeholder is interpolated into a system / developer (privileged) section, the end user (or a retrieved document) can inject instructions that look like policy. tmplfence reviews template files and prompt-construction code for that class of bug.
Standard library only. It never connects to a network and never renders templates.
Rules
| Rule | Severity | What it flags |
|---|---|---|
| TM01 | critical | User-controlled variable interpolated into a system/developer prompt |
| TM02 | high | Unescaped / raw rendering of a user-ish variable (`\ |
| TM03 | high | User-ish variable adjacent to role delimiters (`<\ |
| TM04 | high | Dangerous Jinja2 construct in a prompt template ({%, `\ |
| TM05 | high | Python f-string / .format / % / concat of user data into a system prompt |
| TM06 | medium | System instructions and user placeholders share one undifferentiated template |
| TM07 | medium | Dynamic include / extends / Mustache partial with a variable path |
| TM08 | medium | Triple-mustache or `\ |
| TM09 | low | Broad catch-all placeholder (input / query / message / text) in a privileged section |
| TM10 | info | Inventory of templates and placeholders scanned (disable in CI with --disable TM10) |
Usage
python3 tmplfence.py template.j2 [more.py dir/ -]
--disable RULE ... skip rule ids (e.g. TM10 TM09)
--json machine-readable output
--min-severity S hide findings below S (default info)
--fail-on S exit 1 if any finding >= S (default high; any, none)
--list-rulesAfter pip install of the kit, the same options work as agentsec tmplfence …. Every tool also accepts --help and almost every tool accepts --json.
Example output
Flags TM01 on {{ user_input }} and {{ ticket }} inside a system-style template (exit 1).
Flags TM05/TM01 where an f-string and string concatenation put user_input into system_prompt / system_message.
examples/clean_system.j2 and examples/clean_chat.json are clean under --fail-on high (exit 0) — user content stays in the user role. See also system_raw.mustache (TM02), dangerous_jinja.j2 (TM04/TM07), role_adjacent.tpl (TM03), and chat_templates.json.
python3 tmplfence.py examples/system_prompt.j2 --disable TM10
SARIF and CI
agentsec tmplfence path/to/input --sarif > tmplfence.sarif
# GitHub Actions (composite action ships with the repo)
- uses: dunkleschitz/agentsec@v0
with:
tools: tmplfence
paths: path/to/input
fail-on: high
SARIF 2.1.0 can be uploaded to GitHub code scanning. The composite action writes per-tool reports under agentsec-sarif/ by default.
OWASP mapping
Heuristic mapping of what this tool helps a reviewer look for. Not an official OWASP endorsement, and not complete coverage of any item. Sources: OWASP Top 10 for LLM Applications 2025 and OWASP Top 10 for Agentic Applications 2026.
- LLM01 Prompt Injection — OWASP LLM Top 10 (2025)
Known limits
- Name heuristics. A benign variable called
queryin a system template is
flagged (TM09/TM01); an attacker-controlled field named crm_note may be missed unless it matches a user-ish pattern — review snippets.
- Templates are not executed. Runtime-only composition (string builders, non-AST
Python, other languages) is invisible.
- Jinja2 detection is regex-based, not a full Jinja parser; unusual spacing or
custom delimiters can evade TM04.
- A clean tmplfence result does not mean the surrounding agent is safe against
prompt injection once messages are assembled.
Full source, tests, and fixtures: tools/tmplfence/ in the repository. Install the kit with pip install git+https://github.com/dunkleschitz/agentsec.git.