{"schema_version":"1.7.5","id":"GHSA-85jx-fm8m-x8c6","published":"2026-03-10T23:44:25Z","modified":"2026-03-23T04:56:02.111439054Z","aliases":["CVE-2026-31801","GO-2026-4668"],"summary":"zot’s create-only policy allows overwrite attempts of existing latest tag (update permission not required)","details":"zot’s dist-spec authorization middleware infers the required action for `PUT /v2/{name}/manifests/{reference}` as `create` by default, and only switches to `update` when the tag already exists and `reference != \"latest\"`.\n\nas a result, when `latest` already exists, a user who is allowed to `create` (but not allowed to `update`) can still pass the authorization check for an overwrite attempt of `latest`.\n\n## affected component\n\n- file: `pkg/api/authz.go` (`DistSpecAuthzHandler`)\n- condition: `slices.Contains(tags, reference) && reference != \"latest\"` (line 352 at the pinned commit)\n\n## severity\n\nHIGH\ncategory: CWE-863 (incorrect authorization)\n\nnote: impact depends on how a deployment uses `latest` (for example, if `latest` is treated as a protected or “push-once” tag), and on how access control is provisioned (users with `create` but without `update`). the attached poc demonstrates a real overwrite of `latest` (tag digest changes) under a create-only policy.\n\n## steps to reproduce\n\n1. configure access control so user `attacker` has `create` but not `update` on a repository.\n2. ensure the repository has an existing tag named `latest`.\n3. attempt to push a new manifest to `/v2/acme/app/manifests/latest` (example repository name).\n4. observe that the authorization check is evaluated as `create` (not `update`) for `latest`, so the request passes authorization even though the tag already exists.\n\nthe attached poc demonstrates this deterministically with `canonical.log` and `control.log` markers.\n\n## expected vs actual\n\n- expected: overwriting an existing tag should require `update` permission, including `latest` (or `latest` should be explicitly documented as exempt).\n- actual: when `reference==\"latest\"` and the tag exists, the middleware keeps the action as `create` instead of switching to `update`.\n\n## security impact\n\nthis can break least-privilege expectations in deployments that rely on the `create` vs `update` split to prevent tag overwrites (for example, “push-once” policies). if `latest` is used as a high-trust tag in ci/cd, this can create supply-chain risk because a create-only principal can overwrite an existing `latest` tag while other existing tags correctly require `update`.\n\n## suggested fix\n\nremove the special-case exemption for `latest` when determining whether an existing tag requires `update` permission (treat `latest` the same as other tags), or document and enforce an explicit policy rule for `latest`.\n\n## notes / rationale\n\n- oci distribution spec does not define a standard authorization model; this report is about zot’s own create vs update semantics and the observable behavior in `DistSpecAuthzHandler`.\n- zot documentation describes immutable tags as being enforceable via authorization policies (create-only “push once”, update disallowed). if `latest` is exempt, this control does not apply to `latest` unless documented otherwise.\n\n[addendum.md](https://github.com/user-attachments/files/24986139/addendum.md)\n[poc.zip](https://github.com/user-attachments/files/24986140/poc.zip)\n[PR_DESCRIPTION.md](https://github.com/user-attachments/files/24986141/PR_DESCRIPTION.md)\n[RUNNABLE_POC.md](https://github.com/user-attachments/files/24986142/RUNNABLE_POC.md)","affected":[{"package":{"name":"zotregistry.dev/zot/v2","ecosystem":"Go","purl":"pkg:golang/zotregistry.dev/zot/v2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2.1.15"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-85jx-fm8m-x8c6/GHSA-85jx-fm8m-x8c6.json"}},{"package":{"name":"zotregistry.dev/zot","ecosystem":"Go","purl":"pkg:golang/zotregistry.dev/zot"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.3.0-20210831063041-c8779d9e87d9"},{"last_affected":"1.4.4-20251014054906-73eef25681af"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-85jx-fm8m-x8c6/GHSA-85jx-fm8m-x8c6.json"}}],"references":[{"type":"WEB","url":"https://github.com/project-zot/zot/security/advisories/GHSA-85jx-fm8m-x8c6"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-31801"},{"type":"PACKAGE","url":"https://github.com/project-zot/zot"},{"type":"WEB","url":"https://github.com/project-zot/zot/releases/tag/v2.1.15"}],"database_specific":{"cwe_ids":["CWE-863"],"github_reviewed":true,"github_reviewed_at":"2026-03-10T23:44:25Z","nvd_published_at":"2026-03-10T21:16:49Z","severity":"HIGH"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N"}]}