{"repro_id":"REPRO-2026-00359","version":6,"title":"CVE-2026-84502: Ansible Automation Platform automation-controller — Project scm_url argument injection into git ls-remote --upload-pack yields RCE on controller task pod","repro_type":"security","status":"published","severity":"critical","description":"CVE-2026-84502 (GHSA-vpmq-jxf9-c55v; Red Hat Bugzilla 2527096; fix: RHSA-2026:71115, published 2026-09-23). Critical, CVSS v3.1 base 9.9 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H), CWE-88 (argument injection).","root_cause":"# Root Cause Analysis: CVE-2026-84502\n\n## Summary\n\nCVE-2026-84502 is an authenticated argument-injection vulnerability in Ansible AWX / Automation Controller Project SCM handling. AWX commit `a5b2179a3e693dd8e9f4c186636d93f9a436ada4` accepts a Git Project `scm_url` beginning with `-`. During a real ProjectUpdate, that value reaches Git as an option rather than a repository URL. An attacker-controlled `--upload-pack=<command>` value therefore selects a shell command that Git executes, yielding command execution in the ProjectUpdate execution environment.\n\n## Impact\n\n- **Affected component:** `awx/main/models/projects.py`, specifically Project `scm_url` validation, together with the AWX ProjectUpdate path and the Ansible Git module's `git ls-remote` invocation.\n- **Tested affected build:** AWX commit `a5b2179a3e693dd8e9f4c186636d93f9a436ada4` (`24.6.2.dev949+ga5b2179a3`).\n- **Affected product family:** Red Hat Ansible Automation Platform Automation Controller / upstream AWX versions that accept dash-prefixed Git SCM URLs.\n- **Risk:** Critical. An authenticated user able to create a Git Project and launch its update can execute attacker-selected commands in the controller's ProjectUpdate execution environment, threatening confidentiality, integrity, availability, tenant separation, and adjacent in-cluster resources.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Authenticated remote code execution through the AWX API.\n- **Reproduced impact:** Two independent real API ProjectUpdate attempts executed attacker-selected commands and created unique target-local command-output markers. The first marker contained `root` from `whoami`; the second contained `awx-1` from `hostname`.\n- **Parity:** `full`\n- **Not demonstrated:** No claimed impact remains unproved. The proof did not attempt persistence, lateral movement, credential theft, or destructive activity because command execution itself establishes the claimed impact.\n\n## Root Cause\n\nAWX's vulnerable `Project.clean_scm_url()` does not reject an `scm_url` whose first character is `-`. The API therefore stores input such as `--upload-pack=whoami>MARKER;:x` as if it were a normal Git URL. A ProjectUpdate passes that value into the Ansible Git path, which invokes `git ls-remote` without terminating option processing before the attacker-controlled positional value. Git interprets `--upload-pack=...` as its upload-pack option and uses the supplied string as a shell command. The `:x` suffix supplies the trailing argument shape needed by the resulting invocation.\n\nThe effective remediation tested in this run rejects leading-dash values in `Project.clean_scm_url()` with `ValidationError(_('SCM URL must not begin with a dash.'))`. With that validation active, both same-class control requests failed closed with HTTP 400 and no marker. The ticket identifies the downstream fix as RHSA-2026:71115; an upstream fix commit was not supplied, so the reproduction applies the precise validation change as a runtime negative control.\n\n## Reproduction Steps\n\n1. Run `bundle/repro/reproduction_steps.sh` from any directory. The script derives its bundle path portably and uses the prepared project cache when available.\n2. The script checks out exact AWX commit `a5b2179a3e693dd8e9f4c186636d93f9a436ada4`, starts the real upstream AWX Docker Compose deployment, waits for the HTTPS API, configures a digest-bound minimal control-plane execution environment, and authenticates to the API.\n3. It creates two distinct Git Projects whose `scm_url` values begin with attacker-controlled `--upload-pack=` options, launches each ProjectUpdate through `POST /api/v2/projects/<id>/update/`, and verifies unique command-output marker files in the real ProjectUpdate target path.\n4. It then applies the leading-dash validation remediation, reloads API workers, and submits two fresh same-class requests. Both controls must return HTTP 400 and produce no marker.\n5. Expected terminal output includes two `Vulnerable attempt` lines, two `Fixed attempt` lines, and `CONFIRMED CVE-2026-84502 through the real AWX API and ProjectUpdate task path`.\n\n## Evidence\n\nThe latest successful current-run proof is under `bundle/repro/proof-20260924T032301Z-28737/` and is bound by `bundle/repro/runtime_manifest.json`.\n\nKey artifacts include:\n\n- `vulnerable-1-create-request.json` / `vulnerable-1-create-response.json`: API acceptance of the first dash-prefixed SCM URL with HTTP 201.\n- `vulnerable-1-update-response.json` and `vulnerable-1-update-status.txt`: real ProjectUpdate launch with HTTP 202.\n- `vulnerable-1-stdout.txt`: ProjectUpdate output showing `git ls-remote` with the injected `--upload-pack` value.\n- `vulnerable-1-marker.txt`: target-local output `root` from attacker-selected `whoami`.\n- `vulnerable-2-marker.txt`: target-local output `awx-1` from attacker-selected `hostname`.\n- `fixed-1-create-response.json` and `fixed-2-create-response.json`: `{\"scm_url\":[\"SCM URL must not begin with a dash.\"]}` with HTTP 400.\n- `fixed-1-marker-negative-control.txt` and `fixed-2-marker-negative-control.txt`: marker absence recorded after the fixed requests reached the API.\n- `target-commit.txt`, `target-digest.txt`, `awx-image-digest.txt`, `ee-image-digest.txt`, and `controller-inspect.json`: immutable source and runtime identity evidence.\n- `bundle/logs/reproduction_steps.log`: diagnostic transcript. Its latest run records API health, both vulnerable successes, both fixed rejections, and final confirmation.\n\nLatest transcript excerpts:\n\n```text\n[+] AWX API healthy\n[+] Vulnerable attempt 1: create=201 update=202 marker=awxv117902202139203\n[+] Vulnerable attempt 2: create=201 update=202 marker=awxv217902202233650\n[+] Fixed attempt 1: create=400 marker absent\n[+] Fixed attempt 2: create=400 marker absent\n[+] CONFIRMED CVE-2026-84502 through the real AWX API and ProjectUpdate task path\n```\n\nThe immediately preceding successful proof directory, `bundle/repro/proof-20260924T032109Z-27250/`, independently contains the same two-vulnerable/two-fixed divergence, satisfying consecutive-run repeatability. No sanitizer was used.\n\n## Recommendations / Next Steps\n\n1. Reject Git SCM URLs beginning with `-` before persistence and before any ProjectUpdate scheduling.\n2. Where supported, add an explicit `--` option terminator before untrusted positional arguments passed to Git; input validation and safe argument construction should be layered rather than treated as alternatives.\n3. Avoid shell command construction for SCM subprocesses and pass a fixed argument vector with `shell=False` semantics.\n4. Upgrade affected Automation Controller deployments to the Red Hat release containing RHSA-2026:71115 or a vendor-confirmed later fixed build.\n5. Add API and model tests covering leading `-`, encoded or whitespace-adjacent variants, update of an existing Project, and every SCM operation that consumes the URL.\n6. Add an end-to-end regression test that proves malicious Project creation/update is rejected before an execution job starts and that no filesystem or callback marker appears.\n7. Review ProjectUpdate execution-environment credentials and cluster permissions because successful exploitation occurs inside a control-plane task context.\n\n## Additional Notes\n\n- **Idempotency:** The reproducer generated unique Projects and marker names for every attempt and completed successfully in at least two consecutive current-run executions. It restores the vulnerable source after the fixed controls.\n- **Boundary:** This is a production-path proof through the running AWX HTTPS API and the real ProjectUpdate/ansible-runner/Git path, not a direct parser or Git-only harness.\n- **Authentication precondition:** Exploitation requires an authenticated user authorized to create a Git Project and trigger its update.\n- **Environment adaptation:** The script uses a digest-pinned AWX image and a bounded minimal execution environment to support nested container execution. Those adaptations preserve the affected AWX API and ProjectUpdate semantics.\n- **Artifact size:** Large controller diagnostic output is retained only as non-promoted diagnostics and is not required as proof; compact request, response, stdout, identity, marker, and negative-control artifacts provide the verdict evidence.\n","cve_id":"CVE-2026-84502","cwe_id":"CWE-88","source_url":"https://access.redhat.com/security/cve/CVE-2026-84502","package":{"name":"ansible/awx","ecosystem":"github"},"reproduced_at":"2026-09-24T07:20:51.591072+00:00","duration_secs":4135.0,"tool_calls":633,"handoffs":3,"total_cost_usd":33.5617,"agent_costs":{"claim_matcher":0.030583,"judge":0.925811,"learning_policy":0.014985,"repro":28.510867,"support":0.317528,"vuln_variant":3.761926},"cost_breakdown":{"claim_matcher":{"gpt-5.4-mini-2026-03-17":0.030583},"judge":{"gpt-5.6-sol":0.925811},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.014985},"repro":{"gpt-5.6-sol":28.510867},"support":{"gpt-5.6-sol":0.317528},"vuln_variant":{"gpt-5.6-sol":3.761926}},"vulnerable_version_variant_outcome":"unknown","fix_bypass_outcome":"unknown","variant_disclosure_state":"unknown","quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-09-24T07:20:52.935961+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":8272,"category":"analysis"},{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":19361,"category":"reproduction_script"},{"path":"bundle/repro/proof-20260924T032301Z-28737/awx-image-digest.txt","filename":"awx-image-digest.txt","size":72,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/controller-inspect.json","filename":"controller-inspect.json","size":20830,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/ee-image-digest.txt","filename":"ee-image-digest.txt","size":65,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-1-create-request.json","filename":"fixed-1-create-request.json","size":143,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-1-create-response.json","filename":"fixed-1-create-response.json","size":51,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-1-create-status.txt","filename":"fixed-1-create-status.txt","size":4,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-1-marker-name.txt","filename":"fixed-1-marker-name.txt","size":20,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-1-marker-negative-control.txt","filename":"fixed-1-marker-negative-control.txt","size":7,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-2-create-request.json","filename":"fixed-2-create-request.json","size":144,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-2-create-response.json","filename":"fixed-2-create-response.json","size":51,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-2-create-status.txt","filename":"fixed-2-create-status.txt","size":4,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-2-marker-name.txt","filename":"fixed-2-marker-name.txt","size":21,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/fixed-2-marker-negative-control.txt","filename":"fixed-2-marker-negative-control.txt","size":7,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/healthcheck-request.txt","filename":"healthcheck-request.txt","size":49,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/healthcheck-response.json","filename":"healthcheck-response.json","size":859,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/runtime-configuration.txt","filename":"runtime-configuration.txt","size":105,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/service-start-command.txt","filename":"service-start-command.txt","size":141,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/target-commit.txt","filename":"target-commit.txt","size":41,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/target-digest.txt","filename":"target-digest.txt","size":65,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-1-create-response.json","filename":"vulnerable-1-create-response.json","size":2829,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-1-create-status.txt","filename":"vulnerable-1-create-status.txt","size":4,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-1-marker-name.txt","filename":"vulnerable-1-marker-name.txt","size":20,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-1-update-detail.json","filename":"vulnerable-1-update-detail.json","size":3892,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-1-update-request.json","filename":"vulnerable-1-update-request.json","size":3,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-1-update-status.txt","filename":"vulnerable-1-update-status.txt","size":4,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-create-request.json","filename":"vulnerable-2-create-request.json","size":144,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-create-response.json","filename":"vulnerable-2-create-response.json","size":2831,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-create-status.txt","filename":"vulnerable-2-create-status.txt","size":4,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-marker-name.txt","filename":"vulnerable-2-marker-name.txt","size":20,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-stdout.txt","filename":"vulnerable-2-stdout.txt","size":1545,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-update-detail.json","filename":"vulnerable-2-update-detail.json","size":3894,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-update-request.json","filename":"vulnerable-2-update-request.json","size":3,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-update-response.json","filename":"vulnerable-2-update-response.json","size":2013,"category":"other"},{"path":"bundle/repro/proof-20260924T032301Z-28737/vulnerable-2-update-status.txt","filename":"vulnerable-2-update-status.txt","size":4,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":9361,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1367,"category":"other"}]}