A flaw was found in Ansible Automation Platform's automation-controller. The custom
Credential Type environment-variable injector validates variable names against a
deny-list (an ANSIBLE_* prefix check plus a fixed ENV_BLOCKLIST) that omits
process-hijacking loader variables such as BASH_ENV, ENV, LD_PRELOAD, LD_LIBRARY_PATH,
PYTHONSTARTUP and GIT_SSH_COMMAND. Combined with the credential file injector, a
privileged user can write an attacker-controlled script into the execution environment
and point BASH_ENV at it, obtaining arbitrary code execution inside the
execution-environment container for any job that attaches a credential of that type.
A flaw was found in Ansible Automation Platform's automation-controller. Custom
Credential Types let an author define injectors.env (environment variables set in the
execution environment when a credential of that type is attached to a job) and
injectors.file (files rendered into the execution environment whose path is exposed to
other injectors as {{ tower.filename }}). The env-variable names are validated only by
a deny-list: CredentialTypeInjectorField.validate_env_var_allowed
(awx/main/fields.py:689-701) rejects names beginning with "ANSIBLE_" and names present
in the ENV_BLOCKLIST frozenset (awx/main/constants.py:48-70). The stated purpose of
this control is to stop injectors from hijacking the runner process (it blocks PATH,
PYTHONPATH, VIRTUAL_ENV and all ANSIBLE_* configuration variables). The deny-list is
incomplete: it does not include BASH_ENV, ENV, LD_PRELOAD, LD_LIBRARY_PATH, LD_AUDIT,
PYTHONSTARTUP, PYTHONWARNINGS, GIT_SSH_COMMAND, PERL5OPT, or similar loader/
process-hijacking variables, so those names pass validation. An attacker can therefore
define a credential type whose file injector writes a shell script and whose env
injector sets BASH_ENV to {{ tower.filename }}. Every non-interactive bash process
spawned during a job (Ansible modules shell out constantly) then sources and executes
the attacker's script, yielding arbitrary code execution inside the
execution-environment container — independent of the playbook content — for any job
that attaches a credential of the malicious type, with access to the secrets of all
co-attached credentials in the same job environment and to any inventory host the job
can reach. The same weak check is repeated at the runtime injection sink
(awx_plugins.interfaces _temporary_private_inject_api.py, which re-checks only
ENV_BLOCKLIST), so the fix must be applied in both places. Creating a custom credential
type requires Controller superuser (CredentialTypeAccess inherits BaseAccess), so this
is primarily a defense-in-depth bypass of a control whose explicit purpose is to
constrain exactly this behavior; however, an organization-level Credential Admin (not a
superuser) can weaponize an already-existing malicious custom type, and in
Gateway-managed AAP 2.5+ the platform-admin role is explicitly not host/EE root, so
code execution in the execution environment via a configuration API crosses a real
trust boundary.
Upstream: https://github.com/ansible/tower (private) / awx_plugins.interfaces
Affected files: awx/main/fields.py:689-701; awx/main/constants.py:48-70;
awx_plugins/interfaces/_temporary_private_inject_api.py:263-288
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 Ansible Automation Platform's automation-controller. Custom Credential Types let an author define injectors.env (environment variables set in the execution environment when a credential of that type is attached to a job) and injectors.file (files rendered into the execution environment whose path is exposed to other injectors as {{ tower.filename }}). The env-variable names are validated only by a deny-list: CredentialTypeInjectorField.validate_env_var_allowed (awx/main/fields.py:689-701) rejects names beginning with "ANSIBLE_" and names present in the ENV_BLOCKLIST frozenset (awx/main/constants.py:48-70). The stated purpose of this control is to stop injectors from hijacking the runner process (it blocks PATH, PYTHONPATH, VIRTUAL_ENV and all ANSIBLE_* configuration variables). The deny-list is incomplete: it does not include BASH_ENV, ENV, LD_PRELOAD, LD_LIBRARY_PATH, LD_AUDIT, PYTHONSTARTUP, PYTHONWARNINGS, GIT_SSH_COMMAND, PERL5OPT, or similar loader/ process-hijacking variables, so those names pass validation. An attacker can therefore define a credential type whose file injector writes a shell script and whose env injector sets BASH_ENV to {{ tower.filename }}. Every non-interactive bash process spawned during a job (Ansible modules shell out constantly) then sources and executes the attacker's script, yielding arbitrary code execution inside the execution-environment container — independent of the playbook content — for any job that attaches a credential of the malicious type, with access to the secrets of all co-attached credentials in the same job environment and to any inventory host the job can reach. The same weak check is repeated at the runtime injection sink (awx_plugins.interfaces _temporary_private_inject_api.py, which re-checks only ENV_BLOCKLIST), so the fix must be applied in both places. Creating a custom credential type requires Controller superuser (CredentialTypeAccess inherits BaseAccess), so this is primarily a defense-in-depth bypass of a control whose explicit purpose is to constrain exactly this behavior; however, an organization-level Credential Admin (not a superuser) can weaponize an already-existing malicious custom type, and in Gateway-managed AAP 2.5+ the platform-admin role is explicitly not host/EE root, so code execution in the execution environment via a configuration API crosses a real trust boundary. Upstream: https://github.com/ansible/tower (private) / awx_plugins.interfaces Affected files: awx/main/fields.py:689-701; awx/main/constants.py:48-70; awx_plugins/interfaces/_temporary_private_inject_api.py:263-288