Distribution's tag deletion bypasses `storage.delete.enabled` configuration
Date: 2026-05
Affected: go/github.com/distribution/distribution, go/github.com/distribution/distribution/v3
Attack vector: auth-bypass
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-41888
CWE: CWE-863

### Summary Tag deletion via the `DELETE /v2/<name>/manifests/<tag>` endpoint bypasses the `storage.delete.enabled: false` configuration, allowing any API client to remove tags from repositories even when the operator has explicitly disabled deletion. ### Details When `storage.delete.enabled` is configured to false, digest-based manifest deletion is correctly rejected by the guard in [registry/storage/linkedblobstore.go:212-215](https://github.com/distribution/distribution/blob/main/registry/storage/linkedblobstore.go#L213-L215). However, tag deletion takes a separate code path that never checks this setting: In [`registry/handlers/manifests.go:439-453`](https://github.com/distribution/distribution/blob/main/registry/handlers/manifests.go#L439-L453), `DeleteManifest` detects a tag reference, calls `tagService.Untag()`, returns, never consulting `registry.deleteEnabled`. In turn, [`tagStore.Untag()`](https://github.com/distribution/distribution/blob/main/registry/storage/tagstore.go#L111-L121) calls the storage driver directly to delete the tag path without checking whether deletes are enabled. ### PoC Using a paired down Distribution configuration that explicitly disables deletes, such as this one, stored as `config.yaml`: ```yaml version: 0.1 storage: delete: enabled: false filesystem: rootdirectory: /var/lib/registry http: addr: :5000 ``` Start a local Distribution, mounting in the above configuration from the current directory: ```shell docker r


Mitigations:

Architectural layers (MAESTRO):