aoh

A tool for running Ansible playbooks with `curl <url> | python` commands
git clone https://git.ashermorgan.net/aoh/
Log | Files | Refs | README

commit 73d2c50ff81383592114f11575e0bbada9c63f03
parent 70f2bf7d946b09bb087a268fa76270bf0d75b97a
Author: Asher Morgan <asher@ashermorgan.net>
Date:   Mon,  7 Sep 2026 20:31:28 -0700

Detect escaped Jinja expressions

Diffstat:
MREADME.md | 15++++++++-------
Maoh/playbook.py | 4++--
Maoh/security.py | 6+++++-
Mdemo/playbooks.yml | 8++++----
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:'