Verify GhosttyKit artifact integrity in CI/nightly/release workflows (#1032)

* Verify GhosttyKit checksum in build workflows

* Pin GhosttyKit checksums in build workflows

* Tighten GhosttyKit checksum guards
This commit is contained in:
Lawrence Chen 2026-03-07 02:23:23 -08:00 committed by GitHub
parent 3432a4a941
commit 58bcc929b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 220 additions and 96 deletions

View file

@ -25,6 +25,9 @@ jobs:
- name: Validate cmux scheme test configuration
run: ./tests/test_ci_scheme_testaction_debug.sh
- name: Validate GhosttyKit checksum verification
run: ./tests/test_ci_ghosttykit_checksum_verification.sh
web-typecheck:
runs-on: ubuntu-latest
defaults:
@ -70,31 +73,8 @@ jobs:
xcrun --sdk macosx --show-sdk-path
- name: Download pre-built GhosttyKit.xcframework
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD)
TAG="xcframework-$GHOSTTY_SHA"
URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz"
echo "Downloading xcframework for ghostty $GHOSTTY_SHA"
MAX_RETRIES=30
RETRY_DELAY=20
for i in $(seq 1 $MAX_RETRIES); do
if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then
echo "Download succeeded on attempt $i"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2
exit 1
fi
echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
done
tar xzf GhosttyKit.xcframework.tar.gz
rm GhosttyKit.xcframework.tar.gz
test -d GhosttyKit.xcframework
./scripts/download-prebuilt-ghosttykit.sh
- name: Clean DerivedData
run: |
@ -203,31 +183,8 @@ jobs:
xcodebuild -version
- name: Download pre-built GhosttyKit.xcframework
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD)
TAG="xcframework-$GHOSTTY_SHA"
URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz"
echo "Downloading xcframework for ghostty $GHOSTTY_SHA"
MAX_RETRIES=30
RETRY_DELAY=20
for i in $(seq 1 $MAX_RETRIES); do
if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then
echo "Download succeeded on attempt $i"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2
exit 1
fi
echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
done
tar xzf GhosttyKit.xcframework.tar.gz
rm GhosttyKit.xcframework.tar.gz
test -d GhosttyKit.xcframework
./scripts/download-prebuilt-ghosttykit.sh
- name: Clean DerivedData
run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-*

View file

@ -116,31 +116,8 @@ jobs:
npm install --global "create-dmg@${CREATE_DMG_VERSION}"
- name: Download pre-built GhosttyKit.xcframework
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD)
TAG="xcframework-$GHOSTTY_SHA"
URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz"
echo "Downloading xcframework for ghostty $GHOSTTY_SHA"
MAX_RETRIES=30
RETRY_DELAY=20
for i in $(seq 1 $MAX_RETRIES); do
if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then
echo "Download succeeded on attempt $i"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2
exit 1
fi
echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
done
tar xzf GhosttyKit.xcframework.tar.gz
rm GhosttyKit.xcframework.tar.gz
test -d GhosttyKit.xcframework
./scripts/download-prebuilt-ghosttykit.sh
- name: Cache Swift packages
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4

View file

@ -103,31 +103,8 @@ jobs:
- name: Download pre-built GhosttyKit.xcframework
if: steps.guard_release_assets.outputs.skip_all != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD)
TAG="xcframework-$GHOSTTY_SHA"
URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz"
echo "Downloading xcframework for ghostty $GHOSTTY_SHA"
MAX_RETRIES=30
RETRY_DELAY=20
for i in $(seq 1 $MAX_RETRIES); do
if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then
echo "Download succeeded on attempt $i"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2
exit 1
fi
echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
done
tar xzf GhosttyKit.xcframework.tar.gz
rm GhosttyKit.xcframework.tar.gz
test -d GhosttyKit.xcframework
./scripts/download-prebuilt-ghosttykit.sh
- name: Cache Swift packages
if: steps.guard_release_assets.outputs.skip_all != 'true'

View file

