Files
cozystack/hack/lib/promoted-packages.sh
Myasnikov Daniil 2d734fb229 fix(release): preflight the rc tree's own pin before publishing
hack/promote-packages-artifact.sh pushes the candidate BEFORE rewriting
installer values, so the artifact carries the rc tree's
platformSourceUrl/platformSourceRef verbatim — and
hack/verify-promoted-packages.sh reads exactly those two fields back out
of it as the candidate's rc baseline, requiring a trusted repository and
an immutable `digest=sha256:<64 hex>` pin.

The publisher checked neither. A tag-form ref or an untrusted URL in the
rc tree published a candidate and pushed two commits regardless; the
refusal arrived later, in the promote PR gate or in finalize, once the
staging branch and the draft release already existed. Check both at
dispatch time instead, while nothing has been written.

A copied regex would drift from the guard it is bringing forward and
quietly restore the late failure, so the trusted repository and the
digest pattern move into hack/lib/promoted-packages.sh and both halves
source it. That makes the library part of what the verifier needs at run
time, so promote's base-branch precondition gains it: the list exists
because the base's own workflows run after merge, and a base carrying
the verifier without its library fails at the same two late points.

Tests: both preflights are covered, each asserting the mock registry
client was never invoked — "exited non-zero" alone does not say nothing
was written. Both fail with the preflight removed. The publisher
fixtures now pin the trusted repository the way a real rc tree does, and
still send the candidate elsewhere via REGISTRY so the rewrite
assertions keep their teeth.

Also covers a malformed PROMOTION_ID. It is not attacker-influenced, but
it is the run-uniqueness suffix of the candidate tag and retention.yaml
deletes a candidate only when every tag on the manifest matches
`promotion-v…-run-[0-9]+-[0-9]+`; a value outside that shape publishes
an artifact no cleanup pass can ever collect.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-08-14 18:37:58 +05:00

26 lines
1.5 KiB
Bash

# shellcheck shell=sh
# Shared definitions for the stable-candidate packages artifact.
#
# Sourced by hack/promote-packages-artifact.sh, which publishes the candidate,
# and hack/verify-promoted-packages.sh, which proves it. The publisher's whole
# reason to inspect the pin at all is to make the verifier's refusal land at
# dispatch time, before a registry write, two commits and a draft release exist
# — so the two have to be testing the same thing. Copied constants would drift
# apart silently and turn that dispatch-time refusal back into the late failure
# it was added to remove; they live here instead.
#
# Not a general library: promotion is the only caller, and both halves run from
# a `hack`-only sparse checkout of a trusted ref, so this file has to stay
# inside hack/ next to them.
# The one OCI repository a packages candidate may ever be published to or read
# back from. Overridable only so the behavioural suites can point both halves
# at a fixture registry; production never sets it.
EXPECTED_PACKAGES_REPOSITORY="${EXPECTED_PACKAGES_REPOSITORY:-oci://ghcr.io/cozystack/cozystack/cozystack-packages}"
# An immutable Flux OCI source pin, `digest=sha256:<64 lowercase hex>`. The
# tag form Flux also accepts is exactly what promotion must neither publish nor
# accept: a stable installer pinned by tag resolves to whatever that tag points
# at later, which is how a stable install ends up serving rc content (#3477).
PACKAGES_DIGEST_REF_PATTERN='^digest=sha256:[0-9a-f]{64}$'