On July 7, 2026, HashiCorp shipped Nomad 2.0.4 with a security entry that should make every platform team pause before it makes them patch:
docker: Enforce
allowed_modesorallow_privilegedrequirement to set host namespace modes in task. This is CVE-2026-14891.
Translated out of changelog-ese: until that release, a Nomad job could request the host's network, PID, or IPC namespace and the Docker task driver would simply allow it — even on a client whose operator had never opted into privileged workloads. No kernel exploit. No misconfiguration the operator chose. The gate that was supposed to stand between "run my container" and "join the host's namespaces" was not misconfigured; it was missing.
Here is the verdict up front: if you run multi-tenant workloads on Nomad, upgrade to 2.0.4 (or Enterprise 1.11.8 / 1.10.14), audit every job spec for host namespace modes, and treat this CVE as a lesson about where your scheduler enforces its trust boundary — because Nomad enforces it in the client-side task driver, while Kubernetes enforces it at the API server before scheduling ever happens. That architectural difference, more than the bug itself, is what should inform a self-hosted PaaS choosing between Nomad's operational simplicity and Cluster API's Kubernetes-native isolation stack. The rest of this post is the receipts.
The missing gate, concretely
Nomad's Docker driver lets a task request host namespaces through three familiar options:
task "web" {
driver = "docker"
config {
image = "my-tenant-image:latest"
network_mode = "host"
pid_mode = "host"
ipc_mode = "host"
}
}Each of those lines dissolves a layer of container isolation. network_mode = "host" puts the container on the host's network stack — it can sniff node traffic and bind host ports. pid_mode = "host" exposes every process on the machine, including other tenants' workloads and the Nomad client itself. ipc_mode = "host" shares System V IPC and POSIX shared memory. Any one of them is a sandbox escape in practice; together they are root-on-the-node waiting for a motivated tenant.
Nomad's design anticipated exactly this: client agents carry a Docker plugin block where the operator declares which privilege escalations are acceptable on that machine:
plugin "docker" {
config {
allow_privileged = false
volumes {
enabled = false
}
}
}The contract is straightforward — a task asking for more privilege than the client allows should be rejected. CVE-2026-14891 was the discovery that for host namespace modes, the driver never checked. The allowed_modes / allow_privileged requirement existed as configuration surface but was not enforced on this code path, so any authenticated user able to submit a Docker job — or any API caller at all on a cluster with ACLs disabled, which is Nomad's default — could land a container in the host's namespaces. Third-party trackers rate the CVE 8.7 (High), which fits: low attack complexity, no user interaction, and a full confidentiality/integrity/availability break of the node on success.
The fix versions tell you how seriously HashiCorp took it — a same-day patch across every supported line:
| Release line | Fixed in | Released |
|---|---|---|
| Nomad Community 2.0.x | 2.0.4 | July 7, 2026 |
| Nomad Enterprise 2.0.x | 2.0.4 | July 7, 2026 |
| Nomad Enterprise 1.11.x | 1.11.8 | July 7, 2026 |
| Nomad Enterprise 1.10.x | 1.10.14 | July 7, 2026 |
Functionally, the fix inserts the check where it always belonged: when the Docker driver translates a task's requested namespace modes into a container-create call, it now validates the request against the client's allowed_modes / allow_privileged settings first and rejects the allocation when they do not permit it. The gate moved from "documented in the config reference" to "evaluated on the enforcement path" — which is the entire story of this CVE in one sentence.
It was not a one-off: the siblings in the same release
What elevates CVE-2026-14891 from "patch and move on" to "reconsider your trust model" is the company it kept. Nomad 2.0.4 shipped three security fixes, and all three are the same species — a driver- or client-side enforcement point that failed to hold:
| CVE / fix | What the gate was supposed to do | What actually happened |
|---|---|---|
| CVE-2026-14891 (host namespace modes) | Require allowed_modes / allow_privileged before granting host PID/net/IPC | Requirement never evaluated; any job submitter got host namespaces |
| CVE-2026-14896 (volume symlink bypass) | Honor volumes.enabled = false in the Docker plugin config | A task using a symlink could bypass the disabled-volumes setting and mount host paths |
| Cross-namespace host-volume claim deletion | Scope host-volume-delete to the claim's own namespace | A token with delete permission in one namespace could delete another namespace's sticky volume claims |
Three misses, one release, one pattern: Nomad's isolation guarantees were being enforced (or not) at distributed, per-client decision points rather than at a single admission boundary. Each fix is individually correct, but a platform team has to ask the uncomfortable question — how many more unenforced requirements are sitting in driver code paths that only get audited after someone reports them? HashiCorp's own July 8, 2026 security bulletins treated each as a distinct advisory, which is proper disclosure practice, but for an operator the correlated lesson matters more than any single CVE number.
Where the check lives: Nomad vs Kubernetes
This is the section the TODO item actually demands, so here it is as plainly as possible. The two schedulers put their trust boundaries in different places, and CVE-2026-14891 is what that difference costs when it fails.
| Isolation question | Nomad (pre-2.0.4 behavior) | Kubernetes (Pod Security + RBAC) |
|---|---|---|
| Where is "no host namespaces" decided? | In the Docker task driver on each client agent, consulting local plugin config | At the API server by the Pod Security Admission controller, before the pod is persisted |
| What does a tenant's request hit first? | The server accepts the job; the client driver decides at allocation time | Admission rejects the pod object outright — it never exists, is never scheduled |
| Who can widen the boundary? | Anyone who can edit a client agent's config file (usually the same team running jobs on small fleets) | Only someone with RBAC rights to relabel the namespace or change cluster admission config |
| What can a tenant self-grant? | Pre-fix: host namespaces with a three-line job spec, no operator action | Nothing — a tenant cannot grant itself what admission denies; privilege requires a namespace-label or role change by an admin |
| Cross-namespace leakage surface | Namespaces partition jobs and allocations, but nodes, ACL policies, quotas, and host volumes are cluster-global by design | Namespaces plus NetworkPolicy, ResourceQuota, and per-namespace PSA levels compose a tenant boundary the API server holds |
The Kubernetes parallel is worth making concrete, because it shows what "enforced at admission" feels like. A tenant pod requesting the host network:
spec:
hostNetwork: true
hostPID: truesubmitted into a namespace labeled pod-security.kubernetes.io/enforce: restricted is rejected by the API server with a forbidden error naming the exact violated policy. The pod is never created. There is no client agent to misconfigure, no per-node config file whose absence silently permits the request — the decision happens once, centrally, on every write.
Honesty requires the mirror-image caveat: Kubernetes admission only bites where it is switched on. Pod Security Admission enforces nothing on namespaces that carry no pod-security.kubernetes.io/* labels — an unlabeled tenant namespace silently admits a hostNetwork pod, which is the exact same failure shape (an unenforced default) wearing different clothes. The difference is one of blast radius and auditability: on a Cluster-API-managed fleet you can list every namespace's labels from the management cluster in one command and put enforce: restricted on tenant namespaces as fleet policy, whereas Nomad's pre-fix gap lived in driver code no operator could have audited from config. Both systems need the operator to turn the locks; Kubernetes at least keeps all the locks in one building.
The enterprise-gating wrinkle
There is a second structural difference that matters for a self-hosted PaaS choosing a scheduler on isolation grounds, and it is about what it costs to write content-aware policy — rules like "tenants may only run pre-approved images" or "no job may request host namespaces, ever."
In Nomad, that layer is Sentinel, HashiCorp's policy-as-code framework — and Sentinel is Enterprise-only. An OSS Nomad operator gets ACLs (coarse capabilities per namespace: submit jobs, read logs, manage volumes) but no way to say "reject any job spec containing network_mode" short of an out-of-band admission webhook they build and operate themselves. The irony of CVE-2026-14891 is sharp here: the operators least able to compensate for the missing driver check with their own policy layer were the OSS users, because the policy layer is the paid tier.
In Kubernetes, the equivalents ship in the box: Pod Security Admission for the standard profiles, ValidatingAdmissionPolicy (CEL expressions evaluated by the API server, no webhook to operate) for custom rules like approved-registry-only, and RBAC for who-may-do-what — all OSS, all enforced at the same admission boundary. A Cluster-API fleet inherits this entire stack the moment the management cluster exists; there is no isolation feature that only unlocks with a license key.
None of this makes Nomad indefensible — its single-binary operational model, native non-container workloads, and simpler mental model are genuine advantages for small teams running trusted workloads. But "we run mutually untrusting tenants" is precisely the workload shape where the admission architecture matters more than the binary size, and CVE-2026-14891 is now the case study for why.
Monday-morning checklist
If you operate Nomad, in this order:
- Upgrade. Nomad 2.0.4, or Enterprise 1.11.8 / 1.10.14 on the 1.x lines. The fix is a hard enforcement change, so read the upgrade notes, but do not defer it — the exploit is a job spec, not a binary payload.
- Audit existing specs for host namespace modes. Search every job file for
network_mode,pid_mode, andipc_modeset to"host". Each hit is either a workload that needs an explicit privilege grant going forward or evidence of past exploitation to investigate. - Confirm ACLs are actually enabled. If your cluster never set
acl { enabled = true }, every CVE in this class is remotely triggerable by any API caller. Enabling ACLs after the fact is a migration, but this is the week to start it. - Harden the client plugin blocks explicitly. Do not rely on defaults — set
allow_privileged = false, enumerate an explicitallowed_modesallowlist instead of opening blanket privilege, and keepvolumes { enabled = false }unless a client genuinely needs host volumes (remember the symlink sibling, CVE-2026-14896, targeted exactly this setting). - If you run a CAPI fleet instead, take the transferable lesson: label every tenant namespace
pod-security.kubernetes.io/enforce: restricted(withwarn/auditfirst if you need a migration window), and verify from the management cluster that no tenant namespace is unlabeled — your version of the missing gate is the namespace someone forgot to label.
The question to ask about your scheduler
CVE-2026-14891 was not a kernel bug, not a zero-day in a dependency, and not a misconfiguration any operator opted into. It was a mainstream orchestrator's own privileged-mode gating failing to evaluate its own requirement — disclosed promptly, to HashiCorp's credit, and patched across every supported line within the week. But three sibling-class fixes in one release say the failure was structural: isolation decisions scattered across client-side drivers instead of concentrated at one admission boundary that every request must pass.
So the scheduler question for a self-hosted PaaS is not "which binary is simpler to operate" — it is "show me the single choke point every tenant request passes through, and show me it is on." Kubernetes' answer (admission at the API server, auditable from one place, with the OSS policy stack included) survived this particular comparison. Whoever you bet your tenants' isolation on next should have to give the same demonstration — with evidence, not architecture diagrams.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



