# Kubernetes v1.33 Fixes a 10-Year-Old Image Pull Loophole
> Kubernetes v1.33 finally enforces image pull secrets even for cached images, closing a 10-year-old loophole in multi-tenant cluster security.

Canonical: https://blog.abhimanyu-saharan.com/posts/kubernetes-v1-33-fixes-a-10-year-old-image-pull-loophole
Published: 2025-05-15
Last updated: 2025-06-19
Authors: Abhimanyu Saharan
Categories: Kubernetes v1.33, Kubernetes

For over a decade, Kubernetes had a subtle but significant security gap when it came to image pull policies and private registries. This long-standing issue (tracked as [#18787](https://github.com/kubernetes/kubernetes/issues/18787)) allowed pods to reuse container images already pulled onto a node, even if they weren’t authorized to do so.

With Kubernetes v1.33, that loophole is finally being addressed.

## The Problem: Reusing Private Images Without Authorization

In Kubernetes, image pull policies control when the Kubelet fetches container images from registries. If a pod specifies `imagePullPolicy: IfNotPresent` and the image already exists on the node, Kubelet skips the pull and starts the pod using the cached image. This works fine for public images, but becomes a problem for private ones.

Here’s what could happen:

1. Pod A in namespace `team-a` is authorized to pull `private.registry/foo/bar:latest` using image pull secrets. The image is pulled and cached on Node X.
2. Pod B in namespace `team-b` schedules to the same node and references the same image with `imagePullPolicy: IfNotPresent`, but lacks the necessary pull secrets.
3. Since the image already exists, Kubelet starts Pod B using the image, without validating if Pod B had the right to use it.

This broke the isolation model and led to unintentional privilege escalations across namespaces.

## The Fix: Enforcing Image Pull Secrets Even After Caching

Starting with Kubernetes v1.33, a new feature controlled by the `KubeletEnsureSecretPulledImages` flag changes this behavior. Now, Kubelet verifies that a pod has valid credentials before letting it run a cached image pulled from a private registry.

The key idea: presence of the image on the node is no longer enough. Authorization is always checked—even for `IfNotPresent` and `Never` pull policies.

### Behavior by Pull Policy

- **Always:** No change. Kubelet always pulls the image, and registry auth is enforced.
- **IfNotPresent:** Now validates credentials against a local cache of previously successful image pulls.
- **Never:** Kubelet won't pull, but it still checks that credentials match what was used for the original authorized pull.

## How It Works Internally

The feature uses a file-based cache to track:

- Credential hashes used during successful image pulls
- Originating Secrets that supplied these credentials

When a pod references a cached image:

- If its credentials match the cached entry (via hash or Secret name), it is allowed to run.
- If not, the Kubelet attempts a new pull using the new credentials to verify access.

The cache ensures credential reuse is safe while allowing verification without unnecessary registry calls.

## Projected Service Account Tokens (PSAT) Support

In parallel, [KEP-4412](https://github.com/kubernetes/enhancements/blob/master/keps/sig-auth/4412-projected-service-account-tokens-for-kubelet-image-credential-providers/README.md) enables projected service account tokens to be used by the Kubelet when pulling images, providing better workload isolation and finer-grained access control.

Together, these efforts improve how image pulls are authenticated and authorized at node level.

## How to Enable

To test the feature:

```yaml
# Set this flag on the kubelet
--feature-gates=KubeletEnsureSecretPulledImages=true
```

You can monitor the evolution of this enhancement in [KEP-2535](https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2535-ensure-secret-pulled-images) and track its graduation toward beta in v1.34.

## What’s Next?

Future improvements are already planned:

- Support for PSAT-based image credential providers (KEP-4412)
- TTLs for credential entries to handle expiration and revocation
- In-memory cache to reduce I/O latency
- Benchmarking performance under load

## Final Thoughts

It’s rare to see a 10-year-old issue resolved in such a comprehensive and forward-looking manner. Kubernetes v1.33’s enhancement to image pull security finally closes a long-ignored hole, bringing clarity and control to multi-tenant environments that depend on private registries.

If you're running shared clusters, enabling this feature is a no-brainer.

## FAQ

### What was the image pull security issue in Kubernetes prior to v1.33?

Pods could reuse cached private images on a node without validating their credentials. If a private image had already been pulled by an authorized pod, any subsequent pod referencing it (even without pull secrets) could start using it, bypassing registry authentication.

### What specific change does Kubernetes v1.33 introduce to fix the image reuse issue?

Kubernetes v1.33 introduces the `KubeletEnsureSecretPulledImages` feature flag. When enabled, Kubelet enforces image pull secret validation even for cached images. Before starting a pod, Kubelet checks that the pod's credentials (pull secrets or service account tokens) match those used when the image was originally pulled. If they don’t, Kubelet attempts a new pull using the provided credentials. This ensures that only authorized pods can use cached private images, closing the loophole that previously allowed unauthorized access.

### How does the credential validation mechanism work?

Kubelet maintains a file-based cache of credential hashes and originating secrets from prior successful image pulls. When a pod tries to use a cached image, Kubelet checks that its credentials match the cached entry. If not, it attempts a fresh pull with the new credentials.

### What are the implications for imagePullPolicy settings?

- `Always`: Behavior remains unchanged, Kubelet pulls the image every time.
- `IfNotPresent`: Now validates credentials before using the cached image.
- `Never`: Kubelet won’t pull, but it still validates that the pod has matching credentials for the cached image.
