A flaw was found in the Ansible Automation Platform automation-controller. In the shipped
production configuration, the Controller trusts the client-supplied X-Forwarded-For header as
the request's client IP without verifying that it originated from a trusted proxy, and selects
the leftmost (attacker-controlled) header value. As a result, an attacker can forge the source
IP address recorded for their requests in the Controller's audit and access logs, degrading
the integrity of forensic and SIEM attribution. The flaw does not grant additional access.
A flaw was found in the Ansible Automation Platform automation-controller request handling. The
shipped production settings define REMOTE_HOST_HEADERS=['HTTP_X_FORWARDED_FOR'] and
PROXY_IP_ALLOWED_LIST=[] (empty). In APIView.initialize_request (awx/api/generics.py:199-201),
client-supplied REMOTE_HOST_HEADERS are stripped only when PROXY_IP_ALLOWED_LIST is non-empty
and the request did not arrive through a listed proxy; with the empty shipped allow-list this
guard is skipped and the X-Forwarded-For header is never stripped. When the client IP is
resolved via get_remote_host()/get_remote_hosts() (django-ansible-base
ansible_base/lib/utils/requests.py), the function reads REMOTE_HOST_HEADERS in order and
returns the first (leftmost) X-Forwarded-For entry. Because the deployment fronts Controller
with Envoy, which by default appends (rather than replaces) the real client IP to the incoming
X-Forwarded-For header, an attacker-supplied leftmost value survives to the application and is
chosen as the client IP. That spoofed value is written into the Controller's audit/access
logging, including the login audit message ("User <username> logged in from <ip>") and the API
4xx error log's remote_addr field (API_400_ERROR_LOG_FORMAT). An attacker can therefore forge
the source IP attributed to API actions in Controller logs and any downstream SIEM keyed on
client IP. Unauthenticated failed-login and error log entries can likewise be attributed to
arbitrary IPs. A secure mechanism already exists but is not enabled by default: when a valid
HTTP_X_TRUSTED_PROXY shared-secret header is present, get_remote_hosts prefers the trusted
last/rightmost X-Forwarded-For entry (the value Envoy appends) and honors
x-envoy-external-address. The flaw affects only audit/forensic integrity and does not grant
additional access.
Upstream: https://github.com/ansible/tower (awx) and
https://github.com/ansible/django-ansible-base (get_remote_host[s])
Affected file: awx/api/generics.py:199-201 (guard), :119 and :246-265 (sinks);
awx/settings/production_defaults.py:23; awx/settings/defaults.py:166;
ansible_base/lib/utils/requests.py:14-16,38-42,65-66
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 request handling. The shipped production settings define REMOTE_HOST_HEADERS=['HTTP_X_FORWARDED_FOR'] and PROXY_IP_ALLOWED_LIST=[] (empty). In APIView.initialize_request (awx/api/generics.py:199-201), client-supplied REMOTE_HOST_HEADERS are stripped only when PROXY_IP_ALLOWED_LIST is non-empty and the request did not arrive through a listed proxy; with the empty shipped allow-list this guard is skipped and the X-Forwarded-For header is never stripped. When the client IP is resolved via get_remote_host()/get_remote_hosts() (django-ansible-base ansible_base/lib/utils/requests.py), the function reads REMOTE_HOST_HEADERS in order and returns the first (leftmost) X-Forwarded-For entry. Because the deployment fronts Controller with Envoy, which by default appends (rather than replaces) the real client IP to the incoming X-Forwarded-For header, an attacker-supplied leftmost value survives to the application and is chosen as the client IP. That spoofed value is written into the Controller's audit/access logging, including the login audit message ("User <username> logged in from <ip>") and the API 4xx error log's remote_addr field (API_400_ERROR_LOG_FORMAT). An attacker can therefore forge the source IP attributed to API actions in Controller logs and any downstream SIEM keyed on client IP. Unauthenticated failed-login and error log entries can likewise be attributed to arbitrary IPs. A secure mechanism already exists but is not enabled by default: when a valid HTTP_X_TRUSTED_PROXY shared-secret header is present, get_remote_hosts prefers the trusted last/rightmost X-Forwarded-For entry (the value Envoy appends) and honors x-envoy-external-address. The flaw affects only audit/forensic integrity and does not grant additional access. Upstream: https://github.com/ansible/tower (awx) and https://github.com/ansible/django-ansible-base (get_remote_host[s]) Affected file: awx/api/generics.py:199-201 (guard), :119 and :246-265 (sinks); awx/settings/production_defaults.py:23; awx/settings/defaults.py:166; ansible_base/lib/utils/requests.py:14-16,38-42,65-66