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:
parent
3432a4a941
commit
58bcc929b2
6 changed files with 220 additions and 96 deletions
53
.github/workflows/ci.yml
vendored
53
.github/workflows/ci.yml
vendored
|
|
@ -25,6 +25,9 @@ jobs:
|
||||||
- name: Validate cmux scheme test configuration
|
- name: Validate cmux scheme test configuration
|
||||||
run: ./tests/test_ci_scheme_testaction_debug.sh
|
run: ./tests/test_ci_scheme_testaction_debug.sh
|
||||||
|
|
||||||
|
- name: Validate GhosttyKit checksum verification
|
||||||
|
run: ./tests/test_ci_ghosttykit_checksum_verification.sh
|
||||||
|
|
||||||
web-typecheck:
|
web-typecheck:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
defaults:
|
defaults:
|
||||||
|
|
@ -70,31 +73,8 @@ jobs:
|
||||||
xcrun --sdk macosx --show-sdk-path
|
xcrun --sdk macosx --show-sdk-path
|
||||||
|
|
||||||
- name: Download pre-built GhosttyKit.xcframework
|
- name: Download pre-built GhosttyKit.xcframework
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
./scripts/download-prebuilt-ghosttykit.sh
|
||||||
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
|
|
||||||
|
|
||||||
- name: Clean DerivedData
|
- name: Clean DerivedData
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -203,31 +183,8 @@ jobs:
|
||||||
xcodebuild -version
|
xcodebuild -version
|
||||||
|
|
||||||
- name: Download pre-built GhosttyKit.xcframework
|
- name: Download pre-built GhosttyKit.xcframework
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
./scripts/download-prebuilt-ghosttykit.sh
|
||||||
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
|
|
||||||
|
|
||||||
- name: Clean DerivedData
|
- name: Clean DerivedData
|
||||||
run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-*
|
run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-*
|
||||||
|
|
|
||||||
25
.github/workflows/nightly.yml
vendored
25
.github/workflows/nightly.yml
vendored
|
|
@ -116,31 +116,8 @@ jobs:
|
||||||
npm install --global "create-dmg@${CREATE_DMG_VERSION}"
|
npm install --global "create-dmg@${CREATE_DMG_VERSION}"
|
||||||
|
|
||||||
- name: Download pre-built GhosttyKit.xcframework
|
- name: Download pre-built GhosttyKit.xcframework
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
./scripts/download-prebuilt-ghosttykit.sh
|
||||||
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
|
|
||||||
|
|
||||||
- name: Cache Swift packages
|
- name: Cache Swift packages
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
||||||
|
|
|
||||||
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
|
|
@ -103,31 +103,8 @@ jobs:
|
||||||
|
|
||||||
- name: Download pre-built GhosttyKit.xcframework
|
- name: Download pre-built GhosttyKit.xcframework
|
||||||
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
./scripts/download-prebuilt-ghosttykit.sh
|
||||||
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
|
|
||||||
|
|
||||||
- name: Cache Swift packages
|
- name: Cache Swift packages
|
||||||
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
||||||
|
|
|
||||||
71
scripts/download-prebuilt-ghosttykit.sh
Executable file
71
scripts/download-prebuilt-ghosttykit.sh
Executable 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"
|
||||||
4
scripts/ghosttykit-checksums.txt
Normal file
4
scripts/ghosttykit-checksums.txt
Normal 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
|
||||||
138
tests/test_ci_ghosttykit_checksum_verification.sh
Executable file
138
tests/test_ci_ghosttykit_checksum_verification.sh
Executable 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"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue