# REPRO-2026-00185: HashiCorp Nomad: path traversal in host volume plugin loader → client-host RCE ## Summary Status: published Severity: high Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00185 GHSA: GHSA-hx53-77qj-8663 CVE: CVE-2026-7474 ## Package Name: nomad Ecosystem: go Affected: prior to v2.0.1 (confirmed at v2.0.0, v1.10.5, v1.11.2) Fixed: v2.0.1 ## Root Cause # RCA Report: CVE-2026-7474 ## Summary CVE-2026-7474 is a path traversal vulnerability in HashiCorp Nomad's dynamic host volume plugin loader that allows arbitrary command execution on Nomad client nodes. When an authenticated user with `host-volume-create` ACL capability submits a `HostVolume.Create` request with a malicious `PluginID` containing directory traversal sequences (e.g., `../../../../bin/ls`) and an explicit `NodeID`, the server bypasses plugin feasibility checks and forwards the request to the target client. The client's `NewHostVolumePluginExternal` function uses `filepath.Join(pluginDir, filename)` followed by `os.Stat`, which resolves the traversal to an arbitrary executable outside the configured plugin directory. That executable is later invoked via `exec.CommandContext` during volume `Create`, `Fingerprint`, or `Delete` operations. ## Impact - **Package/component affected:** `github.com/hashicorp/nomad/client/hostvolumemanager` (`host_volume_plugin.go`), `github.com/hashicorp/nomad/nomad` (`host_volume_endpoint.go`) - **Affected versions:** Prior to `v2.0.1` (confirmed vulnerable in `v2.0.0`, `v1.10.5`, `v1.11.2`) - **Fixed versions:** `v2.0.1` - **Risk level:** High (CVSS 3.1: 8.8) - **Consequences:** Authenticated attackers with namespace-level `host-volume-create` ACLs can force any Nomad client node to execute arbitrary binaries from the host filesystem as the Nomad client user, leading to full client-node compromise. ## Root Cause The vulnerability stems from two missing validations in the host volume create workflow: 1. **Client-side path traversal (CWE-22):** `NewHostVolumePluginExternal` in `client/hostvolumemanager/host_volume_plugin.go` (v2.0.0, ~line 224) constructs the plugin executable path with `filepath.Join(pluginDir, filename)` and then calls `os.Stat(executable)`. Because `filepath.Join` does not reject `../` sequences, a malicious `PluginID` resolves to any path on the filesystem. If the target file exists and is executable, the client stores the escaped path and later invokes it via `exec.CommandContext`. 2. **Server-side feasibility bypass:** In `nomad/host_volume_endpoint.go`, the `placeHostVolume` function skips the plugin feasibility constraint (`${attr.plugins.host_volume..version} is_set`) when the user explicitly provides a `NodeID`. This was intended as an optimization, but it allows an attacker to force placement onto a node that does not legitimately advertise the plugin, bypassing the only server-side guard that could block the malicious request. The fix commit (`cd7240c4099ad33eda279924fb3a9459b162d120`, released in `v2.0.1`) addresses both issues: - **Client-side:** Replaces `filepath.Join` + `os.Stat` with `os.OpenRoot(pluginDir)` and `root.Stat(filename)`. On Go 1.24+, `os.Root.Stat` rejects paths that escape the root directory, returning an error that is mapped to `ErrPluginNotExists`. - **Server-side:** Moves the plugin feasibility constraint so it is checked **even when the request already specifies an explicit `NodeID`**, preventing traversal payloads from being forwarded to the client. ## Reproduction Steps The reproduction script is `repro/reproduction_steps.sh`. It performs the following steps: 1. Clones the HashiCorp Nomad repository (if not already present) and checks out both `v2.0.0` (vulnerable) and `v2.0.1` (fixed). 2. Builds the `nomad` binary for each tag (`bin/nomad-vuln` and `bin/nomad-fixed`). 3. Writes a minimal `nomad agent -dev` configuration that enables both server and client roles, binds to `127.0.0.1`, and sets a `host_volume_plugin_dir`. 4. Starts the vulnerable agent, waits for it to be healthy, and queries its NodeID via `/v1/nodes`. 5. Sends a malicious `PUT /v1/volume/host/create` request with: - `PluginID: "../../../../bin/ls"` - Explicit `NodeID` - Minimal capacity constraints 6. Captures the HTTP response body and code into `logs/vulnerable_api.txt`. 7. Stops the vulnerable agent, frees ports, and repeats steps 4–6 with the fixed binary. 8. Captures the fixed response into `logs/fixed_api.txt`. 9. Writes `repro/runtime_manifest.json` with both results. 10. Verifies that the vulnerable run shows evidence of `/bin/ls` execution (`exit status 2`) while the fixed run shows a server-side placement rejection (`node ... is not feasible for volume`). ### Expected evidence - **Vulnerable (`v2.0.0`):** HTTP 500 response containing: `HostVolume.Create error: error creating volume "" with plugin "../../../../bin/ls": exit status 2` This proves the server forwarded the request, the client resolved the traversal to `/bin/ls`, and executed `/bin/ls create` (which exits with code 2 because `ls` does not understand the `create` subcommand). - **Fixed (`v2.0.1`):** HTTP 500 response containing: `could not place volume "cve-test-vol": node is not feasible for volume` This proves the server now enforces the plugin feasibility constraint even with an explicit `NodeID`, rejecting the request before it ever reaches the client. ## Evidence - `logs/vulnerable_api.txt` — API response from the vulnerable agent run. - `logs/fixed_api.txt` — API response from the fixed agent run. - `repro/runtime_manifest.json` — Structured manifest with payload, endpoint, and captured HTTP codes for both versions. - `external/nomad/bin/nomad-vuln` and `external/nomad/bin/nomad-fixed` — Compiled Nomad binaries used during reproduction. ### Key excerpts Vulnerable response (`logs/vulnerable_api.txt`): ``` HostVolume.Create error: error creating volume "f09840bb-a5d8-0b96-39c6-77deafd35c19" with plugin "../../../../bin/ls": exit status 2 HTTP_CODE:500 ``` Fixed response (`logs/fixed_api.txt`): ``` could not place volume "cve-test-vol": node 33225465-afa9-a5c1-2acf-02c0de00879a is not feasible for volume HTTP_CODE:500 ``` ## Recommendations / Next Steps 1. **Upgrade immediately:** All Nomad deployments should be upgraded to `v2.0.1` or later. The fix includes both client-side `os.OpenRoot` containment and server-side feasibility enforcement. 2. **ACL hardening:** Restrict `host-volume-create` capabilities to the smallest set of namespaces and users that genuinely require it. Even with the fix, the capability is powerful. 3. **Network segmentation:** Place Nomad client agents on isolated networks so that even a compromised client cannot easily pivot to other infrastructure. 4. **Monitoring:** Alert on host volume create requests that reference unusual `PluginID` values (e.g., containing `..`, `/`, or absolute paths) as a defense-in-depth measure. 5. **Testing:** Add regression tests that verify `os.Root.Stat` rejects traversal sequences and that `placeHostVolume` always checks plugin feasibility regardless of whether `NodeID` is explicit. ## Additional Notes - **Idempotency:** The reproduction script was executed twice consecutively and produced identical differential behavior on both runs (vulnerable → `exit status 2`, fixed → `not feasible`). - **Environment:** Reproduction performed in an Ubuntu container with Go 1.24.7. A cgroup workaround (`mount -t tmpfs` over `/sys/fs/cgroup/cpuset`) was required because the container lacked the `cpuset.mems` file expected by Nomad's cgroup initialization. - **Limitations:** The reproduction demonstrates the traversal through the actual HTTP API and real agent binaries. The `exit status 2` artifact proves command execution of the escaped path. A fully successful volume creation would require a compliant plugin executable; using `/bin/ls` intentionally produces a failure that still proves the vulnerability. ## Reproduction Details Reproduced: 2026-05-28T19:26:49.817Z Duration: 2890 seconds Tool calls: 337 Turns: 306 Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00185 pruva-verify GHSA-hx53-77qj-8663 pruva-verify CVE-2026-7474 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00185&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00185/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/advisories/GHSA-hx53-77qj-8663 - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-7474 - Source: https://github.com/hashicorp/nomad ## Artifacts - bundle/repro/rca_report.md (analysis, 7599 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 6240 bytes) - bundle/vuln_variant/rca_report.md (analysis, 6623 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 3280 bytes) - bundle/context.json (other, 3466 bytes) - bundle/metadata.json (other, 865 bytes) - bundle/ticket.md (ticket, 7258 bytes) - bundle/repro/runtime_manifest.json (other, 435 bytes) - bundle/repro/validation_verdict.json (other, 3320 bytes) - bundle/repro/cve_repro.go (other, 804 bytes) - bundle/vuln_variant/variant_test.go (other, 1800 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1282 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 4801 bytes) - bundle/vuln_variant/variant_manifest.json (other, 2705 bytes) - bundle/vuln_variant/validation_verdict.json (other, 2933 bytes) - bundle/vuln_variant/source_identity.json (other, 647 bytes) - bundle/logs/vulnerable_api.txt (other, 148 bytes) - bundle/logs/nomad_v2.0.1_stderr.txt (other, 291 bytes) - bundle/logs/nomad-fixed_stderr.txt (other, 291 bytes) - bundle/logs/nomad-vuln_stderr.txt (other, 291 bytes) - bundle/logs/nomad-fixed_stdout.txt (other, 6857 bytes) - bundle/logs/vuln_variant/variant_fixed.txt (other, 421 bytes) - bundle/logs/vuln_variant/variant_vuln.txt (other, 363 bytes) - bundle/logs/retry_run.txt (other, 1071 bytes) - bundle/logs/variant_fixed.txt (other, 421 bytes) - bundle/logs/nomad_v2.0.0_stderr.txt (other, 291 bytes) - bundle/logs/fixed_api.txt (other, 122 bytes) - bundle/logs/nomad_config.hcl (other, 329 bytes) - bundle/logs/fixed_run.txt (other, 46 bytes) - bundle/logs/variant_vuln.txt (other, 363 bytes) - bundle/logs/nomad_v2.0.0_stdout.txt (other, 6373 bytes) - bundle/logs/vulnerable_run.txt (other, 158 bytes) - bundle/logs/nomad-vuln_stdout.txt (other, 6373 bytes) - bundle/logs/nomad_v2.0.1_stdout.txt (other, 6130 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00185 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00185/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00185 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev