From e4aa698f38d9b077c1a52cee6fcb54c46342a768 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 16 Apr 2026 20:20:23 -0700 Subject: [PATCH] update: verify and include new tagged releases from main index The versioned index.json (signed via minisig) is a snapshot tied to the current master nightly and does not include newly tagged releases like 0.16.0. Previously, the script only used this versioned index, so new stable releases were never picked up. The main index.json at ziglang.org/download includes both releases and master, but is not itself signed. However, every individual tarball has a .minisig signature. For new tagged releases that only appear in the main index, the script now downloads and verifies all tarballs for supported targets against Zig's public key. Only verified releases are merged into the output. Master data continues to come exclusively from the signed versioned index. If any signature check fails, the script falls back to the verified index only. --- update | 52 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/update b/update index f9b631b4..b6b570d9 100755 --- a/update +++ b/update @@ -7,7 +7,7 @@ curl -s 'https://ziglang.org/download/community-mirrors.txt' | jq -R '.' | jq -s # The well known public key for Zig PUBLIC_KEY="RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U" -# Grab the JSON and parse the version +# Grab the main index.json (contains all releases + master) rm -rf index.json index.json.minisig curl -s 'https://ziglang.org/download/index.json' > index.json VERSION=$(cat index.json | jq -r '.master.version') @@ -19,8 +19,52 @@ curl -s "https://ziglang.org/builds/zig-${VERSION}-index.json" > versioned-index curl -s "https://ziglang.org/builds/zig-${VERSION}-index.json.minisig" > index.json.minisig minisign -V -P ${PUBLIC_KEY} -x index.json.minisig -m versioned-index.json -# Build our new sources.json from the verified index -cat versioned-index.json | jq ' +# The main index.json is not signed, but every individual tarball has a +# .minisig signature. For release versions that only appear in the main +# index (not in the verified versioned index), verify that a valid +# signature exists for at least one tarball per version. +NEW_VERSIONS=$(jq -rs ' + . as [$main, $verified] | + [$main | keys[] | select(. != "master") | select(. as $k | ($verified | has($k)) | not)] | + .[] +' index.json versioned-index.json) + +FAILED="" +for v in $NEW_VERSIONS; do + # Get all tarball URLs for this version (only for our supported targets) + TARBALL_URLS=$(jq -r --arg v "$v" ' + ["aarch64-linux", "x86_64-linux", "aarch64-macos", "x86_64-macos", "aarch64-windows", "x86_64-windows"] as $targets | + .[$v] | to_entries[] | select(.key as $k | any($targets[]; . == $k)) | .value.tarball + ' index.json) + + for TARBALL_URL in $TARBALL_URLS; do + TARBALL_FILE=$(basename "$TARBALL_URL") + echo "Verifying signature for ${v}: ${TARBALL_FILE}..." + + curl -s "$TARBALL_URL" > "verify-${TARBALL_FILE}" + curl -s "${TARBALL_URL}.minisig" > "verify-${TARBALL_FILE}.minisig" + if ! minisign -V -P ${PUBLIC_KEY} -x "verify-${TARBALL_FILE}.minisig" -m "verify-${TARBALL_FILE}"; then + echo "ERROR: signature verification failed for ${v} (${TARBALL_FILE})" + FAILED="${FAILED} ${v}" + fi + rm -f "verify-${TARBALL_FILE}" "verify-${TARBALL_FILE}.minisig" + done +done + +if [ -n "$FAILED" ]; then + echo "ERROR: Signature verification failed for:${FAILED}" + echo "Falling back to verified index only." + cp versioned-index.json merged-index.json +else + # Start from the verified index and add only the newly verified releases + VERIFIED_VERSIONS=$(echo "$NEW_VERSIONS" | jq -R '.' | jq -s '.') + jq -s --argjson versions "$VERIFIED_VERSIONS" ' + .[1] * (.[0] | to_entries | map(select(.key as $k | $versions | index($k))) | from_entries) + ' index.json versioned-index.json > merged-index.json +fi + +# Build our new sources.json from the merged index +cat merged-index.json | jq ' ["aarch64-linux", "x86_64-linux", "aarch64-macos", "x86_64-macos", "aarch64-windows", "x86_64-windows"] as $targets | def todarwin(x): x | gsub("macos"; "darwin"); def toentry(vsn; x): @@ -61,4 +105,4 @@ cp sources.json sources.old.json jq -s '.[0] * .[1]' sources.old.json sources.new.json > sources.json # Clean up temp files -rm -f index.json versioned-index.json index.json.minisig sources.old.json sources.new.json +rm -f index.json versioned-index.json merged-index.json index.json.minisig sources.old.json sources.new.json