diff --git a/.github/workflows/mirror-reusable.yml b/.github/workflows/mirror-reusable.yml index 3314e28e..ec52acc3 100644 --- a/.github/workflows/mirror-reusable.yml +++ b/.github/workflows/mirror-reusable.yml @@ -91,6 +91,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 if: ${{ env.GITLAB_KEY != '' }} with: @@ -148,6 +149,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 if: ${{ env.BITBUCKET_KEY != '' }} with: @@ -199,6 +201,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 if: ${{ env.CODEBERG_KEY != '' }} with: @@ -250,6 +253,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 if: ${{ env.SOURCEHUT_KEY != '' }} with: @@ -301,6 +305,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 if: ${{ env.DISROOT_KEY != '' }} with: @@ -352,6 +357,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 if: ${{ env.GITEA_KEY != '' }} with: @@ -405,6 +411,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false # All Radicle steps gate on secrets.RADICLE_KEY being set on the # caller repo (passed explicitly under that declared name). Without this gate # the workflow burned ~3 minutes of Rust+Radicle install on every diff --git a/scripts/close-lock.sh b/scripts/close-lock.sh index a3b2babe..9ce855ca 100755 --- a/scripts/close-lock.sh +++ b/scripts/close-lock.sh @@ -185,7 +185,7 @@ for pass in 1 2 3 4 5 6; do tag_ref="" if "$GH_BIN" api "repos/$owner/$name/tags?per_page=100" --paginate \ --jq '.[] | select(.commit.sha == "'"$sha"'") | .name' > "$SC/tags" 2>/dev/null; then - tag_ref=$(awk '/^[A-Za-z0-9._+\/-]+$/ && !(length($0) == 40 && $0 ~ /^[0-9a-f]+$/) { if (!first) first = $0; if (!preferred && $0 ~ /^v?[0-9]+\.[0-9]+\.[0-9]+/) preferred = $0 } END { if (preferred) print preferred; else if (first) print first }' "$SC/tags") + tag_ref=$(awk '/^[A-Za-z0-9._+@\/-]+$/ && !(length($0) == 40 && $0 ~ /^[0-9a-f]+$/) { if (!first) first = $0; if (!preferred && $0 ~ /^v?[0-9]+\.[0-9]+\.[0-9]+/) preferred = $0 } END { if (preferred) print preferred; else if (first) print first }' "$SC/tags") fi if [ -z "$tag_ref" ]; then echo " SKIP $k (cannot resolve a symbolic tag for leaf SHA)" diff --git a/scripts/relock-standards-pin.sh b/scripts/relock-standards-pin.sh index 912e5a5e..8e17343e 100755 --- a/scripts/relock-standards-pin.sh +++ b/scripts/relock-standards-pin.sh @@ -131,18 +131,19 @@ git -C "$SROOT" show "${TARGET}:.github/workflows/actions.lock" > "$tmp/slock" 2 while read -r u; do [ -n "$u" ] || continue existing=$(rec_body "$u" "$tmp/lock") - if [ -n "$existing" ]; then + if [[ -n "$existing" ]]; then pin=${u##*@} if [[ "$pin" =~ ^[0-9a-f]{40}$ ]] && printf '%s\n' "$existing" | grep -qxF " ref: '$pin'"; then # A consumer can retain a poisoned copy after Standards repairs its own # record. Refresh only ref:, and only with identical commit and IDs. repaired=$(rec_body "$u" "$tmp/slock") symbolic=$(printf '%s\n' "$repaired" | sed -n "s/^ ref: '\([^']*\)'$/\1/p") - [ -n "$symbolic" ] && [[ ! "$symbolic" =~ ^[0-9a-fA-F]{40}$ ]] \ + [[ -n "$symbolic" && ! "$symbolic" =~ ^[0-9a-fA-F]{40}$ ]] \ || refuse "no symbolic repair for $u in target Standards lock" local_identity=$(printf '%s\n' "$existing" | grep -E '^ (commit|owner_id|repo_id):' || true) source_identity=$(printf '%s\n' "$repaired" | grep -E '^ (commit|owner_id|repo_id):' || true) - [ "$local_identity" = "$source_identity" ] && [ "$(printf '%s\n' "$source_identity" | wc -l)" -eq 3 ] \ + [[ "$local_identity" == "$source_identity" \ + && "$(printf '%s\n' "$source_identity" | wc -l)" -eq 3 ]] \ || refuse "identity mismatch while repairing $u" awk -v k=" '$u':" -v replacement=" ref: '$symbolic'" ' $0 == k { selected = 1; print; next } diff --git a/scripts/tests/close-lock-test.sh b/scripts/tests/close-lock-test.sh index 24c8217d..fe6d6500 100755 --- a/scripts/tests/close-lock-test.sh +++ b/scripts/tests/close-lock-test.sh @@ -46,6 +46,7 @@ case "\$2" in case " \$* " in *' --paginate '*) echo 'v2.3.4' ;; *) exit 0 ;; esac ;; repos/o/a/tags*|repos/o/b/tags*|repos/o/c/tags*) echo 'v1.0.0' ;; repos/o/buildmeta/tags*) echo 'v1.2.3+build.4' ;; + repos/o/releaseat/tags*) echo 'release@2026' ;; repos/o/leaf/tags*) printf 'v4\nv4.38.0\n' ;; repos/o/*/commits/v1) echo $C ;; repos/o/*/commits/*) echo '{"message":"No commit found"}'; exit 1 ;; @@ -90,9 +91,15 @@ grep -qxF " 'o/leaf@$A':" "$d/actions.lock" && grep -qxF " ref: 'v4.38 echo "=== accepts a version tag with build metadata ===" d=$(mklock buildmeta "o/buildmeta@$A" "$T/missing-local-log" 2>&1; then fail "missing local identity accepted"; fi +cmp -s "$D/actions.lock" "$T/missing-local-before" || fail "missing local identity refusal changed consumer lock" +grep -q 'identity mismatch' "$T/missing-local-log" || fail "missing local identity did not reach refusal" +echo " PASS missing local identity refused without changing lock" + +# Exercise the other grep assignment too: a target Standards record with no +# identity fields must reach the same explicit refusal, not exit on grep status. +cp "$S/.github/workflows/actions.lock" "$T/source-lock-with-identity" +sed -i "/commit: 'sha1-1111111111111111111111111111111111111111'/d; /owner_id: 10$/d; /repo_id: 11$/d" "$S/.github/workflows/actions.lock" +git -C "$S" add .github/workflows/actions.lock +git -C "$S" commit -q -m missing-source-identity +NEW_WITHOUT_ID=$(git -C "$S" rev-parse HEAD) +NEW_SAVE=$NEW; NEW=$NEW_WITHOUT_ID +D=$(consumer missing-source-identity) +sed -i "s/ref: 'v1'/ref: '1111111111111111111111111111111111111111'/" "$D/actions.lock" +cp "$D/actions.lock" "$T/missing-source-before" +if bash "$RELOCK" --to "$NEW" --standards "$S" "$D" >"$T/missing-source-log" 2>&1; then fail "missing source identity accepted"; fi +cmp -s "$D/actions.lock" "$T/missing-source-before" || fail "missing source identity refusal changed consumer lock" +grep -q 'identity mismatch' "$T/missing-source-log" || fail "missing source identity did not reach refusal" +NEW=$NEW_SAVE +cp "$T/source-lock-with-identity" "$S/.github/workflows/actions.lock" +echo " PASS missing source identity refused without changing lock" + # 2. Idempotent. D=$(consumer idem); bash "$RELOCK" --to "$NEW" --standards "$S" "$D" >/dev/null cp "$D/actions.lock" "$T/once"; bash "$RELOCK" --to "$NEW" --standards "$S" "$D" >/dev/null