@ -0,0 +1,71 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
if [ -n "${GHOSTTY_SHA:-}" ]; then
GHOSTTY_SHA="$GHOSTTY_SHA"
else
if [ ! -d "$REPO_ROOT/ghostty" ] || ! git -C "$REPO_ROOT/ghostty" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "Missing ghostty submodule. Run ./scripts/setup.sh or git submodule update --init --recursive first." >&2
exit 1
fi
GHOSTTY_SHA="$(git -C "$REPO_ROOT/ghostty" rev-parse HEAD)"
fi
TAG="xcframework-$GHOSTTY_SHA"
ARCHIVE_NAME="${GHOSTTYKIT_ARCHIVE_NAME:-GhosttyKit.xcframework.tar.gz}"
OUTPUT_DIR="${GHOSTTYKIT_OUTPUT_DIR:-GhosttyKit.xcframework}"
CHECKSUMS_FILE="${GHOSTTYKIT_CHECKSUMS_FILE:-$SCRIPT_DIR/ghosttykit-checksums.txt}"
DOWNLOAD_URL="${GHOSTTYKIT_URL:-https://github.com/manaflow-ai/ghostty/releases/download/$TAG/$ARCHIVE_NAME}"
DOWNLOAD_RETRIES="${GHOSTTYKIT_DOWNLOAD_RETRIES:-30}"
DOWNLOAD_RETRY_DELAY="${GHOSTTYKIT_DOWNLOAD_RETRY_DELAY:-20}"
if [ ! -f "$CHECKSUMS_FILE" ]; then
echo "Missing checksum file: $CHECKSUMS_FILE" >&2
exit 1
fi
EXPECTED_SHA256="$(
awk -v sha="$GHOSTTY_SHA" '
$1 == sha {
print $2
found = 1
exit
}
END {
if (!found) {
exit 1
}
}
' "$CHECKSUMS_FILE" || true
)"
if [ -z "$EXPECTED_SHA256" ]; then
echo "Missing pinned GhosttyKit checksum for ghostty $GHOSTTY_SHA in $CHECKSUMS_FILE" >&2
exit 1
fi
echo "Downloading $ARCHIVE_NAME for ghostty $GHOSTTY_SHA"
curl --fail --show-error --location \
--retry "$DOWNLOAD_RETRIES" \
--retry-delay "$DOWNLOAD_RETRY_DELAY" \
--retry-all-errors \
-o "$ARCHIVE_NAME" \
"$DOWNLOAD_URL"
ACTUAL_SHA256="$(shasum -a 256 "$ARCHIVE_NAME" | awk '{print $1}')"
if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then
echo "$ARCHIVE_NAME checksum mismatch" >&2
echo "Expected: $EXPECTED_SHA256" >&2
echo "Actual: $ACTUAL_SHA256" >&2
exit 1
fi
rm -rf "$OUTPUT_DIR"
tar xzf "$ARCHIVE_NAME"
rm "$ARCHIVE_NAME"
test -d "$OUTPUT_DIR"
echo "Verified and extracted $OUTPUT_DIR"

View file

@ -0,0 +1,4 @@
# Pinned GhosttyKit.xcframework.tar.gz checksums keyed by ghostty submodule SHA.
# Update this file in a reviewed PR whenever the ghostty submodule SHA changes.
# Format: <ghostty_sha> <sha256>
7dd589824d4c9bda8265355718800cccaf7189a0 3915af4256850a0a7bee671c3ba0a47cbfee5dbfc6d71caf952acefdf2ee4207

View file

