Two findings on scripts/cosign-retry.sh from a Bugbot review (one Medium, one Low). Both concern how the retry harness tracks --output-* paths, so filed together.
1. Stale artifacts bypass the fail-closed check (Medium) -- :183
Retry attempts never clear prior --output-* files. The fail-closed check can therefore treat a leftover non-empty .cert/.sig from an earlier failed attempt as proof that a later exit 0 actually signed.
Concretely: a transient failure after cosign has already written its outputs (a Rekor upload timeout, say), followed by an exit 0 that writes nothing, reports signed and exits 0 -- the exact property the harness exists to pin.
2. Equals-form output paths are misparsed (Low) -- :132
outputs_from_args accepts both space-separated and --output-*=value forms, but after an equals-form flag it leaves the whole token in prev. The next argument is then emitted as another output path, so a successfulsign-blob using equals-form flags can fail closed with exit 3 on phantom missing paths.
Why one ticket
Both come from the same place: the script's model of "which files should exist after this attempt" is derived once and never reconciled with reality. One direction fails open (stale file read as success), the other fails closed (phantom path read as missing). Fixing either in isolation leaves the shared bookkeeping wrong.
Suggested shape
Truncate or remove the declared output paths at the start of every attempt, and parse equals-form flags by splitting on the first = so prev is not carried forward. Then test both directions -- a stale-file case that must fail, and an equals-form success that must pass. The stale-file case is the one that currently reports a signature that does not exist, so it is the one worth pinning hardest.
Two findings on
scripts/cosign-retry.shfrom a Bugbot review (one Medium, one Low). Both concern how the retry harness tracks--output-*paths, so filed together.1. Stale artifacts bypass the fail-closed check (Medium) --
:183Retry attempts never clear prior
--output-*files. The fail-closed check can therefore treat a leftover non-empty.cert/.sigfrom an earlier failed attempt as proof that a laterexit 0actually signed.Concretely: a transient failure after cosign has already written its outputs (a Rekor upload timeout, say), followed by an
exit 0that writes nothing, reports signed and exits 0 -- the exact property the harness exists to pin.2. Equals-form output paths are misparsed (Low) --
:132outputs_from_argsaccepts both space-separated and--output-*=valueforms, but after an equals-form flag it leaves the whole token inprev. The next argument is then emitted as another output path, so a successfulsign-blobusing equals-form flags can fail closed with exit 3 on phantom missing paths.Why one ticket
Both come from the same place: the script's model of "which files should exist after this attempt" is derived once and never reconciled with reality. One direction fails open (stale file read as success), the other fails closed (phantom path read as missing). Fixing either in isolation leaves the shared bookkeeping wrong.
Suggested shape
Truncate or remove the declared output paths at the start of every attempt, and parse equals-form flags by splitting on the first
=soprevis not carried forward. Then test both directions -- a stale-file case that must fail, and an equals-form success that must pass. The stale-file case is the one that currently reports a signature that does not exist, so it is the one worth pinning hardest.