See how ansible compares to other vendors in security performance
Ansible before versions 2.3.1.0 and 2.4.0.0 fails to properly mark lookup-plugin results as unsafe. If an attacker could control the results of lookup() calls, they could inject Unicode strings to be parsed by the jinja2 templating system, resulting in code execution. By default, the jinja2 templating language is now marked as 'unsafe' and is not evaluated.
Multiple argument injection vulnerabilities in Ansible before 1.6.7 allow remote attackers to execute arbitrary code by leveraging access to an Ansible managed host and providing a crafted fact, as demonstrated by a fact with (1) a trailing " src=" clause, (2) a trailing " temp=" clause, or (3) a trailing " validate=" clause accompanied by a shell command.
Ansible before 1.6.7 does not prevent inventory data with "{{" and "lookup" substrings, and does not prevent remote data with "{{" substrings, which allows remote attackers to execute arbitrary code via (1) crafted lookup('pipe') calls or (2) crafted Jinja2 data.
A flaw was found in the way Ansible (2.3.x before 2.3.3, and 2.4.x before 2.4.1) passed certain parameters to the jenkinsplugin module. Remote attackers could use this flaw to expose sensitive information from a remote host's logs. This flaw was fixed by not allowing passwords to be specified in the "params" argument, and noting this in the module documentation.
A flaw was found in ansible-core. When installing a collection with ansible-galaxy collection install, the archive extractor validates member paths using lexical path normalisation (os.path.abspath) instead of resolving symbolic links (os.path.realpath), and it performs no containment check on symlink-typed directory members before creating them. A crafted collection tarball can chain symlink directory entries so that a subsequent file member is written outside the intended destination directory. This allows an attacker who can get a victim to install a malicious collection to overwrite arbitrary files with the privileges of the user running ansible-galaxy, leading to code execution on the control node. This is a bypass of the fix for CVE-2020-10691.
A flaw was found in the ansible automation platform. An insecure WebSocket connection was being used in installation from the Ansible rulebook EDA server. An attacker that has access to any machine in the CIDR block could download all rulebook data from the WebSocket, resulting in loss of confidentiality and integrity of the system.
A flaw was found in aap-gateway. Concurrent requests handled by the gateway grpc service can result in "swapping" a request. Effectively, a lesser privileged user (even unauthenticated) can get the JWT of a greater privileged user
Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validation in Ansible's handling of data sent from client systems. An attacker with control over a client system being managed by Ansible and the ability to send facts back to the Ansible server could use this flaw to execute arbitrary code on the Ansible server using the Ansible server privileges.
Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validation in Ansible's handling of data sent from client systems. An attacker with control over a client system being managed by Ansible and the ability to send facts back to the Ansible server could use this flaw to execute arbitrary code on the Ansible server using the Ansible server privileges.
Ansible before version 2.3 has an input validation vulnerability in the handling of data sent from client systems. An attacker with control over a client system being managed by Ansible, and the ability to send facts back to the Ansible server, could use this flaw to execute arbitrary code on the Ansible server using the Ansible server privileges.
A flaw was found in ansible-core. The extractcollectionfromgit() function in ansible-core's concreteartifactmanager.py constructs git clone commands without a '--' (end-of-options) separator before user-supplied URLs when installing collections from git sources. An attacker who provides a crafted collection source URI containing git argument injection payloads can achieve arbitrary command execution when a user runs 'ansible-galaxy collection install' with the malicious source. This is an incomplete fix for CVE-2026-11332, which hardened the role install path but missed the equivalent collection install code path.
The createscript function in the lxccontainer module in Ansible before 1.9.6-1 and 2.x before 2.0.2.0 allows local users to write to arbitrary files or gain privileges via a symlink attack on (1) /opt/.lxc-attach-script, (2) the archived container in the archivepath directory, or the (3) lxc-attach-script.log or (4) lxc-attach-script.err files in the temporary directory.
Ansible "User" module leaks any data which is passed on as a parameter to ssh-keygen. This could lean in undesirable situations such as passphrases credentials passed as a parameter for the ssh-keygen executable. Showing those credentials in clear text form for every user which have access just to the process list.
In Ansible, all Ansible Engine versions up to ansible-engine 2.8.5, ansible-engine 2.7.13, ansible-engine 2.6.19, were logging at the DEBUG level which lead to a disclosure of credentials if a plugin used a library that logged credentials at the DEBUG level. This flaw does not affect Ansible modules, as those are executed in a separate process.
A flaw was found in ansible. ansible.cfg is read from the current working directory which can be altered to make it point to a plugin or a module path under the control of an attacker, thus allowing the attacker to execute arbitrary code.
A flaw was found in Ansible 2.7.17 and prior, 2.8.9 and prior, and 2.9.6 and prior when using the Extract-Zip function from the winunzip module as the extracted file(s) are not checked if they belong to the destination folder. An attacker could take advantage of this flaw by crafting an archive anywhere in the file system, using a path traversal. This issue is fixed in 2.10.
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.validateenvvarallowed (awx/main/fields.py:689-701) rejects names beginning with "ANSIBLE" and names present in the ENVBLOCKLIST 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, VIRTUALENV and all ANSIBLE configuration variables). The deny-list is incomplete: it does not include BASHENV, ENV, LDPRELOAD, LDLIBRARYPATH, LDAUDIT, PYTHONSTARTUP, PYTHONWARNINGS, GITSSHCOMMAND, 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 BASHENV 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 (awxplugins.interfaces temporaryprivateinjectapi.py, which re-checks only ENVBLOCKLIST), 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) / awxplugins.interfaces Affected files: awx/main/fields.py:689-701; awx/main/constants.py:48-70; awxplugins/interfaces/temporaryprivateinjectapi.py:263-288
A flaw was found in the Ansible Engine 2.9.18, where sensitive info is not masked by default and is not protected by the nolog feature when using the sub-option feature of the basic.py module. This flaw allows an attacker to obtain sensitive information. The highest threat from this vulnerability is to confidentiality.
A flaw was found in the solariszone module from the Ansible Community modules. When setting the name for the zone on the Solaris host, the zone name is checked by listing the process with the 'ps' bare command on the remote machine. An attacker could take advantage of this flaw by crafting the name of the zone and executing arbitrary commands in the remote host. Ansible Engine 2.7.15, 2.8.7, and 2.9.2 as well as previous versions are affected.
A template injection flaw was found in Ansible where a user's controller internal templating operations may remove the unsafe designation from template data. This issue could allow an attacker to use a specially crafted file to introduce templating injection when supplying templating data.
A flaw was found in Ansible, where a user's controller is vulnerable to template injection. This issue can occur through facts used in the template if the user is trying to put templates in multi-line YAML strings and the facts being handled do not routinely include special template characters. This flaw allows attackers to perform command injection, which discloses sensitive information. The highest threat from this vulnerability is to confidentiality and integrity.
For GitHub pullrequest webhooks, geteventstatusapi() in awx/api/views/webhooks.py returns pullrequest.statusesurl from the request body verbatim without any host validation:
def geteventstatusapi(self): if self.geteventtype() != 'pullrequest': return return self.request.data.get('pullrequest', {}).get('statusesurl')
The value is persisted into job extravars and later used by updatewebhookstatus() in awx/main/models/mixins.py:
statusapi = self.extravarsdict.get('awxwebhookstatusapi') headers = {k: v.format(self.webhookcredential.getinput('token')), 'Content-Type': 'application/json'} response = requests.post(statusapi, data=json.dumps(data), headers=headers, timeout=30)
There is no host allowlist for the expected Git provider endpoint and no private-network egress filtering before the request is sent. The webhook receiver relies on an HMAC secret (webhookkey), which is readable by users with the admin role on the job template via /api/v2/jobtemplates/{id}/webhookkey/. This allows a template admin to forge a signed webhook payload with statusesurl pointing to an attacker-controlled endpoint, and AWX will POST status updates including the Git PAT in the Authorization header to that endpoint.
Controller registers LOGAGGREGATORHOST as a bare CharField with no character validation, and the logging settings validator only verifies that host/type are present. When any LOGAGGREGATOR setting is changed via PATCH /api/controller/v2/settings/logging/ (superuser-only), Controller regenerates /var/lib/awx/rsyslog/rsyslog.conf and restarts awx-rsyslogd. In the tcp/udp (omfwd) code path the host value is written raw as target="{host}", allowing an attacker to close the action() statement and append module(load="omprog") plus an omprog action() whose binary= runs an arbitrary shell command in the rsyslog control-plane component (uid=awx). That process can read SECRETKEY and the Postgres credentials, decrypt every stored Credential, and forge inter-service JWTs. Upstream: https://github.com/ansible/awx (devel) — UNFIXED (no PR) Affected file: awx/main/utils/externallogging.py (constructrsyslogconftemplate: :26 spoolDirectory, :100 errorfile, :128 target); awx/main/conf.py (:565-574, :958-979); awx/conf/views.py (:131-133)
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.validateenvvarallowed (awx/main/fields.py:689-701) rejects names beginning with "ANSIBLE" and names present in the ENVBLOCKLIST 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, VIRTUALENV and all ANSIBLE configuration variables). The deny-list is incomplete: it does not include BASHENV, ENV, LDPRELOAD, LDLIBRARYPATH, LDAUDIT, PYTHONSTARTUP, PYTHONWARNINGS, GITSSHCOMMAND, 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 BASHENV 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 (awxplugins.interfaces temporaryprivateinjectapi.py, which re-checks only ENVBLOCKLIST), 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) / awxplugins.interfaces Affected files: awx/main/fields.py:689-701; awx/main/constants.py:48-70; awxplugins/interfaces/temporaryprivateinjectapi.py:263-288
A flaw was found in automation-controller (AWX). In WorkflowJobTemplateNodeAccess (awx/main/access.py), canchange() delegates the execute-permission check on a node's unifiedjobtemplate to ujtexecute(), which returns True unconditionally when the node's current obj.unifiedjobtemplate is None, without ever validating the incoming data['unifiedjobtemplate'] against executerole. canadd() does not mark unifiedjobtemplate as mandatory, so a node can be created with unifiedjobtemplate=null (HTTP 201). PATCHing that empty node to any UnifiedJobTemplate id then succeeds (HTTP 200) with no executerole check on the new value. The direct-create path (POST a node with unifiedjobtemplate set) is correctly denied (403) via canadd -> checkrelated(rolefield= 'executerole'); the create-empty-then-PATCH sequence bypasses that guard. Consequently a user with adminrole on any single WorkflowJobTemplate (an organization admin, an Organization Workflow Admin, or an object-level WFJT admin) can bind and execute any Job Template, Project update, Inventory Source sync, System Job Template, or nested Workflow Job Template belonging to any other organization/tenant -- even ones for which direct GET/launch returns 403 -- running the victim template with the victim's attached credentials, inventory and project. The PATCH response summaryfields additionally leaks the victim template's name and description. Discovered internally (pentest-aap); verified live on AAP 2.7 / automation-controller 4.8.x (victim WFJT executed end-to-end, launchedby attacker); still present on devel. Upstream: github.com/ansible/awx (awx/main/access.py: WorkflowJobTemplateNodeAccess.ujtexecute / canadd / canchange; checkrelated both-None handling).
A flaw was found in automation-controller (AWX). In WorkflowJobTemplateNodeAccess (awx/main/access.py), canchange() delegates the execute-permission check on a node's unifiedjobtemplate to ujtexecute(), which returns True unconditionally when the node's current obj.unifiedjobtemplate is None, without ever validating the incoming data['unifiedjobtemplate'] against executerole. canadd() does not mark unifiedjobtemplate as mandatory, so a node can be created with unifiedjobtemplate=null (HTTP 201). PATCHing that empty node to any UnifiedJobTemplate id then succeeds (HTTP 200) with no executerole check on the new value. The direct-create path (POST a node with unifiedjobtemplate set) is correctly denied (403) via canadd -> checkrelated(rolefield= 'executerole'); the create-empty-then-PATCH sequence bypasses that guard. Consequently a user with adminrole on any single WorkflowJobTemplate (an organization admin, an Organization Workflow Admin, or an object-level WFJT admin) can bind and execute any Job Template, Project update, Inventory Source sync, System Job Template, or nested Workflow Job Template belonging to any other organization/tenant -- even ones for which direct GET/launch returns 403 -- running the victim template with the victim's attached credentials, inventory and project. The PATCH response summaryfields additionally leaks the victim template's name and description. Discovered internally (pentest-aap); verified live on AAP 2.7 / automation-controller 4.8.x (victim WFJT executed end-to-end, launchedby attacker); still present on devel. Upstream: github.com/ansible/awx (awx/main/access.py: WorkflowJobTemplateNodeAccess.ujtexecute / canadd / canchange; checkrelated both-None handling).
A flaw was found in automation-controller (AWX). The AWXTASKENV setting is protected by a validator that rejects environment-variable keys which manipulate dynamic linkers or interpreters (LD, DYLD, PYTHON, PERL5OPT). The sibling GALAXYTASKENV setting (awx/main/conf.py) is registered as a bare KeyValueField with no such validator. Its value is passed verbatim into project-update extravars (awx/main/tasks/jobs.py RunProjectUpdate.buildextravarsfile -> 'galaxytaskenv') and injected into the environment of the ansible-galaxy command tasks in awx/playbooks/projectupdate.yml (environment: "{{ galaxytaskenv | combine(additionalgalaxyenv) }}"). A system administrator can set GALAXYTASKENV to include LDPRELOAD or PYTHONPATH referencing a file in a project checkout under the shared /var/lib/awx/projects volume, achieving arbitrary code execution inside the project-sync execution environment on the control-plane task pod on the next sync of any project with a requirements file. The container has read/write access to all organizations' checked-out project source and to injected ANSIBLEGALAXYSERVERTOKEN values, enabling cross- tenant tampering and token theft, and constitutes a bypass of the linker/ interpreter blocklist AWX enforces on AWXTASKENV. Note: in ansible/awx upstream (devel) neither AWXTASKENV nor GALAXYTASKENV currently carries the blocklist -- the AWXTASKENV control is a downstream patch; both must be present upstream to prevent regression. Discovered internally; verified live on AAP 2.7 / automation-controller 4.8.1. Upstream: github.com/ansible/awx (awx/main/conf.py GALAXYTASKENV; awx/main/tasks/jobs.py RunProjectUpdate.buildextravarsfile; awx/playbooks/projectupdate.yml)
A flaw was found in automation-controller (AWX). The external credential test endpoints execute the credential plugin backend in-process in the controller web service (awx/api/views/init.py CredentialExternalTest / CredentialTypeExternalTest -> callbackendwitherrorhandling -> plugin.backend()). CredentialExternalTest uses objpermissiontype='use' and merges request-body 'inputs' over the stored credential inputs, so a user with only the use role can override serverurl. The Thycotic Secret Server plugin (awx-plugins src/awxplugins/credentials/tss.py tssbackend) passes serverurl verbatim into the delinea/thycotic SDK, which issues GET {serverurl}/api/v1/ healthcheck and GET {serverurl}/health and, on a healthy response, POST {serverurl}/oauth2/token, with no allow-list, scheme, IP-range, or DNS-rebind check. Errors are wrapped as a bare SecretServerError (blind), but latency differs by target state (refused ~0.5s vs filtered ~12s until the gateway 503s), yielding a reliable timing oracle for scanning in-cluster services, the Kubernetes API, link-local metadata (169.254.169.254), and localhost daemons from the control-plane pod. This is the same root-cause class as the previously reported HashiCorp Vault / Centrify / CyberArk CCP credential-plugin SSRFs; Thycotic Secret Server is an additional sink. Discovered internally; verified live on AAP 2.6/2.7 / automation-controller 4.8.1; still present on devel (both awx and awx-plugins). Upstream: github.com/ansible/awx (awx/api/views/init.py CredentialExternalTest, CredentialTypeExternalTest, callbackendwitherrorhandling); github.com/ansible/awx-plugins (src/awxplugins/credentials/tss.py tssbackend)
A flaw was found in automation-controller (AWX). The external credential test endpoints execute the credential plugin backend in-process in the controller web service (awx/api/views/init.py CredentialExternalTest / CredentialTypeExternalTest -> callbackendwitherrorhandling -> plugin.backend()). CredentialExternalTest uses objpermissiontype='use' and merges request-body 'inputs' over the stored credential inputs, so a user with only the use role can override serverurl. The Thycotic Secret Server plugin (awx-plugins src/awxplugins/credentials/tss.py tssbackend) passes serverurl verbatim into the delinea/thycotic SDK, which issues GET {serverurl}/api/v1/ healthcheck and GET {serverurl}/health and, on a healthy response, POST {serverurl}/oauth2/token, with no allow-list, scheme, IP-range, or DNS-rebind check. Errors are wrapped as a bare SecretServerError (blind), but latency differs by target state (refused ~0.5s vs filtered ~12s until the gateway 503s), yielding a reliable timing oracle for scanning in-cluster services, the Kubernetes API, link-local metadata (169.254.169.254), and localhost daemons from the control-plane pod. This is the same root-cause class as the previously reported HashiCorp Vault / Centrify / CyberArk CCP credential-plugin SSRFs; Thycotic Secret Server is an additional sink. Discovered internally; verified live on AAP 2.6/2.7 / automation-controller 4.8.1; still present on devel (both awx and awx-plugins). Upstream: github.com/ansible/awx (awx/api/views/init.py CredentialExternalTest, CredentialTypeExternalTest, callbackendwitherrorhandling); github.com/ansible/awx-plugins (src/awxplugins/credentials/tss.py tssbackend)
A flaw was found in automation-controller (AWX). InventorySourceOptions. sourcevars (awx/main/models/inventory.py) is declared as a plain TextField without a preventsearch() wrapper, unlike protected fields such as CustomInventoryScript.script and Credential.inputs. django-ansible-base's FieldLookupBackend permits contains/icontains/startswith/regex lookups that traverse model joins without per-hop RBAC. Because the credentialtypes list endpoint is readable by any authenticated user, an attacker holding zero roles/organization/superuser/auditor privileges -- who is correctly denied on the inventorysources endpoints -- can query credentialtypes?credentialsorganizationinventoriesinventorysources sourcevarscontains=<needle> (and the inventoryupdates variant) and read the result count as a boolean oracle (count>0 iff some tenant's sourcevars contains the needle). Using regex the full plaintext is recovered efficiently. sourcevars is the documented location for inline dynamic- inventory-plugin authentication (e.g. awsaccesskey/awssecretkey, subscriptionid/clientid/secret/tenant, username/password, token, apikey), so the flaw discloses cross-tenant cloud-provider credentials to a zero- privilege user. The field is defined on InventorySourceOptions and inherited by InventorySource and InventoryUpdate. This is an instance of a broader class in which unfiltered FieldLookupBackend joins on the globally-readable credentialtypes list reach fields lacking preventsearch. Discovered internally; verified live on AAP 2.7 / automation-controller 4.8.1. Upstream: github.com/ansible/awx (main/models/inventory.py) + django-ansible-base (FieldLookupBackend)