Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions scripts/tests/actions-lock-update-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ if [ "${2:-}" = "--verify-local" ]; then
printf '%s\n' '{"valid":true,"findings":[{"workflow":".github/workflows/ci.yml","category":"sha-as-ref","severity":"warning","dependency":"actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1"}]}'
exit 1
;;
invalid-advisory-and-reusable)
printf '%s\n' '{"valid":false,"findings":[{"workflow":".github/workflows/ci.yml","category":"sha-as-ref","severity":"warning","dependency":"actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1"},{"workflow":".github/workflows/reusable.yml","category":"stale","severity":"warning","dependency":"hyperpolymath/standards@abc123"}]}'
exit 1
;;
invalid-advisory-and-real-stale)
printf '%s\n' '{"valid":false,"findings":[{"workflow":".github/workflows/ci.yml","category":"sha-as-ref","severity":"warning","dependency":"actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1"},{"workflow":".github/workflows/ci.yml","category":"stale","severity":"warning","dependency":"actions/upload-artifact@v4"}]}'
exit 1
;;
invalid-advisory-only)
printf '%s\n' '{"valid":false,"findings":[{"workflow":".github/workflows/ci.yml","category":"sha-as-ref","severity":"warning","dependency":"actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1"}]}'
exit 1
;;
invalid-empty)
printf '%s\n' '{"valid":false,"findings":[]}'
exit 1
Expand Down Expand Up @@ -145,6 +157,32 @@ if FAKE_VERIFY_FINDING=invalid-empty GH_BIN="$WORK/bin/fake-gh" \
fi
echo "PASS: invalid result with no explainable findings fails closed"

# An advisory finding alongside the accepted reusable-workflow false positive
# must not block. Until 2026-09-22 every `sha-as-ref` became fatal the moment
# one real finding flipped `valid` to false, which held metadatastician/burble
# red on 7 advisories and skipped the secret-baseline controls behind it.
mixed_output="$(FAKE_VERIFY_FINDING=invalid-advisory-and-reusable GH_BIN="$WORK/bin/fake-gh" \
bash "$UPDATE" --verify-local .github/workflows)"
printf '%s\n' "$mixed_output" | grep -q 'Accepted reusable-workflow lock coverage'
printf '%s\n' "$mixed_output" | grep -q 'advisory finding(s) recorded, not blocking'
echo "PASS: advisory finding alongside an accepted reusable false positive does not block"

# The exemption must key on CATEGORY, not severity: v0.1.6 marks `stale`
# `severity: warning` too, so a severity-based exemption would pass vacuously.
if FAKE_VERIFY_FINDING=invalid-advisory-and-real-stale GH_BIN="$WORK/bin/fake-gh" \
bash "$UPDATE" --verify-local .github/workflows >/dev/null 2>&1; then
echo "FAIL: a real stale finding was hidden by the advisory exemption" >&2
exit 1
fi
echo "PASS: a real stale finding still blocks alongside advisory findings"

if FAKE_VERIFY_FINDING=invalid-advisory-only GH_BIN="$WORK/bin/fake-gh" \
bash "$UPDATE" --verify-local .github/workflows >/dev/null 2>&1; then
echo "FAIL: advisory findings alone were accepted as explaining valid:false" >&2
exit 1
fi
echo "PASS: advisory findings alone cannot explain an invalid result"

if FAKE_VERIFY_FINDING=malformed-success GH_BIN="$WORK/bin/fake-gh" \
bash "$UPDATE" --verify-local .github/workflows >/dev/null 2>&1; then
echo "FAIL: malformed successful verifier output was accepted" >&2
Expand Down
28 changes: 26 additions & 2 deletions scripts/update-actions-lock.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ workflow_references_reusable_dependency() {
' "$workflow"
}

is_advisory_category() {
# Advisory findings never affect the tool's own `valid` bit: a tree whose
# only findings are `sha-as-ref` reports `"valid": true` (measured
# 2026-09-22 on metadatastician/burble -- 7 sha-as-ref findings, valid true,
# process exit 1). They must therefore not block on the `valid:false` path
# either.
#
# Discriminate by CATEGORY, never by severity. gh-actions-lock v0.1.6 labels
# every finding `"severity": "warning"`, `stale` included, so a severity test
# would swallow real desyncs and turn this gate vacuous.
case "$1" in
sha-as-ref) return 0 ;;
*) return 1 ;;
esac
}

verify_lock_coverage() {
# gh-actions-lock v0.1.6 does not recognise reusable-workflow `uses:`
# paths. GitHub's startup enforcement nevertheless requires callers to
Expand Down Expand Up @@ -106,8 +122,11 @@ verify_lock_coverage() {

remaining=0
accepted=0
advisory=0
while IFS=$'\t' read -r category workflow dependency; do
if [[ "$category" = stale ]] &&
if is_advisory_category "$category"; then
advisory=$((advisory + 1))
elif [[ "$category" = stale ]] &&
workflow_references_reusable_dependency "$workflow" "$dependency"; then
echo "Accepted reusable-workflow lock coverage: $workflow -> $dependency"
accepted=$((accepted + 1))
Expand All @@ -116,9 +135,14 @@ verify_lock_coverage() {
fi
done < <(printf '%s' "$result" | jq -r '.findings[] | [.category, .workflow, .dependency] | @tsv')

if [[ "$advisory" -gt 0 ]]; then
echo "actions-lock: $advisory advisory finding(s) recorded, not blocking"
fi

# `valid:false` with no findings is contradictory and cannot be explained by
# the one known reusable-workflow false positive. Fail closed rather than
# turning an empty/malformed diagnostic into approval.
# turning an empty/malformed diagnostic into approval. Advisory findings do
# not explain it either, so they never satisfy this guard on their own.
if [[ "$remaining" -ne 0 || "$accepted" -eq 0 ]]; then
printf '%s\n' "$result"
return 1
Expand Down
Loading