F02 | CONFIRMED | src/server/v2.0/handler/gc.go:70 | `parameters["redis_url_reg"] = os.Getenv("_REDIS_URL_REG")` flows into the execution's ExtraAttrs (controller/gc/controller.go:79 -> exeMgr.Create) and is echoed verbatim as `Parameters: string(extraAttrsString)` by GetGC (gc.go:195-204) and GetGCHistory (gc.go:157-165); whether the URL actually carries a password is deployment-dependent, and readers need system GC read (admin), consistent with Low.
F05 | CONFIRMED | src/server/handler/job_status_hook.go:37 | ServeHTTP decodes the body and calls `j.handler.Handle(r.Context(), sc)` with no security-context check of any kind; the routes at src/server/route.go:53-59 are plain `router.NewRoute()` handlers, the global chain only *populates* a context (middleware/security/security.go:45-62, UnauthorizedMiddleware:67) and csrfSkipper exempts `/service/` (middleware/csrf/csrf.go:72-79), so nothing enforces the `secret` generator; task/hook.go:56-84 then looks up by attacker-supplied JobID and calls `UpdateStatus(ctx, task.ID, sc.Status, ...)`.
F08 | CONFIRMED | src/controller/artifact/annotation/v1alpha1.go:85 | `data, err := ioutil.ReadAll(io.LimitReader(icon, 1<<20))` followed by `if err == io.EOF { ...maximum size of the icon is 1MB }` — io.ReadAll never returns io.EOF (it reports nil at EOF), so the 1MB rejection branch is unreachable and an oversized icon is truncated to 1MB, sniffed at :91 and accepted at :97.
F11 | CONFIRMED | src/controller/icon/controller.go:136 | `img, _, err := image.Decode(iconFile)` runs on an attacker-supplied blob with no width/height or byte bound before `resize.Thumbnail` at :147, and the reachable entry point src/server/v2.0/handler/icon.go:37-41 calls `i.ctl.Get(ctx, params.Digest)` with no Require*Access call at all; recording the icon still requires push rights (v1alpha1.go:97), and the exact ~3.6 GB figure is an estimate, not something this tree proves.
F14 | CONFIRMED | src/controller/scan/base_controller.go:334 | `query := q.New(q.KeyWords{"extra_attrs.artifact.digest": artifact.Digest})` then `execMgr.Stop(ctx, executions[0].ID)` — no project or vendor scoping, while the only authorization is `RequireProjectAccess(ctx, params.ProjectName, ActionStop, ResourceScan)` at src/server/v2.0/handler/scan.go:51, so an identical digest present in two projects lets project A stop project B's execution.
F17 | CONFIRMED | src/server/v2.0/handler/notification_policy.go:143 | Authorization is on the URL project (`RequireProjectAccess(ctx, projectNameOrID, ActionDelete, ...)` :139) but the write is `n.webhookPolicyMgr.Delete(ctx, params.WebhookPolicyID)`, and the manager delegates straight to `m.dao.Delete(ctx, policyID)` (pkg/notification/policy/manager.go) with no project predicate.
F20 | CONFIRMED | src/server/v2.0/handler/retention.go:197 | `p := model.NewRetentionPolicyFromSwagger(params.Policy).Metadata; p.ID = params.ID` — `requireAccess(ctx, p, ActionUpdate)` at :205 resolves the project from `p.Scope.Reference`, which came from the request body (:360-369), while `UpdateRetention(ctx, p)` at :210 writes the row identified by the URL ID; controller/retention/controller.go:126 re-reads only `p.ID` and never compares the stored scope to the supplied one.
F23 | CONFIRMED | src/controller/immutable/controller.go:59 | `UpdateImmutableRule` fetches `m0` by the body-supplied `m.ID` (:60) and never compares `m0`'s project to the caller's `projectID`; the toggle path `EnableImmutableRule(ctx, m.ID, m.Disabled)` (:68) drops projectID entirely and the DAO updates by primary key only (`ormer.Update(ir, "Disabled")`, pkg/immutable/dao/dao.go:63-75), as does the update path (`ormer.Update(ir, "TagFilter")`, dao.go:46-59).
F26 | CONFIRMED | src/server/v2.0/handler/preheat.go:262 | `convertParamPolicyToModelPolicy(params.Policy)` copies `ID` and `ProjectID` out of the body (see the converter's `ID: model.ID, ProjectID: model.ProjectID`), authorization is only `RequireProjectAccess(ctx, params.ProjectName, ...)` at :258, and controller/p2p/preheat/controller.go UpdatePolicy re-reads by `schema.ID` without any project comparison — unlike CreatePolicy, which overrides `policy.ProjectID` at :245.
F29 | CONFIRMED | src/server/v2.0/handler/notification_job.go:34 | `policy, err := n.webhookPolicyMgr.Get(ctx, params.PolicyID)` after authorizing only against the URL project (:30); `policy.ProjectID` is never compared to `projectNameOrID`, and the job query is then keyed solely on `query.Keywords["PolicyID"] = policy.ID` (:43).
F32 | CONFIRMED | src/controller/immutable/controller.go:64 | The rule ID is taken from the body at src/server/v2.0/handler/immutable.go:75-76 (`lib.JSONCopy(&metadata, params.ImmutableRule)`) and passed at :84; the nil check at :64 is the only validation before `EnableImmutableRule(ctx, m.ID, m.Disabled)` at :68, which toggles by primary key across projects. The second half of the claimed impact ("then overwrites that project's protected tags") additionally requires push rights on the victim project, which this defect does not grant.
F35 | CONFIRMED | src/server/v2.0/handler/immutable.go:62 | `ia.immuCtl.DeleteImmutableRule(ctx, params.ImmutableRuleID)` after authorizing only the URL project at :58; the chain ends in `ormer.Delete(&model.ImmutableRule{ID: id})` (pkg/immutable/dao/dao.go:114-127), keyed on ID alone.
F38 | CONFIRMED | src/server/v2.0/handler/scan.go:43 | `if err := unescapePathParams(params, "RepositoryName"); err != nil { s.SendError(ctx, err) }` — the responder returned by SendError is discarded and Prepare returns nil at :47, so a failed unescape does not abort the request.
F41 | CONFIRMED | src/server/middleware/util/util.go:71 | `secCtx.Can(..., rbac.ActionPush, ...) && strings.Contains(r.UserAgent(), "cosign")` returns skip=true from an attacker-controlled request header, and the helper is the shared gate for all three cited call sites: vulnerable.go:71, contenttrust/notary.go:56 and contenttrust/cosign.go:41 each `return nil` when it reports true.
F44 | CONFIRMED | src/controller/artifact/processor/default.go:112 | `json.NewDecoder(blob).Decode(&metadata)` reads the whole config blob with no size bound and assigns it straight to `artifact.ExtraAttrs` (:117), which is persisted as the `extra_attrs` JSON column (pkg/artifact/model.go:103-108, dao/model.go:43); reachability is as claimed — processor.go:65-72 falls back to `DefaultProcessor` for any unregistered config mediaType.
F47 | CONFIRMED | src/server/v2.0/handler/preheat.go:650 | `api.executionCtl.Stop(ctx, params.ExecutionID)` with authorization only on `params.ProjectName` (:645); ExecutionCtl.Stop is the generic `ec.mgr.Stop(ctx, id)` (controller/task/execution_controller.go:55-57) with no vendor or project filter.
F50 | CONFIRMED | src/server/v2.0/handler/preheat.go:581 | `execution, err := api.executionCtl.Get(ctx, params.ExecutionID)` — a bare `ec.mgr.Get(ctx, id)` — and the payload built at :544-559 returns `ExtraAttrs`, `VendorType`, `VendorID`, `Trigger` and `Status`; the sibling ListTasks keys only on `query.Keywords["execution_id"] = params.ExecutionID` (:693), so the task half of the claim holds too.
F53 | CONFIRMED | src/server/v2.0/handler/retention.go:353 | `r.retentionCtl.GetRetentionExecTaskLog(ctx, params.Tid)` resolves to `r.taskMgr.GetLog(ctx, taskID)` (controller/retention/controller.go:363) on the generic task manager, with authorization taken from an unrelated policy `params.ID` at :344-348; the stop half is the same shape at :277, `OperateRetentionExec(ctx, params.Eid, ...)`, which only checks the execution exists (controller.go:257-264).
F56 | CONFIRMED | src/server/middleware/immutable/pushmf.go:50 | `af, err := artifact.Ctl.GetByReference(...)` is followed by `if err != nil { log.Debugf(...); return nil }` (:50-53) — every error class, not just not-found, yields a nil return that lets the push through to the immutability check being skipped entirely.
F59 | CONFIRMED | src/server/v2.0/handler/preheat.go:725 | `l, err := api.taskCtl.GetLog(ctx, params.TaskID)` where `taskCtl` is the global `taskCtl.Ctl` (:36) whose GetLog is an unfiltered `c.mgr.GetLog(ctx, id)` (controller/task/controller.go:76-78) — no execution, vendor or project scoping, so any task ID in the shared task table is readable with preheat read on one project.