@ -0,0 +1,138 @@
#!/usr/bin/env bash
# Regression test for the pinned GhosttyKit artifact verification helper.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="$ROOT_DIR/scripts/download-prebuilt-ghosttykit.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
WORKFLOWS=(
"$ROOT_DIR/.github/workflows/ci.yml"
"$ROOT_DIR/.github/workflows/nightly.yml"
"$ROOT_DIR/.github/workflows/release.yml"
)
FIXTURE_SHA="7dd589824d4c9bda8265355718800cccaf7189a0"
FIXTURE_DIR="$TMP_DIR/fixture"
SUCCESS_DIR="$TMP_DIR/success"
MISMATCH_DIR="$TMP_DIR/mismatch"
MISSING_ENTRY_DIR="$TMP_DIR/missing-entry"
BIN_DIR="$TMP_DIR/bin"
CHECKSUMS_FILE="$TMP_DIR/ghosttykit-checksums.txt"
SUCCESS_LOG="$TMP_DIR/curl-success.log"
MISMATCH_LOG="$TMP_DIR/curl-mismatch.log"
MISMATCH_OUTPUT="$TMP_DIR/mismatch.out"
MISSING_ENTRY_OUTPUT="$TMP_DIR/missing-entry.out"
mkdir -p "$FIXTURE_DIR/GhosttyKit.xcframework" "$SUCCESS_DIR" "$MISMATCH_DIR" "$MISSING_ENTRY_DIR" "$BIN_DIR"
printf 'fixture\n' > "$FIXTURE_DIR/GhosttyKit.xcframework/marker.txt"
(cd "$FIXTURE_DIR" && tar czf "$TMP_DIR/GhosttyKit.xcframework.tar.gz" GhosttyKit.xcframework)
ACTUAL_SHA256="$(shasum -a 256 "$TMP_DIR/GhosttyKit.xcframework.tar.gz" | awk '{print $1}')"
printf '%s %s\n' "$FIXTURE_SHA" "$ACTUAL_SHA256" > "$CHECKSUMS_FILE"
for workflow in "${WORKFLOWS[@]}"; do
if ! grep -Fq './scripts/download-prebuilt-ghosttykit.sh' "$workflow"; then
echo "FAIL: $workflow must call download-prebuilt-ghosttykit.sh"
exit 1
fi
done
cat > "$BIN_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
LOG_FILE="${TEST_CURL_LOG:?}"
FIXTURE_ARCHIVE="${TEST_FIXTURE_ARCHIVE:?}"
OUTPUT=""
while [ "$#" -gt 0 ]; do
case "$1" in
-o)
OUTPUT="$2"
shift 2
;;
*)
printf '%s\n' "$1" >> "$LOG_FILE"
shift
;;
esac
done
if [ -z "$OUTPUT" ]; then
echo "curl stub missing -o output path" >&2
exit 1
fi
cp "$FIXTURE_ARCHIVE" "$OUTPUT"
EOF
chmod +x "$BIN_DIR/curl"
(
cd "$SUCCESS_DIR"
PATH="$BIN_DIR:$PATH" \
TEST_CURL_LOG="$SUCCESS_LOG" \
TEST_FIXTURE_ARCHIVE="$TMP_DIR/GhosttyKit.xcframework.tar.gz" \
GHOSTTY_SHA="$FIXTURE_SHA" \
GHOSTTYKIT_CHECKSUMS_FILE="$CHECKSUMS_FILE" \
"$SCRIPT"
)
if [ ! -f "$SUCCESS_DIR/GhosttyKit.xcframework/marker.txt" ]; then
echo "FAIL: verification helper did not extract GhosttyKit.xcframework"
exit 1
fi
if [ -f "$SUCCESS_DIR/GhosttyKit.xcframework.tar.gz" ]; then
echo "FAIL: verification helper did not clean up the downloaded archive"
exit 1
fi
for expected_arg in --retry --retry-delay --retry-all-errors; do
if ! grep -Fxq -- "$expected_arg" "$SUCCESS_LOG"; then
echo "FAIL: curl invocation missing $expected_arg"
exit 1
fi
done
printf '%s %s\n' "$FIXTURE_SHA" "0000000000000000000000000000000000000000000000000000000000000000" > "$CHECKSUMS_FILE"
if (
cd "$MISMATCH_DIR"
PATH="$BIN_DIR:$PATH" \
TEST_CURL_LOG="$MISMATCH_LOG" \
TEST_FIXTURE_ARCHIVE="$TMP_DIR/GhosttyKit.xcframework.tar.gz" \
GHOSTTY_SHA="$FIXTURE_SHA" \
GHOSTTYKIT_CHECKSUMS_FILE="$CHECKSUMS_FILE" \
"$SCRIPT"
) >"$MISMATCH_OUTPUT" 2>&1; then
echo "FAIL: verification helper succeeded with an invalid pinned checksum"
exit 1
fi
if ! grep -Fq "GhosttyKit.xcframework.tar.gz checksum mismatch" "$MISMATCH_OUTPUT"; then
echo "FAIL: verification helper did not report checksum mismatch"
exit 1
fi
printf '%s %s\n' "0000000000000000000000000000000000000000" "$ACTUAL_SHA256" > "$CHECKSUMS_FILE"
if (
cd "$MISSING_ENTRY_DIR"
PATH="$BIN_DIR:$PATH" \
TEST_CURL_LOG="$MISMATCH_LOG" \
TEST_FIXTURE_ARCHIVE="$TMP_DIR/GhosttyKit.xcframework.tar.gz" \
GHOSTTY_SHA="$FIXTURE_SHA" \
GHOSTTYKIT_CHECKSUMS_FILE="$CHECKSUMS_FILE" \
"$SCRIPT"
) >"$MISSING_ENTRY_OUTPUT" 2>&1; then
echo "FAIL: verification helper succeeded without a pinned checksum entry"
exit 1
fi
if ! grep -Fq "Missing pinned GhosttyKit checksum for ghostty $FIXTURE_SHA" "$MISSING_ENTRY_OUTPUT"; then
echo "FAIL: verification helper did not report a missing pinned checksum entry"
exit 1
fi
echo "PASS: GhosttyKit verification helper enforces pinned checksums"