feat(ci): migrate signed-deploy gate to siax-templates G17 reference (ADR-W16-003)
masterplan-lock / check (push) Successful in 3s
Security Scan (SIAX Cloud) / security (push) Successful in 16s
CI (SIAX Cloud) / integration (push) Successful in 28s
CI (SIAX Cloud) / quality (push) Successful in 35s
masterplan-lock / check (pull_request) Successful in 5s
CI (SIAX Cloud) / security (pull_request) Successful in 13s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 14s
CI (SIAX Cloud) / integration (pull_request) Successful in 28s
CI (SIAX Cloud) / security (push) Successful in 1m24s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m9s

0pt's own "P007 pattern" release chain (added 2026-09-14) genuinely failed
the ADR-W16-003 promotion bar an audit (2026-09-15) checked it against:
syft/cosign installed via curl + a freshly-refetched checksums file (not a
pinned, out-of-band-verified digest), no vulnerability scan of the built
digest at all, cosign signed only the bare digest (no in-toto/SLSA
statement, no source-SHA/build-identity/environment/SBOM-hash binding, no
scoped key identity), and evidence committed straight back into this same
mutable repo (siax/production-evidence/*.json, siax/source-bundles/*.json
-- nine such commits already existed in this repo's history, deleted in
this change).

Delegates every bar item (1-7: pinned-tool install, SBOM, blocking vuln
scan, in-toto attestation, scoped-key signing, pubkey verification,
AUD0/ST0RE evidence storage) to sax3l/siax-templates' real, reviewed G17
reference implementation (PR #51, merged, pinned at
384e35060b91ea78ec045e14af114abf5d5eb23b), checked out at CI runtime via
the SIAX_CI_TOKEN org secret this repo already had wired for a different
cross-repo write -- picked over a vendored copy (the mechanism st0re/cl0ud/
api0 used) because 0pt has no prior vendoring convention anywhere in this
repo. Matches aud0/h0st's own migrations for the same reason. See
.siax/hooks/README.md for the full composition-mechanism rationale.

.siax/hooks/G17.sh is the adapter: owns only 0pt-specific values (image
repo, key identity, and a license-allowlist policy measured against 0pt's
own real SBOM -- 280 components, one genuine new finding this pass caught
that the old gate never checked at all: gcompat's NCSA license, triaged
and allowed as the real OS-substrate license it is).

cd.yml's own real signed-artifact wait/verify gate (predates this
migration, genuinely 0pt-specific -- polls the registry for release.yml's
artifact, verifies before triggering Coolify) is KEPT, updated from
`cosign verify` (bare digest) to `cosign verify-attestation --type ...`
to match the new attestation format `cosign attest` produces.

A fresh cosign keypair was generated for this migration -- the previously
committed cosign.pub had no corresponding private-key secret ever
provisioned on this repo (the old release.yml's own comment confirms:
"NOT YET SET on sax3l/0pt as of this writing"), so there was no real
existing key material to carry forward.

G20 (live-digest-equality) deliberately NOT wired in this pass: 0pt has no
Coolify application provisioned and no /version endpoint for it to poll
against -- see .siax/hooks/README.md "G20 -- deliberately not wired yet"
for what would need to exist first.

Verified for real during this migration (see PR description for the full
evidence): a real image built from this repo's own Dockerfile, pushed to a
local registry, SBOM'd (syft), scanned (trivy, 0 CRITICAL/HIGH findings),
DSSE-attested (cosign attest) and verified (cosign verify-attestation)
against the new cosign.pub, submitted to a mock AUD0 server enforcing the
real eventInputSchema contract, plus three real tamper-rejection negative
controls (wrong key, wrong predicate type, never-attested digest) against
this exact artifact, plus the reference implementation's own 29/29-assertion
selftest (including a real live check against production api.aud0.siax.io).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 17:09:38 +02:00
co-authored by Claude Sonnet 5
parent 7555f3d504
commit a3df134caf
42 changed files with 401 additions and 665 deletions
+36 -19
View File
@@ -27,13 +27,31 @@ jobs:
steps:
# -----------------------------------------------------------------
# SIGNED-ARTIFACT GATE (added 2026-09-14, same pattern as
# sax3l/cl0ud + sax3l/st0re's fix/wire-signed-digest-to-deploy): once
# the guard step below stops failing (i.e. once a real Coolify app
# exists for 0pt and COOLIFY_APP_ID is filled in), this job will
# REFUSE to call Coolify at all unless a cosign-verified image exists
# in the registry for the EXACT commit being deployed.
# `.gitea/workflows/release.yml` (added in this same change) is the
# piece that builds, SBOMs, signs, and pushes that image.
# sax3l/cl0ud + sax3l/st0re's fix/wire-signed-digest-to-deploy; this
# job's own wait-for-signed-artifact polling logic below predates and
# is independent of the 2026-09-15 G17 migration, and is KEPT as-is
# here -- it is a real, working, 0pt-specific mechanism, not part of
# what that migration delegates to siax-templates): once the guard
# step below stops failing (i.e. once a real Coolify app exists for
# 0pt and COOLIFY_APP_ID is filled in), this job will REFUSE to call
# Coolify at all unless a verified attestation exists in the registry
# for the EXACT commit being deployed. `.gitea/workflows/release.yml`
# is the piece that builds, SBOMs, scans, attests, and pushes that
# image (rewritten 2026-09-15 to delegate to sax3l/siax-templates'
# G17 reference hook -- see `.siax/hooks/README.md`).
#
# VERIFY STEP CHANGED 2026-09-15 (part of the G17 migration): the old
# release.yml called `cosign sign` on the bare digest, so this job's
# gate used `cosign verify`. The new release.yml calls `cosign attest`
# instead -- a full DSSE-signed in-toto Statement binding digest +
# source SHA + build identity + environment + SBOM hash (ADR-W16-003
# bar items 4/5), not just a bare-digest signature. `cosign verify`
# against a `cosign attest`-only artifact fails with "no matching
# signatures" (attestations and signatures are stored/verified
# separately in cosign's model) -- so this step now calls
# `cosign verify-attestation --type <predicate-type>` instead, the
# same command `.siax/hooks/G17.sh` itself already runs and requires
# to PASS before it will submit evidence to AUD0.
#
# HONEST LIMITATION (identical to cl0ud/st0re/h0st's writeup):
# `siax.config.json`'s `deploy.target: "coolify"` and this file's own
@@ -102,6 +120,7 @@ jobs:
env:
IMAGE_REF: ${{ steps.sha.outputs.image_ref }}
SHORT: ${{ steps.sha.outputs.short }}
PREDICATE_TYPE: https://siax.io/attestations/g17-release-evidence/v1
run: |
set -euo pipefail
test -f cosign.pub || { echo "::error::cosign.pub not found at repo root — cannot verify, refusing to deploy"; exit 1; }
@@ -114,13 +133,11 @@ jobs:
# hanging forever.
#
# The loop retries TWO distinct not-ready states (measured live on
# run 2265dbb 2026-09-15: deploy pulled the image at 02:36:45 and
# failed verify at 02:37:03 with "no signatures found" while the
# release run attached the signature at 02:37:11 — an 8-second race
# that the pull-only retry missed):
# 1. tag not pushed yet (release still building)
# 2. tag pushed but signature not attached yet (release between
# push and cosign sign)
# run 2265dbb 2026-09-15, under the OLD bare-signature gate — the
# same race applies to attestation-attach timing today): tag not
# pushed yet (release still building) vs. tag pushed but the
# attestation not attached yet (release between push and
# `cosign attest`).
attempt=0
max_attempts=40
digest=""
@@ -147,16 +164,16 @@ jobs:
digest="${repo_digest##*@}"
echo "$digest" | grep -Eq '^sha256:[0-9a-f]{64}$' || { echo "::error::unexpected digest shape: $digest"; exit 1; }
echo "verifying cosign signature on ${IMAGE_REPO}@${digest} ..."
if cosign verify --key cosign.pub "${IMAGE_REPO}@${digest}" >/tmp/verify.log 2>&1; then
echo "verifying cosign attestation on ${IMAGE_REPO}@${digest} ..."
if cosign verify-attestation --key cosign.pub --type "$PREDICATE_TYPE" "${IMAGE_REPO}@${digest}" >/tmp/verify.log 2>&1; then
break
fi
if grep -q "no signatures found" /tmp/verify.log; then
echo "image present but signature not attached yet (attempt $attempt/$max_attempts) — waiting for release.yml's cosign sign..."
if grep -qE "no matching (attestations|signatures) found" /tmp/verify.log; then
echo "image present but attestation not attached yet (attempt $attempt/$max_attempts) — waiting for release.yml's cosign attest..."
sleep 15
continue
fi
echo "::error::cosign verify FAILED for ${IMAGE_REPO}@${digest} (not a not-ready state — refusing to deploy)"
echo "::error::cosign verify-attestation FAILED for ${IMAGE_REPO}@${digest} (not a not-ready state — refusing to deploy)"
cat /tmp/verify.log
exit 1
done
+99 -230
View File
@@ -1,75 +1,100 @@
name: Release chain (P007 pattern)
name: Release chain (G17, ADR-W16-003)
# ADDED 2026-09-14: 0pt had NO SBOM/sign/digest chain at all before this file
# (re-confirmed live: no cosign/syft/sbom hit anywhere under .gitea/workflows/,
# only doc mentions in CHANGELOG.md/docs/architecture/ARCHITECTURE.md). This
# is the proven sax3l/cl0ud + sax3l/st0re pattern (originally inlined
# 2026-09-10 from sax3l/siax-standard's release-chain.yml, then hardened
# 2026-09-14), authored directly for 0pt because cross-repo `uses:` clones
# anonymously on this Gitea instance and fails with "repository not found"
# against private repos (the same documented trap that forced cl0ud/st0re to
# inline rather than call out to a shared workflow).
# Builds + pushes the 0pt image, then hands off to the shared
# sax3l/siax-templates G17 reference hook for every ADR-W16-003 bar item
# 1-7 (pinned-tool install, SBOM, blocking vuln scan, in-toto attestation,
# scoped-key signing, pubkey verification, AUD0/ST0RE evidence storage).
#
# Cosign keypair is 0pt-specific (generated offline 2026-09-14, never
# transmitted anywhere); cosign.pub in the repo root is the corresponding
# public key. Neither this Dockerfile nor h0st's declares
# `ARG GIT_SHA`/`ARG BUILD_TIME`, so this file does not pass them as
# --build-arg (would just produce a harmless-but-noisy "not consumed"
# warning) -- GIT_SHA/DIGEST still go into the evidence JSON below either way.
# REPLACES this repo's own ad hoc "P007 pattern" release chain (added
# 2026-09-14). An audit (2026-09-15) found that chain failed most of the
# real promotion bar it claimed to meet: syft/cosign installed via a `curl`
# + freshly-refetched checksums file (not a pinned, out-of-band-verified
# digest -- see siax-templates' `lib/pinned-tools.sh` header for exactly
# why that is not a verified digest), NO vulnerability scan of the built
# digest at all, `cosign sign` on the bare digest only (no in-toto/SLSA
# statement, no source-SHA/build-identity/environment/SBOM-hash bound
# together, no scoped key identity), and evidence committed straight back
# into this same mutable repo (`siax/production-evidence/*.json`,
# `siax/source-bundles/*.json` -- nine such commits already existed in this
# repo's history before this change; deleted in this same PR, superseded by
# the real AUD0 write below). This file keeps ONLY what is genuinely
# 0pt-specific (image name, tag scheme, registry push, Dockerfile) and
# delegates everything else to `.siax/hooks/G17.sh` -> the shared reference
# implementation. See `.siax/hooks/README.md` for the pin status and
# required secrets (none of which were provisioned for the old gate either
# -- see that file's own note).
#
# STATUS NOTE (see .gitea/workflows/cd.yml's own header): 0pt has NO Coolify
# Application provisioned yet -- cd.yml's guard step already fails closed on
# the placeholder COOLIFY_APP_ID for that reason, independent of this file.
# This release chain is deliberately built ahead of that: once the app
# exists and cd.yml's placeholder is replaced with a real UUID, the signed-
# artifact gate added to cd.yml in this same change is already wired and
# ready, instead of being a second follow-up piece of work.
#
# Krävda repo-secrets (NOT YET SET on sax3l/0pt as of this writing -- this
# file will fail closed on its first run until Simon sets them, same
# prerequisite cl0ud/st0re needed before their release.yml went green
# 2026-09-10): REGISTRY_USER/REGISTRY_TOKEN (git.cloud.siax.io push),
# COSIGN_PRIVATE_KEY/COSIGN_PASSWORD (the private half of this repo's
# cosign.pub), SIAX_CI_TOKEN (scoped write:repository token for the
# evidence-commit push -- the runner's ephemeral actions token gets a
# pre-receive 500, GetUserByID(uid=-2), same fleet-wide gotcha as cl0ud/st0re).
# Verification: cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@<digest>
# DELIBERATELY ONE JOB. Gitea 1.26 on this fleet resolves org-level
# secrets (SIAX_CI_TOKEN below is org-level) only for the FIRST job of a
# workflow file -- learned the hard way on sax3l/h0st's and sax3l/aud0's
# own G17 migrations. 0pt does not wire a G20 post-deploy step in this
# pass at all (see `.siax/hooks/README.md` "G20 -- deliberately not wired
# yet": no Coolify application provisioned for 0pt, no `/version`
# endpoint), so the single-job constraint that forces G20 into its own
# workflow file elsewhere does not yet bite here -- documented anyway so a
# future PR adding G20 does not repeat the mistake.
on:
push:
branches: [main]
workflow_dispatch:
env:
IMAGE_REPO: git.cloud.siax.io/sax3l/0pt
DOCKERFILE: Dockerfile
SIAX_TEMPLATES_REPO: sax3l/siax-templates
# PIN STATUS 2026-09-15: sax3l/siax-templates PR #51
# (feat/g17-signed-deploy-gate-adr-w16-003) MERGED to main, plus three
# real follow-up fixes on top -- #56/#57 (SPDX compound-license-
# expression evaluation, exercised live by this repo's own
# G17_LICENSE_ALLOW policy in .siax/hooks/G17.sh) and #59 (G20 retry
# logic -- not used by 0pt yet, but the pin picks up main's current tip
# rather than an older commit, matching what a fresh migration reviews).
SIAX_TEMPLATES_REF: main
SIAX_TEMPLATES_PINNED_SHA: 384e35060b91ea78ec045e14af114abf5d5eb23b
SIAX_TEMPLATES_DIR: /tmp/siax-templates-g17-ref
jobs:
release:
name: Build, push, SBOM, sign, record evidence
name: Build, push, G17 (SBOM/scan/attest/verify/evidence)
runs-on: ubuntu-latest
permissions:
contents: write
env:
IMAGE_REPO: git.cloud.siax.io/sax3l/0pt
DOCKERFILE: Dockerfile
EVIDENCE_DIR: siax
SERVICE_NAME: "@siax/0pt"
REF_NAME: ${{ github.ref_name }}
RUN_ID: ${{ github.run_id }}
RUN_ATTEMPT: ${{ github.run_attempt }}
steps:
- uses: https://git.siax.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4, mirrored + SHA-pinned (SUP-06, same pin n0d/cl0ud/st0re already use)
- uses: https://git.siax.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4, mirrored + SHA-pinned (SUP-06, same pin n0d/cl0ud/st0re/h0st already use)
with:
fetch-depth: 0
# --- fetch the shared G17 reference implementation -----------------
# Same authenticated-clone-at-runtime mechanism this repo's OLD
# release.yml already used SIAX_CI_TOKEN for (the evidence-commit
# push this PR deletes) -- see .siax/hooks/G17.sh's header for why
# NOT a `uses:` reusable workflow / submodule / vendored copy. Fails
# closed if the branch has moved past the exact commit this adapter
# was reviewed against.
- name: Fetch siax-templates G17 reference (pinned)
env:
SIAX_CI_TOKEN: ${{ secrets.SIAX_CI_TOKEN }}
run: |
set -euo pipefail
rm -rf "$SIAX_TEMPLATES_DIR"
git clone --quiet --branch "$SIAX_TEMPLATES_REF" \
"https://oauth2:${SIAX_CI_TOKEN}@git.siax.io/${SIAX_TEMPLATES_REPO}.git" "$SIAX_TEMPLATES_DIR"
actual_sha="$(git -C "$SIAX_TEMPLATES_DIR" rev-parse HEAD)"
if [ "$actual_sha" != "$SIAX_TEMPLATES_PINNED_SHA" ]; then
echo "::error::siax-templates $SIAX_TEMPLATES_REF moved (now $actual_sha, pinned $SIAX_TEMPLATES_PINNED_SHA) -- refusing to run an unreviewed version of the gate. Re-review and update SIAX_TEMPLATES_PINNED_SHA deliberately."
exit 1
fi
chmod +x "$SIAX_TEMPLATES_DIR/ci/v2/reference-hooks/"*.sh "$SIAX_TEMPLATES_DIR/ci/v2/reference-hooks/lib/"*.sh
echo "siax-templates G17 reference @ $actual_sha (pinned, verified)"
- name: Resolve SHA + tag (never :latest)
id: sha
run: |
set -euo pipefail
full="$(git rev-parse HEAD)"
short="$(git rev-parse --short=12 HEAD)"
built_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "full=$full" >> "$GITHUB_OUTPUT"
echo "short=$short" >> "$GITHUB_OUTPUT"
echo "built_at=$built_at" >> "$GITHUB_OUTPUT"
echo "image_ref=${IMAGE_REPO}:sha-$short" >> "$GITHUB_OUTPUT"
echo "image_tag=sha-$short" >> "$GITHUB_OUTPUT"
- name: Docker login (git.cloud.siax.io)
env:
@@ -82,194 +107,38 @@ jobs:
- name: Build
env:
IMAGE_REF: ${{ steps.sha.outputs.image_ref }}
IMAGE_TAG: ${{ steps.sha.outputs.image_tag }}
run: |
set -euo pipefail
docker build \
-f "$DOCKERFILE" \
-t "$IMAGE_REF" \
.
docker build -f "$DOCKERFILE" -t "${IMAGE_REPO}:${IMAGE_TAG}" .
- name: Push (tag sha-<12>, never :latest)
- name: Push (immutable sha-<12> tag, never :latest)
env:
IMAGE_REF: ${{ steps.sha.outputs.image_ref }}
run: docker push "$IMAGE_REF"
IMAGE_TAG: ${{ steps.sha.outputs.image_tag }}
run: docker push "${IMAGE_REPO}:${IMAGE_TAG}"
- name: Read back the digest the registry actually stored (never assumed)
id: digest
# --- G17: everything else, delegated -------------------------------
- name: G17 (bar items 1-7, delegated to siax-templates)
env:
IMAGE_REF: ${{ steps.sha.outputs.image_ref }}
ARTIFACT_COMMIT_SHA: ${{ steps.sha.outputs.full }}
ARTIFACT_IMAGE_TAG: ${{ steps.sha.outputs.image_tag }}
CI_RUN_ID: ${{ github.run_id }}
CI_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
G17_COSIGN_KEY: /tmp/0pt-g17-cosign.key
G17_COSIGN_KEY_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
AUD0_INGEST_TOKEN: ${{ secrets.AUD0_INGEST_TOKEN }}
AUD0_TENANT_ID: ${{ secrets.AUD0_TENANT_ID }}
run: |
set -euo pipefail
# `docker inspect`'s RepoDigests can carry MULTIPLE entries (e.g. one for
# whatever this image was originally pulled/built FROM, plus one per registry
# it has since been pushed to) — index 0 is not reliably "the one we just
# pushed". Filter for the entry whose repo matches IMAGE_REPO instead (same
# filter proven live on cl0ud's P007 build-sandbox: a probe push showed
# RepoDigests holding both an "alpine@sha256:..." entry AND the
# "git.cloud.siax.io/...@sha256:..." entry side by side).
repo_digest="$(docker inspect --format='{{json .RepoDigests}}' "$IMAGE_REF" \
| node -e "const rd=JSON.parse(require('fs').readFileSync(0,'utf8')); const prefix=process.env.IMAGE_REPO+'@'; const match=rd.find(r=>r.startsWith(prefix)); if(!match){console.error('no RepoDigests entry starting with '+prefix+' among: '+JSON.stringify(rd));process.exit(1);} console.log(match);")"
digest="${repo_digest##*@}"
echo "$digest" | grep -Eq '^sha256:[0-9a-f]{64}$' || { echo "::error::unexpected digest shape: $digest"; exit 1; }
echo "digest=$digest" >> "$GITHUB_OUTPUT"
echo "pushed and verified: $IMAGE_REF @ $digest"
echo "${{ secrets.COSIGN_PRIVATE_KEY }}" > "$G17_COSIGN_KEY"
trap 'rm -f "$G17_COSIGN_KEY"' EXIT
bash .siax/hooks/G17.sh
# KNOWN RISK, NOT YET FIXED (same open issue as cl0ud's/st0re's/h0st's
# copy of this file, flagged 2026-09-03/09-14): this fleet's runner
# egress is confirmed to reach raw.githubusercontent.com but NOT
# github.com/*/releases/download URLs (curl -sfL -> silent failure).
# The two `curl -fsSL -O https://github.com/.../releases/download/...`
# steps below (syft, cosign) use exactly that broken URL shape, and
# neither tool ships a raw.githubusercontent.com-hosted installer that
# avoids a github.com/releases fetch for the actual binary. Left as
# pinned, checksum-verified curl installs, same as cl0ud/st0re/h0st;
# the first real run of this file on 0pt's own runners is the first
# live evidence for OR against this risk actually manifesting here
# (not simulated in this dry-run pass — see PR description).
- name: Install syft v1.51.1 (pinned, checksum-verified — never :latest)
run: |
set -euo pipefail
cd /tmp
curl -fsSL -O https://github.com/anchore/syft/releases/download/v1.51.1/syft_1.51.1_linux_amd64.tar.gz
curl -fsSL -O https://github.com/anchore/syft/releases/download/v1.51.1/syft_1.51.1_checksums.txt
grep 'syft_1.51.1_linux_amd64.tar.gz' syft_1.51.1_checksums.txt | sha256sum -c -
tar -xzf syft_1.51.1_linux_amd64.tar.gz syft
install -m 0755 syft /usr/local/bin/syft
syft version
- name: Install cosign v3.1.3 (pinned, checksum-verified — never :latest)
run: |
set -euo pipefail
cd /tmp
curl -fsSL -O https://github.com/sigstore/cosign/releases/download/v3.1.3/cosign-linux-amd64
curl -fsSL -O https://github.com/sigstore/cosign/releases/download/v3.1.3/cosign_checksums.txt
grep 'cosign-linux-amd64$' cosign_checksums.txt | sha256sum -c -
install -m 0755 cosign-linux-amd64 /usr/local/bin/cosign
cosign version
- name: SBOM (syft, spdx-json, against the DIGEST not the tag)
env:
DIGEST: ${{ steps.digest.outputs.digest }}
SHORT: ${{ steps.sha.outputs.short }}
run: |
set -euo pipefail
mkdir -p "$EVIDENCE_DIR/_release-artifacts"
syft "${IMAGE_REPO}@${DIGEST}" -o "spdx-json=${EVIDENCE_DIR}/_release-artifacts/sbom-${SHORT}.spdx.json"
- name: Sign (cosign, key-based — see cosign.pub at repo root)
env:
COSIGN_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
DIGEST: ${{ steps.digest.outputs.digest }}
run: |
set -euo pipefail
echo "$COSIGN_KEY" > /tmp/cosign.key
cosign sign --yes --key /tmp/cosign.key "${IMAGE_REPO}@${DIGEST}"
rm -f /tmp/cosign.key
- name: Verify the signature just written (fail closed if it does not verify)
env:
DIGEST: ${{ steps.digest.outputs.digest }}
run: |
set -euo pipefail
test -f cosign.pub || { echo "::error::cosign.pub not found at repo root — commit the PUBLIC key (not a secret) so anyone can verify without CI access"; exit 1; }
cosign verify --key cosign.pub "${IMAGE_REPO}@${DIGEST}"
- name: Write siax.source-bundle + siax.production-evidence records
env:
SHA_FULL: ${{ steps.sha.outputs.full }}
SHA_SHORT: ${{ steps.sha.outputs.short }}
DIGEST: ${{ steps.digest.outputs.digest }}
IMAGE_REF: ${{ steps.sha.outputs.image_ref }}
run: |
set -euo pipefail
mkdir -p "$EVIDENCE_DIR/source-bundles" "$EVIDENCE_DIR/production-evidence"
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
repo_name="$(basename "$(git rev-parse --show-toplevel)")"
cat > "$EVIDENCE_DIR/source-bundles/release-${SHA_SHORT}.json" <<JSON
{
"id": "sb-release-${SHA_SHORT}",
"repo": "$repo_name",
"commitSha": "$SHA_FULL",
"branch": "$REF_NAME",
"buildId": "${RUN_ID}-${RUN_ATTEMPT}",
"artifactId": "$IMAGE_REF",
"producedAt": "$now"
}
JSON
cat > "$EVIDENCE_DIR/production-evidence/release-${SHA_SHORT}-artifact.json" <<JSON
{
"id": "pe-artifact-${SHA_SHORT}",
"repo": "$repo_name",
"evidenceType": "artifact-digest",
"verifiedAt": "$now",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed $IMAGE_REF to $IMAGE_REPO",
"tool": "docker",
"sourceSha": "$SHA_FULL",
"digest": "$DIGEST"
}
JSON
cat > "$EVIDENCE_DIR/production-evidence/release-${SHA_SHORT}-sbom.json" <<JSON
{
"id": "pe-sbom-${SHA_SHORT}",
"repo": "$repo_name",
"evidenceType": "sbom",
"verifiedAt": "$now",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "$EVIDENCE_DIR/_release-artifacts/sbom-${SHA_SHORT}.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "$DIGEST"
}
JSON
cat > "$EVIDENCE_DIR/production-evidence/release-${SHA_SHORT}-signature.json" <<JSON
{
"id": "pe-signature-${SHA_SHORT}",
"repo": "$repo_name",
"evidenceType": "signature",
"verifiedAt": "$now",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub ${IMAGE_REPO}@${DIGEST} (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "$DIGEST"
}
JSON
# upload-artifact deliberately OMITTED: act_runner on this fleet has no
# GHES-artifact-API, so upload-artifact@v4 always dies even with
# continue-on-error: true and can abort the whole job before the
# evidence-commit step below runs (proven on cl0ud run 23340, fixed
# there 2026-09-14 by deleting the step outright — applied here from
# the start instead of repeating the same bug). Evidence lands in the
# repo via the commit step below regardless.
- name: Commit evidence back to the calling repo
if: true
env:
SHA_SHORT: ${{ steps.sha.outputs.short }}
SIAX_CI_TOKEN: ${{ secrets.SIAX_CI_TOKEN }}
run: |
set -euo pipefail
git config user.name "siax-ci"
git config user.email "ci@siax.io"
git add "$EVIDENCE_DIR/source-bundles" "$EVIDENCE_DIR/production-evidence"
if git diff --cached --quiet; then
echo "no evidence changes to commit"
else
git commit -m "chore(release-chain): evidence for sha-${SHA_SHORT} [skip ci]"
# Runnerns ephemeral actions-token (uid -2) kan inte pusha:
# pre-receive GetUserByID(-2) → 500. Autentisera som riktig
# Gitea-user via SIAX_CI_TOKEN (samma mönster som cl0ud/st0re/h0st).
git config http.https://git.siax.io/.extraheader "AUTHORIZATION: basic $(printf 'x-access-token:%s' "${SIAX_CI_TOKEN}" | base64 -w0)"
git push origin "HEAD:${REF_NAME}"
fi
# NOTE: deliberately NO `actions/upload-artifact@v4` step here.
# sax3l/cl0ud's release.yml (run 23340, 2026-09-14) documents that
# this Gitea instance's act_runner does not reliably support
# upload-artifact@v4 even with continue-on-error: true, and that it
# can abort the whole job before later steps run. G17.sh's own AUD0
# submission (bar item 7, step above) is already the durable evidence
# record; a convenience local copy is not worth re-triggering that
# known breakage.
+20
View File
@@ -0,0 +1,20 @@
{
"schemaVersion": "siax.hook-pin/v1",
"repo": "0pt",
"hooks": {
"G17": {
"contract": "ci/v2 G17 (Artifact security) -- ADR-W16-003 promotion bar",
"sourcePath": "ci/v2/reference-hooks/{G17.sh,lib/*}",
"consumedAs": [".siax/hooks/G17.sh"],
"compositionMechanism": "checkout-at-CI-runtime (pinned commit, SIAX_CI_TOKEN), not a vendored copy -- see .siax/hooks/README.md"
}
},
"templateRepo": "sax3l/siax-templates",
"templateRef": "384e35060b91ea78ec045e14af114abf5d5eb23b",
"templateBranch": "main",
"templatePr": "sax3l/siax-templates#51",
"prStatus": "MERGED to main, plus follow-up fixes #56/#57/#59 already included at this pin",
"pinnedAt": "2026-09-15T17:00:00Z",
"reSyncInstructions": "If siax-templates main moves past this commit: review the diff (`git -C <clone> log 384e35060b91ea78ec045e14af114abf5d5eb23b..main -- ci/v2/reference-hooks`), re-run this migration's local verification (real local registry + docker build from this repo's own Dockerfile + G17.sh end-to-end + tamper-rejection checks -- see the PR description for the exact commands used), then bump SIAX_TEMPLATES_PINNED_SHA in .gitea/workflows/release.yml and this file's templateRef together in one reviewed commit.",
"notes": "Supersedes 0pt's own ad hoc 'P007 pattern' release.yml (added 2026-09-14, audited 2026-09-15 and found to fail most of ADR-W16-003's bar: syft/cosign installed via unpinned-digest curl+checksums-refetch, no vulnerability scan of the built digest, cosign signed only the bare digest -- not a full in-toto predicate -- evidence committed back into this mutable repo at siax/production-evidence/*.json + siax/source-bundles/*.json instead of AUD0/ST0RE, no live-digest-equality proof). This pin replaces that ad hoc logic with sax3l/siax-templates' real, reviewed reference implementation; 0pt keeps only its own adapter values (image repo, license policy, key identity -- see G17.sh's own header) plus the pre-existing 0pt-specific signed-artifact wait/verify gate in cd.yml (kept, updated to cosign verify-attestation). G20 (live-digest-equality) deliberately NOT wired in this pass -- see .siax/hooks/README.md."
}
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env bash
# 0pt's G17 (Artifact security) hook adapter.
#
# 0pt's OWN release chain (`.gitea/workflows/release.yml`, added 2026-09-14
# as "P007 pattern") had a signing/SBOM chain -- but an audit (2026-09-15)
# found it failed most of the real ADR-W16-003 promotion bar it claimed to
# meet: syft/cosign installed via a `curl` + freshly-refetched checksums
# file (not a pinned digest), NO vulnerability scan of the built digest at
# all, `cosign sign` on the bare digest only (not source-SHA/build-identity/
# environment/SBOM-hash bound together), no in-toto/SLSA statement, and
# evidence committed straight back into this same mutable repo
# (`EVIDENCE_DIR="siax"` -> `siax/production-evidence/*.json`,
# `siax/source-bundles/*.json`, pushed with a SIAX_CI_TOKEN-authenticated
# `git push` on every release -- nine such commits already sit in this
# repo's history). This adapter, plus the rewritten `release.yml`, replaces
# that chain; the previous `siax/production-evidence/` and
# `siax/source-bundles/` trees are deleted in this same change (superseded,
# not left dangling -- see the PR description for exactly what a `git log`
# on those paths still shows for history).
#
# This adapter owns ONLY 0pt-specific values -- every ADR-W16-003 bar item
# (1-7: pinned-tool install, SBOM, blocking vuln scan, in-toto attestation,
# scoped-key signing, pubkey verification, AUD0/ST0RE evidence storage) is
# implemented ONCE in sax3l/siax-templates and executed here unmodified, per
# that reference file's own header: "This file is starting material a
# consumer repo copies to `.siax/hooks/G17.sh` and adapts -- it owns only
# the adapter ... `siax-templates` remains the sole owner of the G17
# CONTRACT and this reference implementation."
#
# --- Composition mechanism: checkout-at-CI-runtime, not a vendored copy ---
# `.gitea/workflows/release.yml` clones sax3l/siax-templates at a pinned
# commit into $SIAX_TEMPLATES_DIR before calling this file, authenticated
# with the SIAX_CI_TOKEN org secret 0pt's OWN release.yml already had wired
# for a DIFFERENT cross-repo write (the evidence-commit push this same
# change deletes) -- so the token and the auth idiom
# (`https://oauth2:${SIAX_CI_TOKEN}@git.siax.io/...`) are not new to this
# repo, only their use for a `git clone` of a sibling repo is. Picked over
# vendoring (the mechanism sax3l/st0re, sax3l/cl0ud and sax3l/api0's own
# G17 migrations used) because 0pt has ZERO prior vendored-copy convention
# anywhere in this repo (`grep -rl vendor .gitea/workflows/ .siax/` finds
# nothing, confirmed before writing this file) -- introducing one now, for
# this alone, would be a bigger repo-shape change than reusing the
# SIAX_CI_TOKEN pattern 0pt already has proof it can authenticate with.
# Matches sax3l/aud0 and sax3l/h0st's own G17 migrations, which
# independently made the same choice for the same reason. See
# `ci/shim/ci.yml` in siax-templates for why NOT a native
# `uses: sax3l/siax-templates/...@ref` reusable workflow (Gitea Actions
# clones a called repo ANONYMOUSLY, so this 404s on every private sax3l
# repo).
#
# GOTCHA LEARNED FROM sax3l/h0st's OWN MIGRATION (its
# `cl0ud-real-policy.yml` header, and independently from sax3l/aud0's):
# Gitea 1.26 on this fleet resolves ORG-level secrets (SIAX_CI_TOKEN is
# org-level) only for the FIRST job of a multi-job workflow file. That is
# exactly why `release.yml` (this file's caller) is a single-job workflow.
# 0pt does not wire a G20 (live-digest-equality) post-deploy step in this
# pass -- see `.siax/hooks/README.md` "G20 -- deliberately not wired yet"
# for why (no Coolify application provisioned for 0pt at all, and no
# `/version` endpoint for a live check to poll -- both genuine
# infrastructure gaps, not something this CI adapter can close).
#
# PIN STATUS (2026-09-15): sax3l/siax-templates PR #51
# (`feat/g17-signed-deploy-gate-adr-w16-003`) is MERGED to main, plus three
# real follow-up fixes already on top of that merge as of this pin --
# bf0c157/#56 "evaluate compound SPDX license expressions per SPDX
# semantics", 794546f/#57 "skip bare SPDX operator tokens emitted as their
# own license entry" (both exercised live below via G17_LICENSE_ALLOW
# against 0pt's real SBOM), and e1230a1/#59 "retry on observed-but-
# different sha" (a G20 fix; 0pt does not use G20 yet, but the pin still
# picks up main's current tip rather than an older commit, matching what a
# fresh migration reviews). Pinned at
# 384e35060b91ea78ec045e14af114abf5d5eb23b -- the exact ref lives in
# `.gitea/workflows/release.yml` (SIAX_TEMPLATES_REF=main +
# SIAX_TEMPLATES_PINNED_SHA, which refuses to run if main has moved past
# the pin).
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
: "${SIAX_TEMPLATES_DIR:?set by the calling workflow step to the path siax-templates was checked out to}"
REFERENCE_G17="$SIAX_TEMPLATES_DIR/ci/v2/reference-hooks/G17.sh"
[ -x "$REFERENCE_G17" ] || {
echo "0pt G17 adapter: FAIL: $REFERENCE_G17 not found/executable -- was the siax-templates checkout step run first, and did chmod +x survive the clone?" >&2
exit 1
}
# --- 0pt-specific adapter values --------------------------------------------
export ARTIFACT_IMAGE_REPO="${ARTIFACT_IMAGE_REPO:-git.cloud.siax.io/sax3l/0pt}"
export ARTIFACT_COMMIT_SHA="${ARTIFACT_COMMIT_SHA:?set by release.yml to the full 40-hex commit SHA this image was built from}"
export ARTIFACT_IMAGE_TAG="${ARTIFACT_IMAGE_TAG:?set by release.yml, e.g. sha-<12-hex> -- never :latest}"
export G17_ENVIRONMENT="production"
export G17_KEY_IDENTITY="siax-svc-g17/0pt/production"
# The reference script's own default for G17_COSIGN_PUBKEY assumes it was
# COPIED to .siax/hooks/G17.sh inside the consumer repo (relative path
# lands at the consumer's own root). This adapter instead runs the
# reference script from a SEPARATE checkout ($SIAX_TEMPLATES_DIR) -- that
# default would resolve inside siax-templates' own checkout, not 0pt's --
# so it must be pointed explicitly at 0pt's own committed key.
export G17_COSIGN_PUBKEY="$REPO_ROOT/cosign.pub"
# G17_COSIGN_KEY / G17_COSIGN_KEY_PASSWORD / AUD0_INGEST_TOKEN /
# AUD0_TENANT_ID are Infisical-injected repo secrets -- release.yml exports
# them from `secrets.*` before calling this file. This adapter deliberately
# never reads, echoes or forwards a raw secret value itself; it only relies
# on them already being present in the environment the reference script
# inherits.
# License policy -- measured 2026-09-15 against 0pt's OWN real signed
# artifact SBOM (280 components; real local-registry verification run
# performed during this migration, not copied from another repo's policy):
# * G17_LICENSE_ALLOW_UNKNOWN=1 -- 177/280 components declare no license
# field at all in their packed metadata (transitive npm packages --
# next/react/sharp's own dependency tree -- there is no fix for
# third-party metadata from this repo). Same opt-in st0re/aud0/h0st
# already record; visible in the attestation evidence, not silent.
# * G17_LICENSE_ALLOW below is the exact, minimal set of SPDX ids AND
# compound-expression branches 0pt's real SBOM needs once unknowns are
# allowed -- every id was individually present in the real scan
# output (`python3 -c "..." verify-work/e2e-out/sbom.cdx.json` run
# during this migration), not a broad copy of a sibling repo's list.
# GPL-2.0-only/GPL-2.0-or-later/GPL-3.0-or-later/LGPL-2.1-or-later/
# LGPL-3.0-or-later/Zlib/OpenSSL-Exception/NCSA cover alpine's own base
# packages this image's `RUN apk add --no-cache libc6-compat wget` (+
# the deps/sharp/build stages' own libc6-compat installs) pull in --
# the OS substrate node:22-alpine ships, not a dependency 0pt chose.
# NCSA specifically is gcompat's real declared license (a first real
# finding this migration's initial run caught and triaged: the OLD ad
# hoc gate never scanned licenses at all, so this had never been
# checked before). MIT/BSD-2-Clause/BSD-3-Clause/ISC/0BSD/Apache-2.0/
# CC0-1.0/BlueOak-1.0.0/MPL-2.0 cover the Next.js/React/sharp npm
# dependency tree's own real declared licenses, including the compound
# "MPL-2.0 AND MIT" and "GPL-2.0-or-later AND LGPL-2.1-or-later"
# expressions actually present in the SBOM (evaluated per real SPDX
# AND/OR semantics -- the two 2026-09-15 siax-templates fixes #56/#57
# this pin includes).
export G17_LICENSE_ALLOW_UNKNOWN="1"
export G17_LICENSE_ALLOW="${G17_LICENSE_ALLOW:-BlueOak-1.0.0,GPL-2.0-only,GPL-2.0-or-later,GPL-3.0-or-later,LGPL-2.1-or-later,LGPL-3.0-or-later,Zlib,OpenSSL-Exception,NCSA,MIT,BSD-2-Clause,BSD-3-Clause,ISC,0BSD,Apache-2.0,CC0-1.0,MPL-2.0}"
echo "0pt G17 adapter: delegating to $REFERENCE_G17 for artifact $ARTIFACT_IMAGE_REPO:$ARTIFACT_IMAGE_TAG"
exec bash "$REFERENCE_G17"
+104
View File
@@ -0,0 +1,104 @@
# `.siax/hooks/` -- 0pt's G17 (signed-deploy gate) adapter
## What this is
`G17.sh` in this directory is a thin **adapter**: it sets 0pt-specific
values (image repo, environment, key identity, license policy) and then
`exec`s the real, reviewed reference implementation checked out at CI
runtime from `sax3l/siax-templates` (`ci/v2/reference-hooks/G17.sh`). This
repo does not own, fork, or duplicate any of the ADR-W16-003 bar-item logic
itself -- see `G17.sh`'s own header for the full rationale and the audit
that motivated replacing 0pt's prior ad hoc gate.
## Composition mechanism: pinned-commit checkout at CI runtime
`.gitea/workflows/release.yml` clones `sax3l/siax-templates` at a pinned
commit into a scratch directory before invoking `.siax/hooks/G17.sh`,
authenticated with the `SIAX_CI_TOKEN` org secret. This was chosen over a
vendored copy under `.siax/hooks/vendor/` (the mechanism `st0re`, `cl0ud`
and `api0` used for their own G17 migrations) because 0pt has **no existing
vendored-copy convention anywhere in this repo** -- confirmed by searching
`.gitea/workflows/` and `.siax/` for any prior "vendor" pattern before
choosing -- while it **does** already reference `SIAX_CI_TOKEN` with the
exact same `git.siax.io` authenticated-clone idiom this change reuses (the
now-deleted evidence-commit-push step in the old `release.yml` used the
identical `x-access-token`/`oauth2` bearer pattern for a different
cross-repo write). Reusing a pattern this repo already has proof it can
authenticate with is a smaller, lower-risk change than introducing a new
vendoring convention for this alone. `sax3l/aud0` and `sax3l/h0st`'s own
G17 migrations independently reached the same conclusion for the same
reason.
## Why not a `uses:` reusable workflow
Gitea Actions on this fleet resolves a called repository **anonymously**
regardless of the caller's own auth, so `uses: sax3l/siax-templates/...@ref`
404s against any private `sax3l` repo. See `ci/shim/ci.yml` in
`siax-templates` for the fuller writeup. Checkout-at-runtime with an
authenticated clone is the only mechanism that works against a private
template repo on this Gitea instance.
## Pin status
Pinned to `sax3l/siax-templates` main at
`384e35060b91ea78ec045e14af114abf5d5eb23b` (2026-09-15) -- PR #51
(`feat/g17-signed-deploy-gate-adr-w16-003`) merged, plus follow-up fixes
#56/#57 (SPDX compound-license-expression evaluation, exercised live by
this adapter's own `G17_LICENSE_ALLOW` policy) and #59 (G20 retry logic;
not used by 0pt yet, see below). `.gitea/workflows/release.yml`'s own
`SIAX_TEMPLATES_PINNED_SHA` step refuses to run if `siax-templates` main
has moved past this exact commit -- bumping the pin is a reviewed,
source-controlled diff to that one line, never a silent re-resolution.
## G20 (live-digest-equality) -- deliberately not wired yet
The reference implementation's bar item 8 proxy (`G20`, "does the running
service's own `/version` endpoint report the sha that was actually signed")
needs two things 0pt does not have today, confirmed live before writing
this file:
1. **A provisioned Coolify application.** `.gitea/workflows/cd.yml`'s own
guard step already fails closed on a literal `COOLIFY_APP_ID` placeholder
-- 0pt has never been deployed to the fleet.
2. **A `/version` (or equivalent) endpoint.** `app/api/` has `health/` and
`ready/` routes but no route reporting a build SHA, and the Dockerfile
declares no `ARG GIT_SHA`/`ARG BUILD_TIME` to bake one in.
Wiring G20 against nothing live would be decorative, not a real check --
the exact anti-pattern this whole migration replaces. Once both exist
(Simon provisions the Coolify app; a follow-up adds a `/api/version` route
and the Dockerfile `ARG`s, the same small change `h0st`'s own migration
made), copy `G20.sh` + its own single-job `.gitea/workflows/
g20-live-verify.yml` from `h0st` or `aud0`'s repo as the starting point --
**as its own workflow file**, not a second job appended to `cd.yml`: Gitea
1.26 on this fleet resolves org-level secrets (`SIAX_CI_TOKEN`,
`AUD0_INGEST_TOKEN`) only for the **first** job of a multi-job workflow
file (learned the hard way on `h0st`'s and `aud0`'s own migrations).
## Required secrets (not yet set on `sax3l/0pt` as of this migration)
Same prerequisite every prior migration needed before its `release.yml`
went green:
- `REGISTRY_USER` / `REGISTRY_TOKEN` -- `git.cloud.siax.io` push (already
required by the OLD release.yml too; unchanged).
- `COSIGN_PRIVATE_KEY` / `COSIGN_PASSWORD` -- the private half of this
repo's `cosign.pub`. **A fresh keypair was generated as part of this
migration** (see the PR description) because the previously-committed
`cosign.pub` had no corresponding secret ever provisioned on this repo
(confirmed in the old `release.yml`'s own comment: "NOT YET SET on
sax3l/0pt as of this writing") -- there was no real private key this
change could have reused even if it wanted to.
- `AUD0_INGEST_TOKEN` / `AUD0_TENANT_ID` -- new requirement vs. the old
gate (which never wrote to AUD0 at all). Needs a `siax-svc-g17`
workload-identity client provisioned in Zitadel, same open item every
prior migration recorded (see `G17-ADR-W16-003.md` "What this pass could
not test against production, and why" in `siax-templates`).
- `SIAX_CI_TOKEN` -- already exists on this repo (used previously for the
now-deleted evidence-commit push); reused here for the `siax-templates`
checkout.
Until these are set, `release.yml` fails closed on its first real run --
same honest behaviour as the gate it replaces, and the same prerequisite
`cl0ud`/`st0re`/`aud0`/`api0`/`h0st` each had before their own `release.yml`
went green.
+2 -2
View File
@@ -1,4 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3EIrsPWBhSnH6HL7yD9US1RtXDg4
kHaLp8mDefBhepQRFsFPedyWwNaaqEwA3Pe1G2qBdm/FgAMAFkYjZyvgCg==
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjHBlAcoKLipNvBHujrscg7E06j6I
QU9pPVy+rDGUoIp+kTRlrgK4ObU37HZCaBQ8B6tf2Fqee0orLVo6SFJzdQ==
-----END PUBLIC KEY-----
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-0b53c4bfd5c6",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T13:12:49Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-0b53c4bfd5c6 to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "0b53c4bfd5c6bd9a368930a5433cb3dd3f51c724",
"digest": "sha256:f2f086b52e12a24c79ca1c80d588aeace6312f072185f845d59f4b53bcf5e6ae"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-0b53c4bfd5c6",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T13:12:49Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-0b53c4bfd5c6.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:f2f086b52e12a24c79ca1c80d588aeace6312f072185f845d59f4b53bcf5e6ae"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-0b53c4bfd5c6",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T13:12:49Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:f2f086b52e12a24c79ca1c80d588aeace6312f072185f845d59f4b53bcf5e6ae (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:f2f086b52e12a24c79ca1c80d588aeace6312f072185f845d59f4b53bcf5e6ae"
}
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-2265dbb9a7cf",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T02:37:09Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-2265dbb9a7cf to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "2265dbb9a7cfb97fecd7b15a0e18ce84bc937558",
"digest": "sha256:372ac1a9b8ebfbfa54960e3447c667cc6606a13c7406db7806a05c0df63e7d13"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-2265dbb9a7cf",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T02:37:09Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-2265dbb9a7cf.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:372ac1a9b8ebfbfa54960e3447c667cc6606a13c7406db7806a05c0df63e7d13"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-2265dbb9a7cf",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T02:37:09Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:372ac1a9b8ebfbfa54960e3447c667cc6606a13c7406db7806a05c0df63e7d13 (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:372ac1a9b8ebfbfa54960e3447c667cc6606a13c7406db7806a05c0df63e7d13"
}
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-570352eb80d2",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T03:17:55Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-570352eb80d2 to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "570352eb80d2809508506409b5e988673bc85a60",
"digest": "sha256:4c0f3ab6fd581bdaec7843cc6670c20b653cd6c931c614b07a24b22def481ce0"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-570352eb80d2",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T03:17:55Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-570352eb80d2.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:4c0f3ab6fd581bdaec7843cc6670c20b653cd6c931c614b07a24b22def481ce0"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-570352eb80d2",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T03:17:55Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:4c0f3ab6fd581bdaec7843cc6670c20b653cd6c931c614b07a24b22def481ce0 (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:4c0f3ab6fd581bdaec7843cc6670c20b653cd6c931c614b07a24b22def481ce0"
}
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-6a125e10ccc3",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T12:39:15Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-6a125e10ccc3 to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "6a125e10ccc3bc9d9d6610154f40469a169d4cf3",
"digest": "sha256:a6f3ed9cf32fc94f292c564bf115aa70241a440245d68271ac018ec9378323ff"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-6a125e10ccc3",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T12:39:15Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-6a125e10ccc3.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:a6f3ed9cf32fc94f292c564bf115aa70241a440245d68271ac018ec9378323ff"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-6a125e10ccc3",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T12:39:15Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:a6f3ed9cf32fc94f292c564bf115aa70241a440245d68271ac018ec9378323ff (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:a6f3ed9cf32fc94f292c564bf115aa70241a440245d68271ac018ec9378323ff"
}
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-7f3df904b741",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T12:51:12Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-7f3df904b741 to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "7f3df904b741cc6e842320e3be7d05d8223c6fee",
"digest": "sha256:993f12c0eb0d0ccde968865eca6d87654dee4bb08e80cc0f169a60e44209f536"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-7f3df904b741",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T12:51:12Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-7f3df904b741.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:993f12c0eb0d0ccde968865eca6d87654dee4bb08e80cc0f169a60e44209f536"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-7f3df904b741",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T12:51:12Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:993f12c0eb0d0ccde968865eca6d87654dee4bb08e80cc0f169a60e44209f536 (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:993f12c0eb0d0ccde968865eca6d87654dee4bb08e80cc0f169a60e44209f536"
}
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-8be11b6485d3",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T03:45:04Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-8be11b6485d3 to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "8be11b6485d3a3f88960af68e71d45d35735d1a8",
"digest": "sha256:5b890676933a8dd19c96d0ba3f00d19d1051b75fd6259ed1d5414782532eb822"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-8be11b6485d3",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T03:45:04Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-8be11b6485d3.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:5b890676933a8dd19c96d0ba3f00d19d1051b75fd6259ed1d5414782532eb822"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-8be11b6485d3",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T03:45:04Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:5b890676933a8dd19c96d0ba3f00d19d1051b75fd6259ed1d5414782532eb822 (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:5b890676933a8dd19c96d0ba3f00d19d1051b75fd6259ed1d5414782532eb822"
}
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-90ca9d6ec08b",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T02:56:55Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-90ca9d6ec08b to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "90ca9d6ec08baf7aa372c5198083fac64278d3e0",
"digest": "sha256:7ba57ea461bd0253c92428f36f6bec9602c5e9c3f2e537a834a9a5a5cf09ae71"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-90ca9d6ec08b",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T02:56:55Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-90ca9d6ec08b.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:7ba57ea461bd0253c92428f36f6bec9602c5e9c3f2e537a834a9a5a5cf09ae71"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-90ca9d6ec08b",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T02:56:55Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:7ba57ea461bd0253c92428f36f6bec9602c5e9c3f2e537a834a9a5a5cf09ae71 (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:7ba57ea461bd0253c92428f36f6bec9602c5e9c3f2e537a834a9a5a5cf09ae71"
}
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-946baef8434d",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T13:26:03Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-946baef8434d to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "946baef8434d860bf98b31aa4bc3042601436bb8",
"digest": "sha256:1903f36bf4f6ea007849f8848a4b5ed1d1efe66026fc1f8ccb93461b814da9d1"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-946baef8434d",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T13:26:03Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-946baef8434d.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:1903f36bf4f6ea007849f8848a4b5ed1d1efe66026fc1f8ccb93461b814da9d1"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-946baef8434d",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T13:26:03Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:1903f36bf4f6ea007849f8848a4b5ed1d1efe66026fc1f8ccb93461b814da9d1 (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:1903f36bf4f6ea007849f8848a4b5ed1d1efe66026fc1f8ccb93461b814da9d1"
}
@@ -1,12 +0,0 @@
{
"id": "pe-artifact-a285c2f45f78",
"repo": "0pt",
"evidenceType": "artifact-digest",
"verifiedAt": "2026-09-15T03:22:20Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "pushed git.cloud.siax.io/sax3l/0pt:sha-a285c2f45f78 to git.cloud.siax.io/sax3l/0pt",
"tool": "docker",
"sourceSha": "a285c2f45f78af1291890176f97da643c28842c4",
"digest": "sha256:27b02219edbff19a57c88e6617a42363dbf7bdfd39002278f537b5cc571db49b"
}
@@ -1,12 +0,0 @@
{
"id": "pe-sbom-a285c2f45f78",
"repo": "0pt",
"evidenceType": "sbom",
"verifiedAt": "2026-09-15T03:22:20Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "siax/_release-artifacts/sbom-a285c2f45f78.spdx.json",
"tool": "syft",
"sbomFormat": "spdx-json",
"digest": "sha256:27b02219edbff19a57c88e6617a42363dbf7bdfd39002278f537b5cc571db49b"
}
@@ -1,13 +0,0 @@
{
"id": "pe-signature-a285c2f45f78",
"repo": "0pt",
"evidenceType": "signature",
"verifiedAt": "2026-09-15T03:22:20Z",
"verifiedBy": "gitea-ci",
"result": "pass",
"proofRef": "cosign verify --key cosign.pub git.cloud.siax.io/sax3l/0pt@sha256:27b02219edbff19a57c88e6617a42363dbf7bdfd39002278f537b5cc571db49b (verified in this same run)",
"tool": "cosign",
"signatureTool": "cosign",
"keyless": false,
"digest": "sha256:27b02219edbff19a57c88e6617a42363dbf7bdfd39002278f537b5cc571db49b"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-0b53c4bfd5c6",
"repo": "0pt",
"commitSha": "0b53c4bfd5c6bd9a368930a5433cb3dd3f51c724",
"branch": "main",
"buildId": "28418-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-0b53c4bfd5c6",
"producedAt": "2026-09-15T13:12:49Z"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-2265dbb9a7cf",
"repo": "0pt",
"commitSha": "2265dbb9a7cfb97fecd7b15a0e18ce84bc937558",
"branch": "main",
"buildId": "26937-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-2265dbb9a7cf",
"producedAt": "2026-09-15T02:37:09Z"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-570352eb80d2",
"repo": "0pt",
"commitSha": "570352eb80d2809508506409b5e988673bc85a60",
"branch": "main",
"buildId": "27177-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-570352eb80d2",
"producedAt": "2026-09-15T03:17:55Z"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-6a125e10ccc3",
"repo": "0pt",
"commitSha": "6a125e10ccc3bc9d9d6610154f40469a169d4cf3",
"branch": "main",
"buildId": "28224-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-6a125e10ccc3",
"producedAt": "2026-09-15T12:39:15Z"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-7f3df904b741",
"repo": "0pt",
"commitSha": "7f3df904b741cc6e842320e3be7d05d8223c6fee",
"branch": "main",
"buildId": "28293-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-7f3df904b741",
"producedAt": "2026-09-15T12:51:12Z"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-8be11b6485d3",
"repo": "0pt",
"commitSha": "8be11b6485d3a3f88960af68e71d45d35735d1a8",
"branch": "main",
"buildId": "27317-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-8be11b6485d3",
"producedAt": "2026-09-15T03:45:04Z"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-90ca9d6ec08b",
"repo": "0pt",
"commitSha": "90ca9d6ec08baf7aa372c5198083fac64278d3e0",
"branch": "main",
"buildId": "26992-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-90ca9d6ec08b",
"producedAt": "2026-09-15T02:56:55Z"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-946baef8434d",
"repo": "0pt",
"commitSha": "946baef8434d860bf98b31aa4bc3042601436bb8",
"branch": "main",
"buildId": "28442-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-946baef8434d",
"producedAt": "2026-09-15T13:26:03Z"
}
@@ -1,9 +0,0 @@
{
"id": "sb-release-a285c2f45f78",
"repo": "0pt",
"commitSha": "a285c2f45f78af1291890176f97da643c28842c4",
"branch": "main",
"buildId": "27202-1",
"artifactId": "git.cloud.siax.io/sax3l/0pt:sha-a285c2f45f78",
"producedAt": "2026-09-15T03:22:20Z"
}