Skip to content

CVE-2026-7474: Verified Repro With Script Download

CVE-2026-7474: HashiCorp Nomad: path traversal in host volume plugin loader → client-host RCE

CVE-2026-7474 is verified against nomad · go. Affected versions: prior to v2.0.1 (confirmed at v2.0.0, v1.10.5, v1.11.2). Fixed in v2.0.1. Vulnerability class: RCE. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00185.

REPRO-2026-00185 nomad · go RCE Variant found May 28, 2026 CVE entry ↗ .txt
Severity
HIGH
CVSS
8.8
Confidence
HIGH
Reproduced in
48m 9s
Tool calls
337
Spend
$3.48
01 · Overview

What Is CVE-2026-7474?

CVE-2026-7474 (GHSA-hx53-77qj-8663) is a high-severity path traversal vulnerability in HashiCorp Nomad's dynamic host volume plugin loader that allows arbitrary command execution on Nomad client nodes. Pruva reproduced it (reproduction REPRO-2026-00185).

02 · Severity & CVSS

CVE-2026-7474 Severity & CVSS Score

CVE-2026-7474 is rated high severity, with a CVSS base score of 8.8 out of 10.

HIGH threat level
8.8 / 10 CVSS base
Weakness CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected nomad Versions

nomad · go versions prior to v2.0.1 (confirmed at v2.0.0, v1.10.5, v1.11.2) are affected.

How to Reproduce CVE-2026-7474

$ pruva-verify REPRO-2026-00185
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00185/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-7474

Reproduced by Pruva's autonomous agents — 337 tool calls over 48 min. Full root-cause analysis and the complete transcript are below.

Variants tested

Variant analysis for CVE-2026-7474: tested path traversal, symlink escape, and Register->Delete chain against the fixed version v2.0.1. No bypass confirmed. Client-side os.OpenRoot blocks all direct traversal variants.

How the agent worked 1,258 events · 337 tool calls · 48 min
48 minDuration
337Tool calls
302Reasoning steps
1,258Events
8Dead-ends
Agent activity over 48 min
Support
18
Repro
749
Variant
487
0:0048:09

Root Cause and Exploit Chain for CVE-2026-7474

Versions: Prior to v2.0.1 (confirmed vulnerable in v2.0.0, v1.10.5, v1.11.2)Fixed: v2.0.1

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.

  • 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.<plugin>.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 "<id>" 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 <id> 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.

Variant Analysis & Alternative Triggers for CVE-2026-7474

Versions: v2.0.0 and earlier (vulnerable to variants); v2.0.1 (blocked)

CVE-2026-7474 is a path-traversal vulnerability in HashiCorp Nomad's dynamic host volume plugin loader. The original reproduction showed that a malicious PluginID containing ../ sequences could be passed through HostVolume.Create to the client, where filepath.Join(pluginDir, filename) resolved outside the plugin directory and allowed arbitrary code execution.

Variant analysis examined three distinct attack hypotheses against the fixed version (v2.0.1):

  1. Symlink bypass: A symlink inside the plugin directory pointing to an arbitrary executable outside.
  2. Register → Delete chain: Updating an existing volume's PluginID via HostVolume.Register, then deleting it to trigger execution.
  3. Path traversal with ..: The original payload tested directly against NewHostVolumePluginExternal.

All three direct traversal variants were blocked on v2.0.1 by the client-side os.OpenRoot containment in NewHostVolumePluginExternal. No bypass of the patched code path was found. The Register endpoint remains a latent variant path because it does not validate PluginID, but it cannot be exploited because the client-side sink is hardened.

Fix Coverage / Assumptions

The fix (cd7240c4099ad33eda279924fb3a9459b162d120) relies on two invariants:

  1. Client-side os.OpenRoot is unbypassable. root.Stat(filename) uses Linux openat2 with RESOLVE_IN_ROOT, which prevents .. resolution, absolute paths, and symlink escapes from leaving the opened directory. Empirical testing with Go 1.24+ confirmed this blocks ../, ../../bin/ls, absolute paths, and symlinks pointing outside the root.
  2. Server-side feasibility check blocks malicious requests before forwarding. Even when an explicit NodeID is provided, placeHostVolume now enforces the ${attr.plugins.host_volume.<plugin>.version} is_set constraint, preventing traversal payloads from reaching the client via the normal HostVolume.Create path.

The fix does not cover:

  • The HostVolume.Register endpoint, which does not validate PluginID for path traversal and does not enforce plugin feasibility.
  • Agent restart via restoreFromState, which loads PluginID from local disk state.

Variant / Alternate Trigger

Variant 1: Symlink escape via os.OpenRoot
  • Hypothesis: A symlink placed inside the plugin directory could point to /bin/ls. On v2.0.0, os.Stat followed the symlink and found the target executable. On v2.0.1, root.Stat with RESOLVE_IN_ROOT blocks symlinks that escape the root.
  • Entry point: Same as original (HostVolume.Create with PluginID = "symlink_plugin").
  • Sink: NewHostVolumePluginExternalroot.Stat → blocked.
Variant 2: Register → Delete chain
  • Hypothesis: HostVolume.Register allows updating an existing volume's PluginID because ValidateUpdate does not check PluginID changes and Register does not call placeHostVolume. After updating the PluginID to a traversal payload, HostVolume.Delete forwards the stored PluginID to the client.
  • Entry point: HostVolume.Register followed by HostVolume.Delete.
  • Sink: HostVolumeManager.DeletegetPluginNewHostVolumePluginExternal → blocked on v2.0.1 by os.OpenRoot.
