trivy-action checks its own repository out of github.com using the runner
token; on this self-hosted Gitea that token is a Gitea token, GitHub answers
"Bad credentials", and both scan steps die before trivy is installed.
Download the v0.69.3 release archive directly, verify it against a sha256
digest pinned inline, and unpack only the binary into .release-work/bin,
which is gitignored and excluded from the Docker build context. The gate
keeps its exact semantics: --exit-code 1, --severity HIGH,CRITICAL, table
output, unfixed vulnerabilities still in scope. The SPDX step writes the
same filename release_artifacts.py normalize-container-sbom consumes, and
TRIVY_CACHE_DIR keeps the vulnerability DB inside the work directory.
The release job stopped at the bind-source gate. RUNNER_TEMP is
container-internal on this runner and no runner mount exposes it on the Docker
host, so no sibling container could ever share it; only the workspace is
host-mounted.
Move every path shared between the runner's shell steps and its sibling
containers under $GITHUB_WORKSPACE/.release-work: the release directory, the
release builder's HOME and NuGet cache, both candidate image tars, and the
container SPDX inventory. Resolution now maps the workspace alone to its host
path, and each sibling binds that source at $GITHUB_WORKSPACE and works from
there instead of /source, so a shared path is the same string on both sides of
the boundary. publish-release.sh follows with a single bind and requires the
release directory to sit inside the workspace.
Ignore .release-work in Git so the tag gate's cleanliness check stays true while
artifacts accumulate, and in Docker so artifacts written between the two
candidate builds cannot alter the build context the byte-comparison gate
depends on. Skip it in the dependency inventory as well, keeping the restored
package cache out of the license policy scan.
The release workflow could not complete on the self-hosted sibling-container
runner. Four defects are corrected together because three of them share the
same release.yml plumbing.
Exclude the tag under release from the prior-tag probe. check-release-tag.sh
already requires the tag to point at HEAD, so the unfiltered listing always
matched itself and rejected every first release before the initial baseline
branch could run.
Resolve host bind sources once per job. Steps run inside the runner container
while docker run starts siblings on the host daemon, so GITHUB_WORKSPACE and
RUNNER_TEMP are not usable as bind sources. A new step maps both to host paths
through the runner's own mounts and exports them; the build, provenance, smoke,
finalize, and publish steps reuse them, and publish-release.sh receives them by
environment instead of repeating the inspection. Bind destinations stay at the
container-internal paths so runner-side reads keep working unchanged.
Prove the signing material before the first push. Gitea package versions are
immutable, so a missing or undecryptable cosign key must abort ahead of the
require_absent gate rather than after packages and the image are published.
Authenticate the pinned consumer fetches. Both consumer repositories are
private, so anonymous fetches fail; the release token is passed to the
verification step and applied as a URL-scoped extra header through git's
config environment, keeping it out of argv, remotes, and on-disk config.
Anonymous fetch remains the fallback for local runs.