From 5e2a30e1995a92ffe12d0e7a5cd59db8af6b40f5 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Mon, 25 May 2026 18:34:52 -0700 Subject: [PATCH 1/5] Add universal macOS release artifact guard --- .github/workflows/ci.yml | 9 ++ .github/workflows/nightly.yml | 16 +--- .github/workflows/release.yml | 16 +--- scripts/build-sign-upload.sh | 7 +- scripts/verify-universal-macos-app.sh | 106 +++++++++++++++++++++++ tests/test_verify_universal_macos_app.sh | 76 ++++++++++++++++ 6 files changed, 203 insertions(+), 27 deletions(-) create mode 100755 scripts/verify-universal-macos-app.sh create mode 100755 tests/test_verify_universal_macos_app.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4720fb93e2ef..3f0c4e0eb5fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,9 @@ jobs: - name: Validate release asset guard run: node scripts/release_asset_guard.test.js + - name: Validate universal macOS app verifier + run: ./tests/test_verify_universal_macos_app.sh + - name: Validate current GhosttyKit checksum pin run: ./tests/test_ci_ghosttykit_checksum_present.sh @@ -653,6 +656,12 @@ jobs: ONLY_ACTIVE_ARCH=NO \ CODE_SIGNING_ALLOWED=NO ASSETCATALOG_COMPILER_APPICON_NAME=AppIcon-Nightly build + - name: Verify universal app binary architectures + run: | + ./scripts/verify-universal-macos-app.sh \ + "build-universal/Build/Products/Release/cmux.app" \ + --label "CI Release app" + ui-regressions: runs-on: warp-macos-15-arm64-6x timeout-minutes: 25 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 700825a43565..f4e03a278c8a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -209,19 +209,9 @@ jobs: - name: Verify nightly binary architectures if: needs.decide.outputs.should_publish != 'true' || steps.current_head_prebuild.outputs.still_current == 'true' run: | - set -euo pipefail - APP_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/MacOS/cmux" - CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux" - HELPER_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/ghostty" - APP_ARCHS="$(lipo -archs "$APP_BINARY")" - CLI_ARCHS="$(lipo -archs "$CLI_BINARY")" - HELPER_ARCHS="$(lipo -archs "$HELPER_BINARY")" - echo "App binary architectures: $APP_ARCHS" - echo "CLI binary architectures: $CLI_ARCHS" - echo "Ghostty helper architectures: $HELPER_ARCHS" - [[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]] - [[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]] - [[ "$HELPER_ARCHS" == *arm64* && "$HELPER_ARCHS" == *x86_64* ]] + ./scripts/verify-universal-macos-app.sh \ + "build-universal/Build/Products/Release/cmux.app" \ + --label "Nightly app" - name: Run CLI version memory guard regression if: needs.decide.outputs.should_publish != 'true' || steps.current_head_prebuild.outputs.still_current == 'true' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c842a3496972..9f69bd4b6e55 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -173,19 +173,9 @@ jobs: - name: Verify binary architectures if: steps.guard_release_assets.outputs.skip_all != 'true' run: | - set -euo pipefail - APP_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/MacOS/cmux" - CLI_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux" - HELPER_BINARY="build-universal/Build/Products/Release/cmux.app/Contents/Resources/bin/ghostty" - APP_ARCHS="$(lipo -archs "$APP_BINARY")" - CLI_ARCHS="$(lipo -archs "$CLI_BINARY")" - HELPER_ARCHS="$(lipo -archs "$HELPER_BINARY")" - echo "App binary architectures: $APP_ARCHS" - echo "CLI binary architectures: $CLI_ARCHS" - echo "Ghostty helper architectures: $HELPER_ARCHS" - [[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]] - [[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]] - [[ "$HELPER_ARCHS" == *arm64* && "$HELPER_ARCHS" == *x86_64* ]] + ./scripts/verify-universal-macos-app.sh \ + "build-universal/Build/Products/Release/cmux.app" \ + --label "Release app" - name: Build remote daemon release assets and inject manifest if: steps.guard_release_assets.outputs.skip_all != 'true' diff --git a/scripts/build-sign-upload.sh b/scripts/build-sign-upload.sh index 0c4ae66fa7a3..549c1b937bc7 100755 --- a/scripts/build-sign-upload.sh +++ b/scripts/build-sign-upload.sh @@ -71,7 +71,11 @@ cp -R ghostty/macos/GhosttyKit.xcframework GhosttyKit.xcframework # --- Build app (Release, unsigned) --- echo "Building app..." rm -rf build/ -xcodebuild -scheme cmux -configuration Release -derivedDataPath build CODE_SIGNING_ALLOWED=NO build 2>&1 | tail -5 +xcodebuild -project cmux.xcodeproj -scheme cmux -configuration Release -derivedDataPath build \ + -destination 'generic/platform=macOS' \ + ARCHS="arm64 x86_64" \ + ONLY_ACTIVE_ARCH=NO \ + CODE_SIGNING_ALLOWED=NO build 2>&1 | tail -5 echo "Build succeeded" HELPER_PATH="$APP_PATH/Contents/Resources/bin/ghostty" @@ -79,6 +83,7 @@ if [ ! -x "$HELPER_PATH" ]; then echo "Ghostty theme picker helper not found at $HELPER_PATH" >&2 exit 1 fi +./scripts/verify-universal-macos-app.sh "$APP_PATH" --label "Release app" # --- Inject Sparkle keys --- echo "Injecting Sparkle keys..." diff --git a/scripts/verify-universal-macos-app.sh b/scripts/verify-universal-macos-app.sh new file mode 100755 index 000000000000..2902e8aa01b4 --- /dev/null +++ b/scripts/verify-universal-macos-app.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: ./scripts/verify-universal-macos-app.sh [--label ] + +Verifies that the app executable, bundled cmux CLI, and bundled Ghostty helper +all contain both arm64 and x86_64 Mach-O slices. +EOF +} + +APP_PATH="" +LABEL="macOS app" +while [[ $# -gt 0 ]]; do + case "$1" in + --label) + LABEL="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + -*) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + *) + if [[ -n "$APP_PATH" ]]; then + echo "Unexpected argument: $1" >&2 + usage >&2 + exit 1 + fi + APP_PATH="$1" + shift + ;; + esac +done + +if [[ -z "$APP_PATH" ]]; then + usage >&2 + exit 1 +fi + +if [[ -z "$LABEL" ]]; then + echo "Missing value for --label" >&2 + exit 1 +fi + +if [[ ! -d "$APP_PATH" ]]; then + echo "error: app bundle not found at $APP_PATH" >&2 + exit 1 +fi + +LIPO_BIN="${CMUX_LIPO:-lipo}" +if ! command -v "$LIPO_BIN" >/dev/null 2>&1; then + echo "error: lipo is required to verify universal macOS binaries" >&2 + exit 1 +fi + +INFO_PLIST="$APP_PATH/Contents/Info.plist" +EXECUTABLE_NAME="" +if [[ -f "$INFO_PLIST" && -x /usr/libexec/PlistBuddy ]]; then + EXECUTABLE_NAME="$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$INFO_PLIST" 2>/dev/null || true)" +fi +if [[ -z "$EXECUTABLE_NAME" ]]; then + EXECUTABLE_NAME="$(basename "$APP_PATH" .app)" +fi + +APP_BINARY="$APP_PATH/Contents/MacOS/$EXECUTABLE_NAME" +CLI_BINARY="$APP_PATH/Contents/Resources/bin/cmux" +HELPER_BINARY="$APP_PATH/Contents/Resources/bin/ghostty" + +verify_binary_archs() { + local name="$1" + local path="$2" + local archs + + if [[ ! -x "$path" ]]; then + echo "error: $name is missing or not executable at $path" >&2 + exit 1 + fi + + if ! archs="$("$LIPO_BIN" -archs "$path")"; then + echo "error: failed to inspect $name architectures at $path" >&2 + exit 1 + fi + + echo "$LABEL $name architectures: $archs" + for expected_arch in arm64 x86_64; do + case " $archs " in + *" $expected_arch "*) + ;; + *) + echo "error: $name at $path is missing $expected_arch slice" >&2 + exit 1 + ;; + esac + done +} + +verify_binary_archs "app binary" "$APP_BINARY" +verify_binary_archs "CLI binary" "$CLI_BINARY" +verify_binary_archs "Ghostty helper" "$HELPER_BINARY" diff --git a/tests/test_verify_universal_macos_app.sh b/tests/test_verify_universal_macos_app.sh new file mode 100755 index 000000000000..5e6e3e6d0f5c --- /dev/null +++ b/tests/test_verify_universal_macos_app.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cmux-universal-verify.XXXXXX")" +trap 'rm -rf "$TMP_DIR"' EXIT + +APP_PATH="$TMP_DIR/cmux.app" +FAKE_LIPO="$TMP_DIR/lipo" + +mkdir -p "$APP_PATH/Contents/MacOS" "$APP_PATH/Contents/Resources/bin" +cat > "$APP_PATH/Contents/Info.plist" <<'EOF' + + + + + CFBundleExecutable + cmux + + +EOF + +touch "$APP_PATH/Contents/MacOS/cmux" +touch "$APP_PATH/Contents/Resources/bin/cmux" +touch "$APP_PATH/Contents/Resources/bin/ghostty" +chmod 755 \ + "$APP_PATH/Contents/MacOS/cmux" \ + "$APP_PATH/Contents/Resources/bin/cmux" \ + "$APP_PATH/Contents/Resources/bin/ghostty" + +cat > "$FAKE_LIPO" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +if [[ "${1:-}" != "-archs" || $# -ne 2 ]]; then + echo "unexpected lipo invocation" >&2 + exit 2 +fi +cat "$2.archs" +EOF +chmod +x "$FAKE_LIPO" + +set_archs() { + printf '%s\n' "$2" > "$1.archs" +} + +VERIFY_SCRIPT="$ROOT_DIR/scripts/verify-universal-macos-app.sh" +export CMUX_LIPO="$FAKE_LIPO" + +set_archs "$APP_PATH/Contents/MacOS/cmux" "x86_64 arm64" +set_archs "$APP_PATH/Contents/Resources/bin/cmux" "arm64 x86_64" +set_archs "$APP_PATH/Contents/Resources/bin/ghostty" "arm64 x86_64" +"$VERIFY_SCRIPT" "$APP_PATH" --label "fixture app" >/dev/null + +set_archs "$APP_PATH/Contents/Resources/bin/ghostty" "arm64" +if "$VERIFY_SCRIPT" "$APP_PATH" --label "fixture app" >"$TMP_DIR/missing-slice.out" 2>"$TMP_DIR/missing-slice.err"; then + echo "FAIL: verifier accepted a helper missing the x86_64 slice" >&2 + exit 1 +fi +if ! grep -Fq "missing x86_64 slice" "$TMP_DIR/missing-slice.err"; then + echo "FAIL: verifier did not explain the missing x86_64 slice" >&2 + cat "$TMP_DIR/missing-slice.err" >&2 + exit 1 +fi + +rm "$APP_PATH/Contents/Resources/bin/cmux" +if "$VERIFY_SCRIPT" "$APP_PATH" --label "fixture app" >"$TMP_DIR/missing-cli.out" 2>"$TMP_DIR/missing-cli.err"; then + echo "FAIL: verifier accepted an app bundle missing the embedded CLI" >&2 + exit 1 +fi +if ! grep -Fq "CLI binary is missing or not executable" "$TMP_DIR/missing-cli.err"; then + echo "FAIL: verifier did not explain the missing CLI" >&2 + cat "$TMP_DIR/missing-cli.err" >&2 + exit 1 +fi + +echo "PASS: universal macOS app verifier enforces app, CLI, and helper slices" From a8d9c9555cf37c1bc89b6e7868644ef7ce16f29c Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Mon, 25 May 2026 18:41:52 -0700 Subject: [PATCH 2/5] Tighten universal verifier coverage --- scripts/verify-universal-macos-app.sh | 6 +++++- tests/test_verify_universal_macos_app.sh | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/scripts/verify-universal-macos-app.sh b/scripts/verify-universal-macos-app.sh index 2902e8aa01b4..c964ae00f98f 100755 --- a/scripts/verify-universal-macos-app.sh +++ b/scripts/verify-universal-macos-app.sh @@ -15,7 +15,11 @@ LABEL="macOS app" while [[ $# -gt 0 ]]; do case "$1" in --label) - LABEL="${2:-}" + if [[ $# -lt 2 || -z "${2:-}" ]]; then + echo "Missing value for --label" >&2 + exit 1 + fi + LABEL="$2" shift 2 ;; -h|--help) diff --git a/tests/test_verify_universal_macos_app.sh b/tests/test_verify_universal_macos_app.sh index 5e6e3e6d0f5c..42a32c758f2e 100755 --- a/tests/test_verify_universal_macos_app.sh +++ b/tests/test_verify_universal_macos_app.sh @@ -51,6 +51,27 @@ set_archs "$APP_PATH/Contents/Resources/bin/cmux" "arm64 x86_64" set_archs "$APP_PATH/Contents/Resources/bin/ghostty" "arm64 x86_64" "$VERIFY_SCRIPT" "$APP_PATH" --label "fixture app" >/dev/null +if "$VERIFY_SCRIPT" "$APP_PATH" --label >"$TMP_DIR/missing-label.out" 2>"$TMP_DIR/missing-label.err"; then + echo "FAIL: verifier accepted --label without a value" >&2 + exit 1 +fi +if ! grep -Fq "Missing value for --label" "$TMP_DIR/missing-label.err"; then + echo "FAIL: verifier did not explain the missing label value" >&2 + cat "$TMP_DIR/missing-label.err" >&2 + exit 1 +fi + +set_archs "$APP_PATH/Contents/Resources/bin/ghostty" "x86_64" +if "$VERIFY_SCRIPT" "$APP_PATH" --label "fixture app" >"$TMP_DIR/missing-arm.out" 2>"$TMP_DIR/missing-arm.err"; then + echo "FAIL: verifier accepted a helper missing the arm64 slice" >&2 + exit 1 +fi +if ! grep -Fq "missing arm64 slice" "$TMP_DIR/missing-arm.err"; then + echo "FAIL: verifier did not explain the missing arm64 slice" >&2 + cat "$TMP_DIR/missing-arm.err" >&2 + exit 1 +fi + set_archs "$APP_PATH/Contents/Resources/bin/ghostty" "arm64" if "$VERIFY_SCRIPT" "$APP_PATH" --label "fixture app" >"$TMP_DIR/missing-slice.out" 2>"$TMP_DIR/missing-slice.err"; then echo "FAIL: verifier accepted a helper missing the x86_64 slice" >&2 From acf2897a5d9238feda6e483ee0740cdb793e9c78 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sat, 6 Jun 2026 01:52:08 -0700 Subject: [PATCH 3/5] fix: align workflow guard with universal verifier --- tests/test_ci_self_hosted_guard.sh | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/tests/test_ci_self_hosted_guard.sh b/tests/test_ci_self_hosted_guard.sh index 7685a488617e..b0456a083c72 100755 --- a/tests/test_ci_self_hosted_guard.sh +++ b/tests/test_ci_self_hosted_guard.sh @@ -13,6 +13,16 @@ CI_FILE="$ROOT_DIR/.github/workflows/ci.yml" GHOSTTYKIT_FILE="$ROOT_DIR/.github/workflows/build-ghosttykit.yml" COMPAT_FILE="$ROOT_DIR/.github/workflows/ci-macos-compat.yml" E2E_FILE="$ROOT_DIR/.github/workflows/test-e2e.yml" +UNIVERSAL_VERIFIER="$ROOT_DIR/scripts/verify-universal-macos-app.sh" + +job_section() { + local file="$1" job="$2" + awk -v job="$job" ' + $0 ~ "^ "job":" { in_job=1; next } + in_job && /^ [^[:space:]#][^:]*:[[:space:]]*(#.*)?$/ { exit } + in_job { print } + ' "$file" +} check_macos_runner() { local file="$1" job="$2" @@ -105,18 +115,26 @@ check_xcode_selection() { } check_release_build_signal() { - if ! grep -Fq 'lipo "$APP_BINARY" -verify_arch arm64 x86_64' "$CI_FILE"; then - echo "FAIL: release-build must verify the Release app binary stays universal" + local section + section="$(job_section "$CI_FILE" "release-build")" + + if [[ "$section" != *"./scripts/verify-universal-macos-app.sh"* ]]; then + echo "FAIL: release-build must verify the Release artifact through the universal app verifier" + exit 1 + fi + + if ! grep -Fq 'verify_binary_archs "app binary" "$APP_BINARY"' "$UNIVERSAL_VERIFIER"; then + echo "FAIL: universal app verifier must check the Release app binary" exit 1 fi - if ! grep -Fq 'lipo "$CLI_BINARY" -verify_arch arm64 x86_64' "$CI_FILE"; then - echo "FAIL: release-build must verify the bundled CLI stays universal" + if ! grep -Fq 'verify_binary_archs "CLI binary" "$CLI_BINARY"' "$UNIVERSAL_VERIFIER"; then + echo "FAIL: universal app verifier must check the bundled CLI" exit 1 fi - if ! grep -Fq 'lipo "$HELPER_BINARY" -verify_arch arm64 x86_64' "$CI_FILE"; then - echo "FAIL: release-build must verify the bundled Ghostty helper stays universal" + if ! grep -Fq 'verify_binary_archs "Ghostty helper" "$HELPER_BINARY"' "$UNIVERSAL_VERIFIER"; then + echo "FAIL: universal app verifier must check the bundled Ghostty helper" exit 1 fi From de49750cd28bd36935cc214a6f4d5336e5c4e687 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sat, 6 Jun 2026 02:00:54 -0700 Subject: [PATCH 4/5] fix: narrow CI workflow permissions --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f267522e1602..1eb6a7236718 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,6 @@ on: permissions: contents: read - actions: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 9822afe475cac02d681aaca76b6c6ade7c7e855b Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sat, 6 Jun 2026 02:08:00 -0700 Subject: [PATCH 5/5] fix: address release verifier review feedback --- .github/workflows/ci.yml | 6 ++++++ scripts/build-sign-upload.sh | 5 ++++- tests/test_ci_release_sdk_lane.sh | 8 +++++++- tests/test_ci_self_hosted_guard.sh | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eb6a7236718..8014f10eea7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -691,6 +691,9 @@ jobs: release-ghostty-cli-helper: runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }} timeout-minutes: 20 + permissions: + contents: read + actions: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -730,6 +733,9 @@ jobs: # compiles into the same artifact shape as nightly and stable releases. runs-on: ${{ vars.MACOS_RUNNER_26 || 'warp-macos-26-arm64-6x' }} timeout-minutes: 20 + permissions: + contents: read + actions: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/scripts/build-sign-upload.sh b/scripts/build-sign-upload.sh index 18fc99a4aa90..443ccd3311fa 100755 --- a/scripts/build-sign-upload.sh +++ b/scripts/build-sign-upload.sh @@ -83,7 +83,10 @@ if [ ! -x "$HELPER_PATH" ]; then echo "Ghostty theme picker helper not found at $HELPER_PATH" >&2 exit 1 fi -./scripts/verify-universal-macos-app.sh "$APP_PATH" --label "Release app" +./scripts/verify-universal-macos-app.sh \ + "$APP_PATH" \ + --label "Release app" \ + --require-sdk-prefix "26." # --- Inject Sparkle keys --- echo "Injecting Sparkle keys..." diff --git a/tests/test_ci_release_sdk_lane.sh b/tests/test_ci_release_sdk_lane.sh index 5a575d53cc73..0bbd2e98e037 100755 --- a/tests/test_ci_release_sdk_lane.sh +++ b/tests/test_ci_release_sdk_lane.sh @@ -4,6 +4,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" CI_FILE="$ROOT_DIR/.github/workflows/ci.yml" RELEASE_FILE="$ROOT_DIR/.github/workflows/release.yml" +BUILD_SIGN_UPLOAD_FILE="$ROOT_DIR/scripts/build-sign-upload.sh" # nightly.yml builds the macOS 26 SDK app with its own inline helper-build model # (PR #5077). This lane guards the release/CI artifact-download model added by @@ -74,4 +75,9 @@ for workflow in "$CI_FILE" "$RELEASE_FILE"; do fi done -echo "PASS: release and CI app builds use macOS 26 SDK with a macOS 15-built Ghostty CLI helper" +if ! grep -Fq -- '--require-sdk-prefix "26."' "$BUILD_SIGN_UPLOAD_FILE"; then + echo "FAIL: build-sign-upload.sh must verify the app binary was built with a macOS 26 SDK through the universal app verifier" >&2 + exit 1 +fi + +echo "PASS: release, CI, and manual app builds use macOS 26 SDK with a macOS 15-built Ghostty CLI helper" diff --git a/tests/test_ci_self_hosted_guard.sh b/tests/test_ci_self_hosted_guard.sh index b0456a083c72..637989d3ecad 100755 --- a/tests/test_ci_self_hosted_guard.sh +++ b/tests/test_ci_self_hosted_guard.sh @@ -118,7 +118,7 @@ check_release_build_signal() { local section section="$(job_section "$CI_FILE" "release-build")" - if [[ "$section" != *"./scripts/verify-universal-macos-app.sh"* ]]; then + if ! grep -Eq '^[[:space:]]*\./scripts/verify-universal-macos-app\.sh([[:space:]\\]|$)' <<< "$section"; then echo "FAIL: release-build must verify the Release artifact through the universal app verifier" exit 1 fi