@@ -20,20 +20,20 @@ Usage:
2020 $0 --type TYPE --image IMAGE[:TAG] --output FILE --predicate-only # extract only predicate content
2121
2222Options:
23- --type TYPE Attestation type (slsa|cyclonedx|spdx|vuln|license|triage|custom)
24- --image IMAGE Fully qualified image reference (required)
25- --choice Which attestation to fetch: index, all
26- --last Automatically select the most recent attestation if multiple exist
27- --output PATH Output file (single type) or directory (all types)
28- --list List available predicateTypes and counts
29- --show-null Show entries missing predicateType in --list
30- --inspect-null Inspect referrers missing predicateType
31- --verify Verify attestations using cosign before extraction
32- --no-extraction Skip extraction and content output (useful with --verify for verification-only)
33- --predicate-only Extract only the predicate content, not the full attestation envelope (mutually exclusive with --no-extraction)
23+ --type TYPE Attestation type (slsa|cyclonedx|spdx|vuln|license|triage|custom)
24+ --image IMAGE Fully qualified image reference (required)
25+ --choice Which attestation to fetch: index, all
26+ --last Automatically select the most recent attestation if multiple exist
27+ --output PATH Output file (single type) or directory (all types)
28+ --list List available predicateTypes and counts
29+ --show-null Show entries missing predicateType in --list
30+ --inspect-null Inspect referrers missing predicateType
31+ --verify Verify attestations using cosign before extraction
32+ --no-extraction Skip extraction and content output (useful with --verify for verification-only)
33+ --predicate-only Extract only the predicate content, not the full attestation envelope (mutually exclusive with --no-extraction)
3434 --certificate-oidc-issuer ISSUER OIDC issuer for verification (default: https://token.actions.githubusercontent.com)
3535 --certificate-identity-regexp REGEX Identity regexp for verification (default: Aleph Alpha workflows)
36- -h, --help Show this help
36+ -h, --help Show this help
3737
3838Verification:
3939 When --verify is used, attestations are verified using cosign verify-attestation before extraction.
@@ -340,27 +340,48 @@ if $VERIFY; then
340340fi
341341
342342DIGESTS=()
343- REFERRERS=$( oras discover " $IMAGE @$DIGEST " --format json \
344- | jq -r --arg pt " $PRED_TYPE " '
345- .referrers[]
346- | select(.artifactType=="application/vnd.dev.sigstore.bundle.v0.3+json")
347- | select(.annotations["dev.sigstore.bundle.predicateType"]==$pt)
348- | .digest' )
349-
350- for d in $REFERRERS ; do
351- echo " 🔎 Checking candidate referrer digest=$d "
352- layer_digest=$( oras manifest fetch " $IMAGE @$d " | jq -r ' .layers[0].digest' )
353- bundle=$( mktemp 2> /dev/null || mktemp -t cosign-extract)
354- oras blob fetch " $IMAGE @$layer_digest " --output " $bundle " > /dev/null
355- raw=$( jq -r ' .dsseEnvelope.payload' " $bundle " | base64 -d 2> /dev/null || jq -r ' .dsseEnvelope.payload' " $bundle " | base64 -D)
356- inner=$( echo " $raw " | jq -r ' .predicateType' )
357- echo " ↳ inner predicateType=$inner "
358- rm -f " $bundle "
359-
360- if [ " $inner " = " $PRED_TYPE " ]; then
361- DIGESTS+=(" $d " )
343+ # Collect referrers, reversing order if --last is used for optimization
344+ if $USE_LAST ; then
345+ REFERRERS=$( oras discover " $IMAGE @$DIGEST " --format json \
346+ | jq -r --arg pt " $PRED_TYPE " '
347+ [.referrers[]
348+ | select(.artifactType=="application/vnd.dev.sigstore.bundle.v0.3+json")
349+ | select(.annotations["dev.sigstore.bundle.predicateType"]==$pt)
350+ | .digest]
351+ | reverse
352+ | .[]' )
353+
354+ # When --last is used, trust the annotation filter and use the first referrer
355+ # (which is the most recent after reversing). Skip expensive verification loop.
356+ # We'll verify the inner predicateType only when fetching the actual content.
357+ FIRST_REFERRER=$( echo " $REFERRERS " | head -n 1)
358+ if [ -n " $FIRST_REFERRER " ]; then
359+ DIGESTS+=(" $FIRST_REFERRER " )
362360 fi
363- done
361+ else
362+ REFERRERS=$( oras discover " $IMAGE @$DIGEST " --format json \
363+ | jq -r --arg pt " $PRED_TYPE " '
364+ .referrers[]
365+ | select(.artifactType=="application/vnd.dev.sigstore.bundle.v0.3+json")
366+ | select(.annotations["dev.sigstore.bundle.predicateType"]==$pt)
367+ | .digest' )
368+
369+ # When not using --last, verify inner predicateType for all candidates
370+ for d in $REFERRERS ; do
371+ echo " 🔎 Checking candidate referrer digest=$d "
372+ layer_digest=$( oras manifest fetch " $IMAGE @$d " | jq -r ' .layers[0].digest' )
373+ bundle=$( mktemp 2> /dev/null || mktemp -t cosign-extract)
374+ oras blob fetch " $IMAGE @$layer_digest " --output " $bundle " > /dev/null
375+ raw=$( jq -r ' .dsseEnvelope.payload' " $bundle " | base64 -d 2> /dev/null || jq -r ' .dsseEnvelope.payload' " $bundle " | base64 -D)
376+ inner=$( echo " $raw " | jq -r ' .predicateType' )
377+ echo " ↳ inner predicateType=$inner "
378+ rm -f " $bundle "
379+
380+ if [ " $inner " = " $PRED_TYPE " ]; then
381+ DIGESTS+=(" $d " )
382+ fi
383+ done
384+ fi
364385
365386if [ ${# DIGESTS[@]} -eq 0 ]; then
366387 echo " ❌ No attestations found for type=$TYPE (predicateType=$PRED_TYPE )"
@@ -372,12 +393,15 @@ if [ ${#DIGESTS[@]} -eq 0 ]; then
372393 exit 1
373394fi
374395
375- echo " 🔎 Found ${# DIGESTS[@]} attestations for type=$TYPE :"
376- i=1
377- for d in " ${DIGESTS[@]} " ; do
378- echo " [$i ] $d "
379- i=$(( i+ 1 ))
380- done
396+ # Skip listing when --last is used (we only validated one)
397+ if ! $USE_LAST ; then
398+ echo " 🔎 Found ${# DIGESTS[@]} attestations for type=$TYPE :"
399+ i=1
400+ for d in " ${DIGESTS[@]} " ; do
401+ echo " [$i ] $d "
402+ i=$(( i+ 1 ))
403+ done
404+ fi
381405
382406# Function to fetch + decode one attestation
383407fetch_attestation () {
0 commit comments