NVIDIA's product security CVE index lists ten records whose affected product is the NVIDIA Container Toolkit, every one of them since September 2024, and six are time-of-check time-of-use races or symlink following. The toolkit's job is to act on container-supplied paths from the host, and that is the part that keeps breaking. The current record is CVE-2026-24260, CWE-367, scored 8.5 HIGH by NVIDIA, affecting all versions up to and including 1.19.0, with 1.19.1 and GPU Operator 26.3.2 named as the updated versions in bulletin 5850. The national vulnerability database published it on 1 July 2026 with no primary score and no CPE configuration, so there is no NVD-computed affected range for it at all. Neither the v1.19.1 release notes, whose seven bullets cover WSL2 systemd units and egl-wayland2 discovery, nor CHANGELOG.md, which has no v1.19.1 section, mentions a security fix: the change is visible only in the diff, where create-symlinks stops re-resolving container paths by name and opens the container root as a descriptor instead. On 15 September 2026 the minimums to assert are toolkit 1.19.1, GPU Operator 26.3.2, device plugin 0.17.3 and MIG Manager 0.12.2. Separately, accept-nvidia-visible-devices-envvar-when-unprivileged ships as true, so an unprivileged container's NVIDIA_VISIBLE_DEVICES is honoured out of the box. Start by running nvidia-ctk --version on one node and reading its /etc/nvidia-container-runtime/config.toml.
The list of NVIDIA Container Toolkit security vulnerabilities to work from is NVIDIA's own product security CVE index, not a vendor blog and not an aggregator. Filter it for the affected-product string "NVIDIA Container Toolkit" and you get ten records: six with 2024 identifiers, three with 2025, one with 2026. The 2022 and 2023 files return nothing. The earliest is carried by bulletin 5582 dated 25 September 2024 and the most recent by bulletin 5850 dated 30 June 2026, which makes the entire published history ten records inside twenty-one months.
Read the CWE column instead of the severity column and the shape of the problem appears. Four of the ten are CWE-367, time-of-check time-of-use. One is CWE-59, link following. One is CWE-61, UNIX symlink following. Six of ten records are the same job failing: a path the container controls, resolved by name by a process running with host privilege. That is not a coincidence in a codebase whose entire purpose is to take paths from inside a container and act on them from outside it.
This post is about what that means when the node is yours and the workload is not.
Ten records, and six of them are path-resolution bugs
Every figure in this table is NVIDIA's own, read from the product security CVE index. The scores are NVIDIA's, not the national vulnerability database's.
Three things in that table need a caveat before you quote it in a risk register.
The scores are contested. NVIDIA rates CVE-2024-0132 at 9.0 critical; the national vulnerability database's primary score is 8.3 high, on a different vector. CVE-2024-0136 is 7.6 from NVIDIA and 8.4 from NVD. CVE-2024-0137 is 5.5 from NVIDIA and 6.5 from NVD. Two of these records have no NVD primary score at all: CVE-2026-24260 and CVE-2025-23266 carry only the secondary score submitted by NVIDIA's own PSIRT. Whenever you write one of these numbers down, write down whose number it is.
The ranges are contested too. For CVE-2024-0135, CVE-2024-0136 and CVE-2024-0137, NVIDIA's index says affected up to and including 1.17.0, fixed in 1.17.1. The NVD CPE match for the same three says versionEndExcluding 1.17.3. Two primary sources, two different answers, for three records at once. Assert against the wider of the two and you are on 1.17.3 rather than 1.17.1, which costs you nothing and settles the argument.
One more on CVE-2025-23267: NVIDIA's own vector for it is AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:H. Confidentiality impact is none. An 8.5 high on a link-following bug in the update-ldcache hook reads like a data-theft story and NVIDIA's vector says it is a tampering and availability story. The number alone reads in the wrong direction, and the vector is the correction.
| CVE | CWE | NVIDIA base score | NVIDIA: affected up to and including | NVIDIA: updated version | Bulletin |
|---|---|---|---|---|---|
| CVE-2024-0132 | CWE-367 | 9.0 critical | v1.16.1 | v1.16.2, v24.6.2 | 5582 |
| CVE-2024-0133 | CWE-367 | 4.1 medium | v1.16.1 | v1.16.2, v24.6.2 | 5582 |
| CVE-2024-0134 | CWE-61 | 4.1 medium | v1.16.2 | v1.17, 24.9.0 | 5585 |
| CVE-2024-0135 | CWE-653 | 7.6 high | 1.17.0 | 1.17.1, 24.9.1 | 5599 |
| CVE-2024-0136 | CWE-653 | 7.6 high | 1.17.0 | 1.17.1, 24.9.1 | 5599 |
| CVE-2024-0137 | CWE-653 | 5.5 medium | 1.17.0 | 1.17.1, 24.9.1 | 5599 |
| CVE-2025-23266 | CWE-426 | 9.0 critical | 1.17.7 (CDI mode only before 1.17.5) | 1.17.8, 25.3.2, 0.17.3, 0.12.2 | 5659 |
| CVE-2025-23267 | CWE-59 | 8.5 high | 1.17.7 (same scope) | 1.17.8, 25.3.2, 0.17.3, 0.12.2 | 5659 |
| CVE-2025-23359 | CWE-367 | 8.3 high | 1.17.3 | 1.17.4, 24.9.2 | 5616 |
| CVE-2026-24260 | CWE-367 | 8.5 high | 1.19.0 | 1.19.1, 26.3.2 | 5850 |
What runs inside your container before it starts
The toolkit is not a library your workload links against. It is a set of host binaries that the container runtime executes while creating your container, and the shipped defaults put them at /usr/bin/nvidia-container-runtime-hook, /usr/bin/nvidia-cdi-hook and /usr/bin/nvidia-ctk.
There are two shapes to how they get invoked, and they differ in a way that is a security property rather than an operational preference. The legacy path modifies the OCI spec in place and appends nvidia-container-runtime-hook to spec.Hooks.Prestart. The CDI path writes hook invocations into a CDI specification, and at toolkit v1.20.0 every hook the toolkit generates runs at the createContainer lifecycle stage: getOCIHookType in internal/discover/hooks.go returns OCIHookTypeCreateContainer for every hook name and for the default branch, and the constant's own comment reads "runs in the container mount namespace before pivot_root".
The OCI Runtime Specification is explicit about what that stage means. prestart hooks "MUST be executed in the runtime namespace". createContainer hooks' path "MUST resolve in the runtime namespace" while the hooks themselves "MUST be executed in the container namespace", after the mount namespace has been created and the mounts performed but before pivot_root. Then the sentence that changes your threat model: "The definition of createContainer hooks is currently underspecified and hooks authors, should only expect from the runtime that the mount namespace and different mounts will be setup. Other operations such as cgroups and SELinux/AppArmor labels might not have been performed by the runtime."
A host binary, executing with the container's mounts visible, at a moment when the label that would confine it may not yet be applied. Path resolution is the part of this codebase that has produced six of its ten CVE records.
Six hook subcommands are registered on nvidia-cdi-hook at v1.20.0: update-ldcache, create-symlinks, chmod, enable-cuda-compat, disable-device-node-modification and update-application-profile, plus a noop that exists to test the CLI. A name the binary does not implement is not fatal, either: the dispatcher's default branch logs "Unsupported CDI hook: %v" and returns nil, so the container still launches.
Before any of that matters, find out what the node is actually running and where its hook invocations live.
# Toolkit version on the node itself, not inferred from the operator release nvidia-ctk --version # The config the runtime reads (override path: NVIDIA_CTK_CONFIG_FILE_PATH) cat /etc/nvidia-container-runtime/config.toml # The CDI specifications, and the hook invocations inside them ls /etc/cdi /var/run/cdi
Which runtime mode a given node ends up in, and how to tell without guessing, is a separate decision that we worked through in failed to initialize NVML on GPU nodes. This post assumes you already know.
CVE-2026-24260, and the versions to assert on this date
Bulletin 5850, dated 30 June 2026 at revision 1.0.0, is the whole primary record. It describes a time-of-check time-of-use race condition in the NVIDIA Container Toolkit for Linux whose successful exploitation "might lead to code execution, escalation of privileges, and data tampering", scores it 8.5 high on CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H, and gives two rows in its security updates table: toolkit affected up to and including 1.19.0, updated version 1.19.1; GPU Operator affected up to and including 26.3.1, updated version 26.3.2.
The NVD record adds nothing to that. It was published on 1 July 2026, its status is awaiting analysis, its only CVSS metric is the secondary one submitted by NVIDIA, and it has no CPE configuration. There is no NVD-computed affected-version range for CVE-2026-24260, which means anyone quoting one is quoting nothing.
Four components, because bulletin 5659 named four. Every one of those four rows carries a condition: CDI mode only for toolkit versions prior to 1.17.5 and operator versions prior to 25.3.1, the device plugin only when the device-list strategy is cdi-annotations or cdi-cri, the MIG Manager only when CDI is enabled. Assert all four anyway, because each condition is a property of a node configuration and configurations change. That bulletin also demonstrates why a minimum recorded on disclosure day is not necessarily the final one: it shipped at revision 1.0.0 on 15 July 2025 and was revised to 2.0.0 on 12 August 2025 with the note "Updated affected products and mitigation steps". Twenty-eight days after the first publication, the list of affected products changed. If your process captures a bulletin once and never re-reads it, that revision never reached you.
The timing runs the other way too. Toolkit v1.19.1 was published on 21 May 2026 and bulletin 5850 is dated 30 June 2026, so the code was public for forty days before NVIDIA said what it was for. That is the normal case here: across the six Container Toolkit bulletins, five were preceded by the release carrying their fix and one landed the same day. We counted the same gap release by release on the serving side in Triton Inference Server hardening. The operational reading is simple: a minimum derived from disclosure dates is wrong in both directions, late because the fix predates the announcement, and premature because the announcement can still change.
| Component | Minimum to assert | Source of that minimum | Newest release on 15 September 2026 |
|---|---|---|---|
| NVIDIA Container Toolkit | 1.19.1 | bulletin 5850 | 1.20.0, 13 August 2026 |
| NVIDIA GPU Operator | 26.3.2 | bulletin 5850 | 26.7.0, 21 August 2026 |
| NVIDIA Kubernetes Device Plugin | 0.17.3 | bulletin 5659 | 0.20.0, 19 August 2026 |
| NVIDIA MIG Manager | 0.12.2 | bulletin 5659 | no release stream we could tie to that product name |
The fix is in the diff and not in the changelog
Here is what the v1.19.1 release notes say, in full, under What's Changed: a WSL2 fix for the nvidia-cdi-refresh systemd unit conditions, removal of that unit's dependency on multi-user.target, discovery of the egl-wayland2 library and its config file into CDI specifications, a CUDA compat header fix on Orin systems, a corrected default path for cri-o's drop-in configuration directory, /dev/dri* device node injection for MIG devices, and a new disable-ipc-discoverer feature flag in the nvcdi library. Seven bullets, no security note, no CVE, no advisory reference.
The changelog is worse. In CHANGELOG.md at tag v1.20.0, the top-level headings run ## v1.20.0, ## v1.20.0-rc.1, ## v1.19.0, ## v1.19.0-rc.7 and downward. There is no ## v1.19.1 section, no ## v1.17.8, no ## v1.17.4 and no ## v1.17.1. Grepping the file for those four version strings returns nothing, while ## v1.16.2 is there. The omission is structural: after v1.16.2 the file carries minor releases and their release candidates and nothing else, so no patch release since has been written up. Six bulletins name six updated toolkit versions, and four of the six are patch releases published after v1.16.2.
The code is a different story. At tag v1.19.0, cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go builds a link path with filepath.Join(containerRoot, link), resolves the parent once through symlink.FollowSymlinkInScope(filepath.Dir(linkPath), containerRoot), then rebuilds the target as filepath.Join(resolvedLinkParent, filepath.Base(linkPath)) and hands that string to os.MkdirAll and symlinks.ForceCreate. The path is resolved, and then it is used again by name.
At tag v1.19.1 a file that does not exist at v1.19.0 appears beside it: cmd/nvidia-cdi-hook/create-symlinks/container-root_linux.go. It imports the pathrs-lite package from the filepath-securejoin module, opens the container root as a descriptor with os.OpenFile(containerRootDir, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0), walks into it with pathrs.OpenatInRoot and pathrs.MkdirAllHandle, and creates the link with unix.Symlinkat followed by unix.Renameat against the open directory descriptor. After that change the hook never re-resolves the path by name. The same code is present at v1.20.0.
Two facts, stated side by side and not joined. NVIDIA's bulletin names 1.19.1 as the updated version for CVE-2026-24260. Version 1.19.1 is the release in which root-confined path resolution first appears in the create-symlinks hook. NVIDIA publishes no commit reference for the CVE, so the attribution is theirs to make and not ours.
Two hooks in the same binary, two levels of care about paths
The rewrite did not sweep the binary. At v1.20.0, cmd/nvidia-cdi-hook/chmod/chmod.go still does the old thing: getPaths builds path := filepath.Join(root, f), calls os.Stat(path) to filter, and run then calls os.Chmod(path, cfg.mode) on whatever survived. Resolve, then act by name, with a permission error explicitly ignored and logged at debug level. Its flags are --path (repeatable), --mode (parsed with strconv.ParseUint(cfg.modeStr, 8, 32), so octal) and --container-spec, and its usage string says out loud that "The container root is prefixed to the specified paths."
In the same binary, disable-device-node-modification opens the container root with os.OpenRoot(containerRootDirPath) and works from there.
Two things keep this from being a finding. The chmod hook is marked deprecated in internal/discover/hooks.go at v1.20.0 with a note that it will be removed in a future release, and it is the sole entry in defaultDisabledHooks, disabled by default because it was a workaround for older versions of crun that have since been fixed. And nothing we opened establishes that its filepath.Join path is reachable by a container-controlled string under any shipped CDI specification.
What is left is a pattern worth knowing when you review this codebase or a vendor's fork of it. Two hooks in this binary work through a descriptor confined to the container root: disable-device-node-modification, added in v1.17.8, and create-symlinks as of v1.19.1. The chmod hook does not. When you audit a GPU node, the question is not "is the toolkit patched" but "which hooks does this node's CDI specification actually invoke, and at what version were they last touched".
Eight settings that change what a hostile image can ask for
A current toolkit is necessary and it is not the control surface. The control surface is /etc/nvidia-container-runtime/config.toml, and its defaults are permissive in one specific place.
Four of those eight need changing on a default node. Three more only need asserting, because they are pointer-to-bool fields that read false when absent and you want them written down rather than inherited from a node image someone else built. The eighth, nvidia-container-cli.ldconfig, you verify rather than set: ldconfigPath.assertValid rejects a non-host value with the error nvidia-container-cli.ldconfig value %q is not host-relative (does not start with a '@').
# /etc/nvidia-container-runtime/config.toml # Ships as true. An unprivileged container's NVIDIA_VISIBLE_DEVICES is # honoured out of the box. accept-nvidia-visible-devices-envvar-when-unprivileged = false # Ships unset. Let the device list arrive as volume mounts instead. accept-nvidia-visible-devices-as-volume-mounts = true [nvidia-container-cli] # The '@' prefix resolves the binary on the host. A value without it is # rejected unless the feature flag below is turned on. ldconfig = "@/sbin/ldconfig" [features] allow-ldconfig-from-container = false allow-cuda-compat-libs-from-container = false allow-unknown-oci-spec-fields = false ignore-imex-channel-requests = true disable-imex-channel-creation = true
That host-relative enforcement has a date on it, and it is not the date of the CVE that lived in that hook. The changelog entries "Only allow host-relative LDConfig paths" and "Create virtual copy of host ldconfig binary before calling fexecve()" both sit in the v1.18.0-rc.1 section, and v1.18.0 was published on 21 October 2025. That is almost five months after v1.17.8 shipped the updated version for CVE-2025-23267, the link-following bug in the update-ldcache hook. The structural change that stops the ldcache path trusting a container-supplied ldconfig landed well after the patch for the CVE that lived there.
The same seven boolean settings can be applied without hand-editing the file. nvidia-ctk config resolves dotted keys by splitting on . and walking the config struct, so a [features] key is addressable as features.<key>, and an unknown key fails instead of being written.
nvidia-ctk config --in-place \ --set accept-nvidia-visible-devices-envvar-when-unprivileged=false \ --set accept-nvidia-visible-devices-as-volume-mounts=true \ --set features.ignore-imex-channel-requests=true \ --set features.disable-imex-channel-creation=true \ --set features.allow-ldconfig-from-container=false \ --set features.allow-cuda-compat-libs-from-container=false \ --set features.allow-unknown-oci-spec-fields=false # A key that does not exist fails loudly rather than being written: # invalid --set option foo=bar: invalid config option: undefined field
On a node whose toolkit is installed by the toolkit container rather than by hand, the two device-list settings are also reachable as environment variables, because nvidia-ctk-installer declares both flags with exactly these sources and writes the resulting values into the root table of the config it generates.
ACCEPT_NVIDIA_VISIBLE_DEVICES_ENVVAR_WHEN_UNPRIVILEGED=false ACCEPT_NVIDIA_VISIBLE_DEVICES_AS_VOLUME_MOUNTS=true
How you pass those through depends on how the toolkit container is installed in your cluster, and you should read that from your own deployment rather than from a field name quoted in a blog post. The failure mode to plan for is the other one: what breaks when an unprivileged container stops being able to name its own devices depends entirely on how your workloads request GPUs today. Set it on one cordoned node, run a representative GPU pod, confirm the container sees the devices it expects, then roll it.
| Setting | Ships as | Set it to | What that changes |
|---|---|---|---|
accept-nvidia-visible-devices-envvar-when-unprivileged | true | false | An unprivileged container can no longer select devices through the NVIDIA_VISIBLE_DEVICES environment variable it controls |
accept-nvidia-visible-devices-as-volume-mounts | unset (false) | true | The device list arrives as volume mounts, which the orchestrator sets, instead of as container-supplied environment |
nvidia-container-cli.ldconfig | @/sbin/ldconfig (normalised to @/sbin/ldconfig.real when that file exists) | leave the @ | The @ prefix means the binary is resolved on the host; a value without it is rejected |
features.allow-ldconfig-from-container | absent (false) | keep false | Setting it true is what would make non-host ldconfig paths valid again |
features.allow-cuda-compat-libs-from-container | absent (false) | keep false | Stops CUDA compat libraries shipped in the image from overriding host driver library mounts |
features.allow-unknown-oci-spec-fields | absent (false) | keep false | Unknown fields in the OCI spec are not silently dropped when the runtime loads it |
features.ignore-imex-channel-requests | absent (false) | true | IMEX channel requests made through NVIDIA_IMEX_CHANNELS or volume mounts are ignored, which is the toolkit's own stated reason for the flag |
features.disable-imex-channel-creation | absent (false) | true | Implicit creation of requested IMEX channels is skipped when invoking nvidia-container-cli |
Isolation between tenants is a property of the sharing mode
Everything above governs what a container is allowed to ask the toolkit for. None of it governs what one workload can observe of another once both are resident on the same physical GPU. That is the sharing mode, and it is a different decision with a different answer.
Read the NVIDIA Kubernetes device plugin README as a security document and one sentence settles the easy case: with time-slicing, "nothing special is done to isolate workloads that are granted replicas from the same underlying GPU, and each workload has access to the GPU memory and runs in the same fault-domain as of all the others". Two workloads on a time-sliced GPU are one trust domain. If your tenant boundary is a compliance boundary, time-slicing is not a candidate, regardless of toolkit version. The mechanics of choosing between the sharing modes, and what each one actually partitions, are the whole subject of MIG vs MPS vs time-slicing, and this is the only claim we are making about them here.
One neighbouring record looks like it belongs here and does not. CVE-2023-4969, published 16 January 2024 and scored 6.5 medium, describes a GPU kernel reading sensitive data from another GPU kernel through local memory. Its vulnerable CPE list names Khronos OpenCL, Khronos Vulkan, Imagination Technologies' DDK and a long list of AMD firmware. NVIDIA is not on that list, and the record is not a Container Toolkit advisory. Cite it for the class of risk if you like; do not cite it as evidence about NVIDIA hardware.
Two adjacent problems are deliberately out of scope here. Protecting a tenant from the host operator is the opposite trust direction and has its own cost curve, covered in confidential GPU inference and the H100 performance tax. And if the untrusted thing is code your own agents generate rather than a co-tenant's image, the isolation technology is a different one entirely, which is the subject of SmolVM and Firecracker microVMs.
The gate to pass before one GPU node serves two tenants
On a private cluster the tenants can all be internal: a model team, a vendor doing a fine-tune under contract, a clinical analytics group, a quant desk. That makes the boundary easier to ignore and no less real, because in an on-premise deployment nobody upgrades the node for you and the regulator's question is about the control, not the provider. The toolkit config on that node is the boundary, and it is easy for that file to end up with no owner: written by an operator chart, never diffed, never in the node image's review path. The wider on-premise case, and what it costs against a managed platform, is in cloud versus on-premise AI security and cost.
Seven checks, in this order.
nvidia-ctk --version on the machine, per node, not the operator chart version and not the Helm values./etc/nvidia-container-runtime/config.toml on the node. Compare it to the eight settings above. A node built from an image someone else produced can carry the permissive default untouched./etc/cdi and /var/run/cdi and read the hook invocations inside. Those two directories are where the CDI specifications live, and they tell you which hooks this node actually runs.This week, do three of them on one node: the version, the config file and the CDI directory, written down. That is a short exercise. The CVE list you can read off NVIDIA's index; what the config file on that node says you can read nowhere else. For the rest of the control set around private model serving, start at our AI security pillar.
FAQ
Quick answers to the questions this post tends to raise.


