Skip to content

release.yml can publish a signed, attested release containing zero binaries #402

Description

@avrabe

Found during a multi-persona review of REQ-GUARD-GATE-EVIDENCE-002. Verified at 618b29df.

This is the same defect family as #381/#384/#385/#388 — an error path that yields the ideal reading — but it sits on the release path rather than the merge gate, so its blast radius is a published artifact rather than a green check.

The chain

.github/workflows/release.yml:456-459 gathers release assets:

find artifacts -type f \
  \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.vsix" \) \
  -exec cp {} release-assets/ \;

find exits 0 when it matches nothing. Confirmed:

$ mkdir -p /tmp/rz && find /tmp/rz -type f -name '*.tar.gz' -exec cp {} /tmp/rz \; ; echo $?
0

Upstream, every one of the six upload-artifact steps leaves if-no-files-found at its default (warn):

job if-no-files-found
build-binaries (default: warn)
build-compliance (default: warn)
build-test-evidence (default: warn)
build-wasm (default: warn)
build-vsix (default: warn)
build-sbom (default: warn)

So an empty or partial build produces no upload error, find copies nothing, and sha256sum ./* (:481), the cosign signing, and gh release create ... release-assets/* (:559-562) all proceed over whatever survived — up to and including nothing.

The repo already knows the difference, which is what makes this a gap rather than a policy: the SBOM step immediately below (:464-470) is explicitly guarded with if [ -n "$SBOM_SRC" ] ... else ::error:: ... exit 1, and trace-fixtures.yml:453 sets if-no-files-found: error. Five sibling globs were not given the same treatment.

Second instance in the same workflow

release.yml:213-220:

set +e
cargo run --release -- analyze --root Analysis_Pkg::Full_System.Impl \
  test-data/analysis_test.aadl > test-evidence/validation/analyze-output.txt 2>&1
rc=$?
set -e
echo "exit_code=${rc}" >> test-evidence/validation/analyze-output.txt

exit_code= is written and never read — a grep of the whole repo finds line 220 as its only occurrence. A panic, or a --root selector that stops matching after a model rename, yields a green step, a green build-test-evidence job, a satisfied needs, and a released evidence bundle whose "validation" content is a crash trace.

Suggested fix

  1. if-no-files-found: error on all six uploads (the value the repo already uses elsewhere).
  2. Replace the bare find with a gathered-and-counted form that fails on zero, and assert an expected minimum asset count before signing — signing is the step that converts "we built nothing" into "we attested nothing".
  3. Either assert on rc in the evidence step, or stop writing a value nothing consumes.

Acceptance: a release run with an artificially emptied artifacts/ must fail before gh release create, not after.

NOT claimed: that any published release is currently empty. This is about what the pipeline permits.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions