# Root Cause Analysis: CVE-2026-84502

## Summary

CVE-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.

## Impact

- **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.
- **Tested affected build:** AWX commit `a5b2179a3e693dd8e9f4c186636d93f9a436ada4` (`24.6.2.dev949+ga5b2179a3`).
- **Affected product family:** Red Hat Ansible Automation Platform Automation Controller / upstream AWX versions that accept dash-prefixed Git SCM URLs.
- **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.

## Impact Parity

- **Disclosed/claimed maximum impact:** Authenticated remote code execution through the AWX API.
- **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`.
- **Parity:** `full`
- **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.

## Root Cause

AWX'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.

The 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.

## Reproduction Steps

1. Run `bundle/repro/reproduction_steps.sh` from any directory. The script derives its bundle path portably and uses the prepared project cache when available.
2. 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.
3. 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.
4. 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.
5. 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`.

## Evidence

The latest successful current-run proof is under `bundle/repro/proof-20260924T032301Z-28737/` and is bound by `bundle/repro/runtime_manifest.json`.

Key artifacts include:

- `vulnerable-1-create-request.json` / `vulnerable-1-create-response.json`: API acceptance of the first dash-prefixed SCM URL with HTTP 201.
- `vulnerable-1-update-response.json` and `vulnerable-1-update-status.txt`: real ProjectUpdate launch with HTTP 202.
- `vulnerable-1-stdout.txt`: ProjectUpdate output showing `git ls-remote` with the injected `--upload-pack` value.
- `vulnerable-1-marker.txt`: target-local output `root` from attacker-selected `whoami`.
- `vulnerable-2-marker.txt`: target-local output `awx-1` from attacker-selected `hostname`.
- `fixed-1-create-response.json` and `fixed-2-create-response.json`: `{"scm_url":["SCM URL must not begin with a dash."]}` with HTTP 400.
- `fixed-1-marker-negative-control.txt` and `fixed-2-marker-negative-control.txt`: marker absence recorded after the fixed requests reached the API.
- `target-commit.txt`, `target-digest.txt`, `awx-image-digest.txt`, `ee-image-digest.txt`, and `controller-inspect.json`: immutable source and runtime identity evidence.
- `bundle/logs/reproduction_steps.log`: diagnostic transcript. Its latest run records API health, both vulnerable successes, both fixed rejections, and final confirmation.

Latest transcript excerpts:

```text
[+] AWX API healthy
[+] Vulnerable attempt 1: create=201 update=202 marker=awxv117902202139203
[+] Vulnerable attempt 2: create=201 update=202 marker=awxv217902202233650
[+] Fixed attempt 1: create=400 marker absent
[+] Fixed attempt 2: create=400 marker absent
[+] CONFIRMED CVE-2026-84502 through the real AWX API and ProjectUpdate task path
```

The 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.

## Recommendations / Next Steps

1. Reject Git SCM URLs beginning with `-` before persistence and before any ProjectUpdate scheduling.
2. 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.
3. Avoid shell command construction for SCM subprocesses and pass a fixed argument vector with `shell=False` semantics.
4. Upgrade affected Automation Controller deployments to the Red Hat release containing RHSA-2026:71115 or a vendor-confirmed later fixed build.
5. 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.
6. 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.
7. Review ProjectUpdate execution-environment credentials and cluster permissions because successful exploitation occurs inside a control-plane task context.

## Additional Notes

- **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.
- **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.
- **Authentication precondition:** Exploitation requires an authenticated user authorized to create a Git Project and trigger its update.
- **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.
- **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.