Variant 3: Direct traversal payload
  • Hypothesis: Directly calling NewHostVolumePluginExternal with PluginID = "../../../../bin/ls".

  • Entry point: Any RPC path that reaches getPlugin (Create, Delete, restoreFromState).

  • Sink: NewHostVolumePluginExternal → blocked on v2.0.1.

  • Package/component: github.com/hashicorp/nomad/client/hostvolumemanager

  • Affected versions: v2.0.0 and earlier (vulnerable to variants); v2.0.1 (blocked)

  • Risk level on vulnerable version: High — arbitrary command execution as Nomad client user

  • Risk level on fixed version: None — variants are blocked at the client sink

Root Cause

The underlying root cause is the same as the original CVE: NewHostVolumePluginExternal resolves a user-supplied filename against a filesystem directory without constraining the resolution to that directory. The fix replaces filepath.Join + os.Stat with os.OpenRoot + root.Stat, which enforces the containment invariant at the kernel level via RESOLVE_IN_ROOT.

The Register endpoint is a latent variant path because it lacks the same input validation on PluginID, but it cannot be exploited on the fixed version because the client sink is hardened.

Reproduction Steps

  1. Run bash vuln_variant/reproduction_steps.sh
  2. The script:
    • Creates Git worktrees for v2.0.0 (vulnerable) and v2.0.1 (fixed)
    • Copies a Go test (variant_test_test.go) into both worktrees
    • Runs go test -run TestVariantAttempts on both
    • Compares results
  3. Expected evidence:
    • On v2.0.0: Variant 1 (traversal): err = <nil> and Variant 2 (symlink): err = <nil>
    • On v2.0.1: Variant 1 (traversal): err = no such plugin and Variant 2 (symlink): err = no such plugin

Evidence

  • logs/vuln_variant/variant_vuln.txt — test output from v2.0.0 showing traversal and symlink variants succeed
  • logs/vuln_variant/variant_fixed.txt — test output from v2.0.1 showing both variants blocked by os.OpenRoot
  • vuln_variant/patch_analysis.md — detailed diff analysis of the fix

Recommendations / Next Steps

  1. Harden the Register endpoint: Add PluginID validation to HostVolume.ValidateUpdate or HostVolume.Register to reject path traversal characters (.., /, \). This provides defense-in-depth in case the client-side os.OpenRoot is ever bypassed or not available (e.g., on older kernels without openat2).
  2. Normalize/sanitize PluginID early: Apply a basename-only validation (e.g., filepath.Base(pluginID) == pluginID && !strings.Contains(pluginID, "..")) at the API boundary before any storage or RPC forwarding.
  3. Audit other filepath.Join usages: Search the codebase for other places where user input is joined with a configured directory path to ensure consistent containment.

Additional Notes

  • Idempotency: The reproduction script was run twice consecutively and produced identical results.
  • Go version: Tests run with Go 1.24.7 on Linux amd64. os.OpenRoot requires kernel ≥5.6 for openat2 with RESOLVE_IN_ROOT.
  • Trust boundary: All tested variants require authenticated access (host-volume-create or host-volume-register ACL). The trust boundary is crossed when the attacker submits an API request that reaches the client RPC.

CVE-2026-7474 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:28
0:00
session startedaccounts/fireworks/models/kimi-k2p6 · cve-2026-7474 · cve-2026
0:29
0:29
0:29
error

Unknown error

0:32
0:32
web search
0:32
error

Unknown error

1:22
1:22
extract_facts
no facts extracted
1:23
1:23
1:23
supportrepro
1:26
1:26
1:26
1:27
1:27
1:27
1:27
1:28
1:28
1:28
1:28
08 · How to Fix

How to Fix CVE-2026-7474

Upgrade nomad · go to v2.0.1 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-7474 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-7474

How does the CVE-2026-7474 path-traversal attack work?

An authenticated user with the host-volume-create ACL capability submits a HostVolume.Create request with a malicious PluginID containing directory traversal sequences and an explicit NodeID. The server bypasses plugin feasibility checks and forwards a ClientHostVolume.Create RPC to the target client, which invokes the traversed, arbitrary executable via exec.CommandContext during Create, Fingerprint, or Delete operations, running attacker-chosen binaries from the host filesystem as the Nomad client user.

Which Nomad versions are affected by CVE-2026-7474, and where is it fixed?

Versions prior to v2.0.1 are affected, confirmed vulnerable in v2.0.0, v1.10.5, and v1.11.2. It is fixed in v2.0.1.

How severe is CVE-2026-7474?

It is rated high severity. Exploitation requires an authenticated user with namespace-level host-volume-create ACL capability, but they can force any Nomad client node to execute arbitrary binaries, leading to full client-node compromise.

How can I reproduce CVE-2026-7474?

Download the verified script from this page and run it in an isolated environment against Nomad v2.0.0 (or v1.10.5/v1.11.2) with host-volume-create ACL access. It submits a HostVolume.Create request with a path-traversing PluginID and an explicit NodeID, and shows the target client executing an arbitrary binary outside the configured plugin directory, then confirms v2.0.1 rejects the traversal.
11 · References

References for CVE-2026-7474

Authoritative sources for CVE-2026-7474 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.