build: use rsync to sync firmware files for releasing
What changed, and why it matters
This commit changes an internal release script used by Blockstream Jade developers. It switches from downloading firmware files over the public web (using wget) to copying them from a private Google Cloud bucket (using gcloud rsync). It also tightens a consistency check so release scripts now verify that every firmware file has a matching hash file, except for very old versions. There is no direct evidence this fixes a security vulnerability; it appears to be a build/release tooling improvement.
No immediate security action required. Treat as a normal build/release hygiene improvement. Reviewers may want to confirm the gcloud bucket permissions and the rsync behavior do not accidentally expose or overwrite release artifacts, and that the new hash check is enforced in CI before publishing.
Security signals we found
Strengthened release-integrity check: now enforces presence of .hash files for firmware binaries
Changed release artifact source from public HTTPS endpoint to authenticated Google Cloud Storage bucket
Removed unconditional rm -rf of local mirror directory before sync
Evidence from the diff
The patch rewrites release/scripts/checkfwsvr.sh. The old implementation mirrored https://jadefw.blockstream.com/bin via wget, created a fresh local mirror, and checked index files. The new implementation requires the operator to run from a ‘release’ or ‘staging’ directory, authenticates to gcloud, runs ‘gcloud storage rsync’ from gs://jadefw.blockstream.com/bin, and then validates that every firmware file referenced in index.json/LATEST/BETA/PREVIOUS exists locally and that every _fw.bin (except 0.1.) has a corresponding .hash file. The change is operational: it reduces sync time and improves hash-presence validation. No runtime firmware code, cryptography, or user-facing behavior is modified.
Changed components
release/scripts/checkfwsvr.shInspect captured patch +57 / −58
diff --git a/release/scripts/checkfwsvr.sh b/release/scripts/checkfwsvr.sh
index 763ac39..b7d2a20 100755
--- a/release/scripts/checkfwsvr.sh
+++ b/release/scripts/checkfwsvr.sh
@@ -1,74 +1,73 @@
#!/bin/bash
LOCAL_DIR="${1:-fwsvr_mirror}"
-MISSING="missing.log"
-
-FWSERVER="https://jadefw.blockstream.com/bin"
-
+MISSING_LOG="missing.log"
+GCLOUD_BUCKET="gs://jadefw.blockstream.com/bin"
HWDIRS="jade jade1.1 jade2.0 jadedev jade1.1dev jade2.0dev"
-INDEXES="LATEST BETA PREVIOUS"
-INDEX_JSON="index.json"
-
-# jq filter: <roots>/<release types>/<fw types>/<filename>
-INDEX_JSON_FILENAME_FILTER=".[] | .[] | .[] | .filename"
-if [ -d "${LOCAL_DIR}" ]
-then
- rm -rf "${LOCAL_DIR}"
+if [ $(basename $PWD) != "release" ]; then
+ if [ $(basename $PWD) != "staging" ]; then
+ echo "ERROR: This script must be run from the 'release' or 'staging' source directory"
+ exit 1
+ fi
fi
-mkdir "${LOCAL_DIR}"
-pushd "${LOCAL_DIR}"
-
-echo "Pulling fw server files into $(pwd)"
-for hwdir in ${HWDIRS}
-do
- mkdir "${hwdir}"
- pushd "${hwdir}"
- FWLOCATION="${FWSERVER}/${hwdir}"
+echo "Checking authentication:"
+if ! echo "" | gcloud projects list &> /dev/null; then
+ echo "ERROR: You must run 'gcloud login' to authenticate to gcloud"
+ exit 1
+fi
- for index in "${INDEX_JSON}" ${INDEXES}
- do
- wget "${FWLOCATION}"/"${index}"
- if [ $? -ne 0 ]
- then
- echo "Missing: ${hwdir} - ${index}" >> ../"${MISSING}"
- else
- if [ "${index}" == "${INDEX_JSON}" ]
- then
- FWFILES=$(jq -r "${INDEX_JSON_FILENAME_FILTER}" "${INDEX_JSON}")
- else
- FWFILES=$(cat ${index})
- fi
+echo "Syncing firmware server files into ${LOCAL_DIR}"
+# This always give an error, either:
+# ERROR: [Errno 20] Not a directory: 'fwsvr_mirror/_.gstmp' -> 'fwsvr_mirror/'
+# or:
+# ERROR: [Errno 21] Is a directory: 'fwsvr_mirror/'
+# but it doesn't affect the actual downloaded files
+gcloud storage rsync ${GCLOUD_BUCKET} ${LOCAL_DIR} --recursive --delete-unmatched-destination-objects
+# We always get this file too even though it doesn't appear to exist in the bucket.
+rm -f ${LOCAL_DIR}/_.gstmp
- for fwfile in ${FWFILES}
- do
- if [ ! -f "${fwfile}" ]
- then
- if [ "${index}" != "${INDEX_JSON}" ]
- then
- echo "Missing from ${INDEX_JSON}: ${hwdir} - ${index} - ${fwfile}" >> ../"${MISSING}"
- fi
+echo "Checking firmware server files and indices"
+rm -f "${MISSING_LOG}"
- mkdir -p $(dirname "${fwfile}")
- wget -O "${fwfile}" "${FWLOCATION}/${fwfile}"
- if [ $? -ne 0 ]
- then
- echo "Missing: ${hwdir} - ${index} - ${fwfile}" >> ../"${MISSING}"
- fi
- wget "${FWLOCATION}/${fwfile}.hash"
+for hwdir in ${HWDIRS}; do
+ for index_name in index.json LATEST BETA PREVIOUS; do
+ index_file="${LOCAL_DIR}/${hwdir}/${index_name}"
+ if [ ! -f "${index_file}" ]; then
+ echo "Missing Index: ${index_file}" >> "${MISSING_LOG}"
+ fw_file_names=""
+ else
+ if [ "${index_name}" == index.json ]; then
+ # jq filter: <roots>/<release types>/<fw types>/<filename>
+ fw_file_names=$(jq -r ".[] | .[] | .[] | .filename" "${index_file}")
+ else
+ fw_file_names=$(cat "${index_file}")
+ fi
fi
- done
- fi
- done
- popd
+
+ for fw_file_name in $(echo "${fw_file_names}" | tr '\n' ' '); do
+ fw_file="${LOCAL_DIR}/${hwdir}/${fw_file_name}"
+ if [ ! -f "${fw_file}" ]; then
+ echo "Missing FW ${fw_file} named in ${index_file}" >> "${MISSING_LOG}"
+ fi
+ case $fw_file_name in
+ 0\.1\.*) ;; # Ignore early FW versions without hashes
+ *_fw.bin)
+ if [ ! -f "${fw_file}.hash" ]; then
+ echo "Missing hash for ${fw_file}" >> "${MISSING_LOG}"
+ fi
+ ;;
+ esac
+ done
+ done
done
-popd
-if [ -f "${LOCAL_DIR}"/"${MISSING}" ]
-then
- cat "${LOCAL_DIR}"/"${MISSING}"
+# TODO: find files that are present but not listed in the indices
+
+if [ -f "${MISSING_LOG}" ]; then
+ cat "${MISSING_LOG}"
exit 1
fi
echo "ALL GOOD!"
-
+exit 0
Why this scored 13/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.