commit 73d2c50ff81383592114f11575e0bbada9c63f03
parent 70f2bf7d946b09bb087a268fa76270bf0d75b97a
Author: Asher Morgan <asher@ashermorgan.net>
Date: Mon, 7 Sep 2026 20:31:28 -0700
Detect escaped Jinja expressions
Diffstat:
4 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
@@ -117,14 +117,14 @@ main.yml: # The playbook name
block_opts:
- '--list-tags'
+ # Whether to allow clients to use Jinja expressions in ansible-playbook
+ # arguments. Defaults to false.
+ jinja_args: true
+
# Additional raw ansible-playbook options
extra_args:
- '--diff'
- # Whether to allow clients to use Jinja expressions in ansible-playbook
- # options. Defaults to false.
- jinja: true
-
# The description displayed on the playbook web page. Defaults to null, which
# disables the playbook web page.
web_description: 'Run the main.yml playbook with a single command:'
@@ -168,9 +168,10 @@ takes, or can be configured to take, to mitigate security risks.
options can be allowed or blocked via the `allow_opts` and `block_opts`
playbook options.
-- **Blocking Jinja Expressions:** By default, clients-supplied
- `ansible-playbook` options containing Jinja expressions are blocked. If
- necessary, this protection can be disabled via the `jinja` playbook option.
+- **Blocking Jinja Expressions in Ansible Arguments:** By default,
+ clients-supplied `ansible-playbook` options containing Jinja expressions are
+ blocked. If necessary, this protection can be disabled via the `jinja_args`
+ playbook option.
- **Hiding Ansible Output:** By default, clients receives and print all Ansible
output, including `--diff` output, `--verbose` logs, and even output from
diff --git a/aoh/playbook.py b/aoh/playbook.py
@@ -22,9 +22,9 @@ class Playbook:
'groups': list,
'password': bool,
'output': bool,
- 'jinja': bool,
'allow_opts': list,
'block_opts': list,
+ 'jinja_args': bool,
'extra_args': list,
'web_description': str,
}
@@ -45,9 +45,9 @@ class Playbook:
self.groups = dict.get('groups', [])
self.password = dict.get('password', False)
self.output = dict.get('output', True)
- self.jinja = dict.get('jinja', False)
self.allow_opts = dict.get('allow_opts', [])
self.block_opts = dict.get('block_opts', [])
+ self.jinja_args = dict.get('jinja_args', False)
self.extra_args = dict.get('extra_args', [])
self.web_description = dict.get('web_description')
diff --git a/aoh/security.py b/aoh/security.py
@@ -66,8 +66,12 @@ def _get_opts(args):
def validate_args(playbook, args):
"""Determine if arguments pass a playbook's security policies."""
+ l1 = ['{', r'\x7b', r'\u007b', r'\U0000007b']
+ l2 = ['%', r'\x25', r'\u0025', r'\U00000025']
+ JINJA_PATTERNS = [a+b for a in l1 for b in l1+l2]
+
cmdline = ' '.join(args)
- if not playbook.jinja and ('{{' in cmdline or '{%' in cmdline):
+ if not playbook.jinja_args and any(x in cmdline for x in JINJA_PATTERNS):
return False
for opt in _get_opts(args):
diff --git a/demo/playbooks.yml b/demo/playbooks.yml
@@ -37,14 +37,14 @@ main.yml: # The playbook name
block_opts:
- '--list-tags'
+ # Whether to allow clients to use Jinja expressions in ansible-playbook
+ # arguments. Defaults to false.
+ jinja_args: true
+
# Additional raw ansible-playbook options
extra_args:
- '--diff'
- # Whether to allow clients to use Jinja expressions in ansible-playbook
- # options. Defaults to false.
- jinja: true
-
# The description displayed on the playbook web page. Defaults to null, which
# disables the playbook web page.
web_description: 'Run the main.yml playbook with a single command:'