Jinja2 / Lookup RCE

Detects remote code execution and information disclosure via Jinja2 lookups, !unsafe tags, and unsanitised template evaluation in when/set_fact/assert clauses.

10 rules in jinja_lookup_rce.yml

CRITICAL: 2 | HIGH: 6 | MEDIUM: 2

Rule IDSeverityTitleDescriptionRefs
jinja_statement_block_lookupCRITICALJinja2 {% … %} Statement Block Invoking lookup/pipe/urlA {% %} statement inside a playbook value can call lookup() directly, bypassing the usual {{ }} context-awareness and letting attackers smuggle command execution through what looks like control flow.
lookup_pipe_rceCRITICALlookup(‘pipe’, …) - Arbitrary Command Execution on ControllerThe ‘pipe’ lookup runs an arbitrary shell command on the Ansible controller and returns its stdout. Any variable interpolation inside makes this an RCE sink.
jinja_in_set_fact_unsafeHIGHset_fact Assigns Unescaped Template Expressionset_fact with a value like key: ‘{{ user_input }}’ stores the rendered (possibly attacker-controlled) template string as a fact. The next task that interpolates that fact renders it again - classic second-order template injection.
jinja_in_when_clauseHIGHJinja2 Expression Wrapped Inside when: ClauseAnsible already treats the body of when: as a Jinja2 expression - wrapping it in {{ }} causes it to be rendered twice, enabling template injection if the inner variable is attacker-controlled.
lookup_file_traversalHIGHlookup(‘file’, …) With Attacker-Input Path - Controller File DisclosureThe file lookup reads files from the Ansible controller filesystem. This rule fires when the lookup path contains an INTEROLATED attacker-controlled variable (user_input, request_body, query_params, raw_input, …) OR a literal .. traversal segment. A compromised inventory variable that flows into a file lookup can disclose /etc/shadow, ~/.ssh/id_rsa, or vaulted secrets from the controller. Role-config path vars like {{ directory_name }}/config.yml are NOT flagged - they’re controller-owned and are the normal shape for parameterised role file-reads; the genuine CWE-22 risk requires attacker-controlled input, which this rule now anchors on.
lookup_password_write_world_readableHIGHlookup(‘password’, …) Writing Plaintext to Shared Pathlookup(‘password’, ‘/tmp/…’ ) or any world-readable path persists the generated password in plaintext on the controller filesystem.
lookup_url_rceHIGHlookup(‘url’, …) - Remote Content Retrieval on ControllerThe ‘url’ lookup fetches content from a URL on the Ansible controller during play compilation. Unpinned URLs and missing validation turn this into a remote-payload sink (SSRF, malicious JSON/YAML ingestion, cache poisoning).
unsafe_tag_bypassHIGHYAML !unsafe Tag Bypasses Template SandboxMarking a value !unsafe tells Ansible to treat the string as literal and skip templating - but when the value is later concatenated into a shell/command/jinja context, the attacker-controlled content bypasses input validation.
jinja_in_assert_msgMEDIUMassert: fail_msg Interpolates Unsanitised Variablefail_msg / success_msg render Jinja2. If they interpolate attacker-controlled data, log exfiltration or second-order SSTI becomes possible.
lookup_env_leakMEDIUMlookup(’env’, …) Reading Likely-Sensitive Controller VariableThe ’env’ lookup reads environment variables from the Ansible controller process (not the managed host). When the variable name looks like a secret (token, key, password, credentials, AWS_, GH_, etc.) this leaks controller secrets into the play scope. Benign env reads like HOME / USER / PATH / CI flag do not match.