An argument-injection flaw was found in the Ansible Automation Platform automation-controller
system-job subsystem. The system-job template launch endpoint stores a user-supplied "days"
variable without running the integer validation defined elsewhere for that field, and the
dispatcher flattens the management-command argument list into a single string with spaces before
the job runner re-splits it, so spaces in the value become additional command-line arguments.
Because system jobs are executed in-process on the control node without the container isolation
applied to all other job types, an authenticated user with superuser privileges can inject
arbitrary arguments — including Python's path option — into the control-plane awx-manage process,
controlling its argument vector and the first entry of its module search path. Full remote code
execution requires an additional import gadget that is not present in the current management
commands, so the demonstrated impact is argument injection with control of the process search
path rather than confirmed code execution.
A flaw was found in the Ansible Automation Platform automation-controller system-job dispatcher.
SystemJobTemplateLaunch.post (awx/api/views/__init__.py:3802-3805) calls create_unified_job with
the request's extra_vars directly and never invokes _accept_or_ignore_job_kwargs, so the "days"
integer validator in SystemJobTemplate._accept_or_ignore_variables (awx/main/models/jobs.py:
1234-1245) is not applied on the launch path; create_unified_job (awx/main/models/unified_jobs.py:
369-400) copies the extra_vars dict onto the SystemJob verbatim after checking only key names.
RunSystemJob.build_args (awx/main/tasks/jobs.py:2113-2134) then appends str(days) to the
awx-manage argument list without integer coercion, and RunSystemJob.write_args_file (jobs.py:
2136-2137) writes ' '.join(args) to the ansible-runner args file, which ansible-runner re-tokenizes
with shlex.split. A "days" value such as "5 --pythonpath /path" therefore splits into extra
awx-manage arguments. RunSystemJob.build_execution_environment_params returns {} (jobs.py:2110-2111)
and the dispatcher runs ansible_runner.interface.run() in-process for SystemJob instances
(jobs.py:773-781) — system jobs are the only unified-job class executed without receptor/podman
isolation — so the injected arguments reach an uncontainerized control-plane awx-manage process
running as the awx user with access to SECRET_KEY, database credentials, and the receptor control
socket. Django's handle_default_options parses global options such as --pythonpath from the
argument vector and inserts the attacker-supplied directory at the front of sys.path. Only a
superuser can trigger this (SystemJobTemplateAccess.can_start is decorated @check_superuser,
awx/main/access.py:1770-1773). Full code execution additionally requires a top-level module
imported for the first time after handle_default_options; no such import exists in the current
cleanup_jobs / cleanup_activitystream import graph, so the issue is confirmed as argument injection
with control of the process argument vector and sys.path[0], with code execution unproven.
Upstream: https://github.com/ansible/tower (awx)
Affected file: awx/main/tasks/jobs.py:2136-2137 (RunSystemJob.write_args_file — ' '.join(args),
root cause); jobs.py:2113-2134 (build_args, no int() coercion); jobs.py:
2110-2111 + 773-781 (uncontainerized in-process execution);
This issue has been addressed in the following products:
Red Hat Ansible Automation Platform 2.6 for RHEL 10
Red Hat Ansible Automation Platform 2.6 for RHEL 9
Via RHSA-2026:71113 https://access.redhat.com/errata/RHSA-2026:71113
This issue has been addressed in the following products:
Red Hat Ansible Automation Platform 2.5 for RHEL 9
Red Hat Ansible Automation Platform 2.5 for RHEL 8
Via RHSA-2026:71114 https://access.redhat.com/errata/RHSA-2026:71114
A flaw was found in the Ansible Automation Platform automation-controller system-job dispatcher. SystemJobTemplateLaunch.post (awx/api/views/__init__.py:3802-3805) calls create_unified_job with the request's extra_vars directly and never invokes _accept_or_ignore_job_kwargs, so the "days" integer validator in SystemJobTemplate._accept_or_ignore_variables (awx/main/models/jobs.py: 1234-1245) is not applied on the launch path; create_unified_job (awx/main/models/unified_jobs.py: 369-400) copies the extra_vars dict onto the SystemJob verbatim after checking only key names. RunSystemJob.build_args (awx/main/tasks/jobs.py:2113-2134) then appends str(days) to the awx-manage argument list without integer coercion, and RunSystemJob.write_args_file (jobs.py: 2136-2137) writes ' '.join(args) to the ansible-runner args file, which ansible-runner re-tokenizes with shlex.split. A "days" value such as "5 --pythonpath /path" therefore splits into extra awx-manage arguments. RunSystemJob.build_execution_environment_params returns {} (jobs.py:2110-2111) and the dispatcher runs ansible_runner.interface.run() in-process for SystemJob instances (jobs.py:773-781) — system jobs are the only unified-job class executed without receptor/podman isolation — so the injected arguments reach an uncontainerized control-plane awx-manage process running as the awx user with access to SECRET_KEY, database credentials, and the receptor control socket. Django's handle_default_options parses global options such as --pythonpath from the argument vector and inserts the attacker-supplied directory at the front of sys.path. Only a superuser can trigger this (SystemJobTemplateAccess.can_start is decorated @check_superuser, awx/main/access.py:1770-1773). Full code execution additionally requires a top-level module imported for the first time after handle_default_options; no such import exists in the current cleanup_jobs / cleanup_activitystream import graph, so the issue is confirmed as argument injection with control of the process argument vector and sys.path[0], with code execution unproven. Upstream: https://github.com/ansible/tower (awx) Affected file: awx/main/tasks/jobs.py:2136-2137 (RunSystemJob.write_args_file — ' '.join(args), root cause); jobs.py:2113-2134 (build_args, no int() coercion); jobs.py: 2110-2111 + 773-781 (uncontainerized in-process execution);