From 5c9b0b5a26259013e9fb059e209ea280bac8a8da Mon Sep 17 00:00:00 2001 From: Jakub Marek Date: Thu, 13 Aug 2026 11:17:05 +0200 Subject: [PATCH] fix: unset empty App Store Connect API key issuer ID Making the issuer ID optional in 2.4.4 only relaxed the input and secret declarations. Both composite actions still set the env var unconditionally, so an omitted secret reaches fastlane as "" rather than being absent. Ruby treats "" as truthy, so spaceship takes the team-key branch in ConnectAPI::Token#refresh! and signs a JWT with iss: "" instead of the individual-key sub: "user". Apple rejects it with "Authentication credentials are missing or invalid", failing the release on the first App Store Connect call. Unset the variable when it is empty or whitespace-only, so fastlane sees it as absent and signs an individual-key token. Whitespace is stripped first because fastlane's own .strip would collapse a padded value back to "" and hit the same branch. Fixing beta.sh and release.sh covers every affected workflow: ios-selfhosted-release and ios-kmp-selfhosted-release use ios-fastlane-release; ios-selfhosted-nightly-build and ios-selfhosted-on-demand-build use ios-fastlane-beta; ios-kmp-selfhosted-build and kmp-combined-nightly-build reach ios-fastlane-beta through ios-kmp-build; ios-selfhosted-build is a deprecated shim over ios-selfhosted-nightly-build. Co-Authored-By: Claude Opus 5 (1M context) --- .github/actions/ios-fastlane-beta/beta.sh | 6 +++++ .../ios-fastlane-beta/test/test_beta.bats | 18 +++++++++++++ .../ios-fastlane-beta/test/test_helper.bash | 8 +++--- .../actions/ios-fastlane-release/release.sh | 6 +++++ .../test/test_release.bats | 26 ++++++++++++++++--- 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/.github/actions/ios-fastlane-beta/beta.sh b/.github/actions/ios-fastlane-beta/beta.sh index 88e439c6..1d5a359a 100755 --- a/.github/actions/ios-fastlane-beta/beta.sh +++ b/.github/actions/ios-fastlane-beta/beta.sh @@ -1,6 +1,12 @@ #!/bin/bash set -e +# Individual keys have no issuer ID, but an omitted secret arrives as "", which +# is truthy in Ruby — fastlane would sign iss: "" instead of sub: "user". +if [ -z "${APP_STORE_CONNECT_API_KEY_ISSUER_ID//[[:space:]]/}" ]; then + unset APP_STORE_CONNECT_API_KEY_ISSUER_ID +fi + # Change to iOS root for bundle install if [ -n "$IOS_ROOT_PATH" ]; then echo "Installing gems from: $IOS_ROOT_PATH" diff --git a/.github/actions/ios-fastlane-beta/test/test_beta.bats b/.github/actions/ios-fastlane-beta/test/test_beta.bats index baf82e9b..a84b7394 100644 --- a/.github/actions/ios-fastlane-beta/test/test_beta.bats +++ b/.github/actions/ios-fastlane-beta/test/test_beta.bats @@ -27,3 +27,21 @@ SCRIPT="$BATS_TEST_DIRNAME/../beta.sh" [ "$status" -eq 0 ] grep -q "^exec fastlane beta build_number:42 version_number:1.2.0$" "$BUNDLE_LOG" } + +@test "empty issuer ID — unset so fastlane treats the key as individual" { + APP_STORE_CONNECT_API_KEY_ISSUER_ID="" run bash "$SCRIPT" + [ "$status" -eq 0 ] + ! grep -q "^APP_STORE_CONNECT_API_KEY_ISSUER_ID=" "$ENV_LOG" +} + +@test "whitespace-only issuer ID — unset" { + APP_STORE_CONNECT_API_KEY_ISSUER_ID=" " run bash "$SCRIPT" + [ "$status" -eq 0 ] + ! grep -q "^APP_STORE_CONNECT_API_KEY_ISSUER_ID=" "$ENV_LOG" +} + +@test "issuer ID set — passed through to fastlane" { + APP_STORE_CONNECT_API_KEY_ISSUER_ID="abc-123" run bash "$SCRIPT" + [ "$status" -eq 0 ] + grep -q "^APP_STORE_CONNECT_API_KEY_ISSUER_ID=abc-123$" "$ENV_LOG" +} diff --git a/.github/actions/ios-fastlane-beta/test/test_helper.bash b/.github/actions/ios-fastlane-beta/test/test_helper.bash index 27486494..0acb0305 100644 --- a/.github/actions/ios-fastlane-beta/test/test_helper.bash +++ b/.github/actions/ios-fastlane-beta/test/test_helper.bash @@ -9,13 +9,15 @@ exit 0 MOCK chmod +x "$MOCK_DIR/gem" - # Mock bundle command — capture the full invocation + # Mock bundle command — capture the full invocation and the env fastlane sees BUNDLE_LOG="$(mktemp)" - export BUNDLE_LOG + ENV_LOG="$(mktemp)" + export BUNDLE_LOG ENV_LOG cat > "$MOCK_DIR/bundle" <> "$BUNDLE_LOG" + env > "$ENV_LOG" fi exit 0 MOCK @@ -23,5 +25,5 @@ MOCK } teardown() { - rm -rf "$MOCK_DIR" "$BUNDLE_LOG" + rm -rf "$MOCK_DIR" "$BUNDLE_LOG" "$ENV_LOG" } diff --git a/.github/actions/ios-fastlane-release/release.sh b/.github/actions/ios-fastlane-release/release.sh index 5eabeea3..e4c0b746 100755 --- a/.github/actions/ios-fastlane-release/release.sh +++ b/.github/actions/ios-fastlane-release/release.sh @@ -1,6 +1,12 @@ #!/bin/bash set -e +# Individual keys have no issuer ID, but an omitted secret arrives as "", which +# is truthy in Ruby — fastlane would sign iss: "" instead of sub: "user". +if [ -z "${APP_STORE_CONNECT_API_KEY_ISSUER_ID//[[:space:]]/}" ]; then + unset APP_STORE_CONNECT_API_KEY_ISSUER_ID +fi + # Change to iOS root for bundle install if [ -n "$IOS_ROOT_PATH" ]; then echo "Installing gems from: $IOS_ROOT_PATH" diff --git a/.github/actions/ios-fastlane-release/test/test_release.bats b/.github/actions/ios-fastlane-release/test/test_release.bats index 3b3e75bb..351b3f16 100644 --- a/.github/actions/ios-fastlane-release/test/test_release.bats +++ b/.github/actions/ios-fastlane-release/test/test_release.bats @@ -15,13 +15,15 @@ exit 0 MOCK chmod +x "$MOCK_DIR/gem" - # Mock bundle command — capture the full invocation + # Mock bundle command — capture the full invocation and the env fastlane sees BUNDLE_LOG="$(mktemp)" - export BUNDLE_LOG + ENV_LOG="$(mktemp)" + export BUNDLE_LOG ENV_LOG cat > "$MOCK_DIR/bundle" <> "$BUNDLE_LOG" + env > "$ENV_LOG" fi exit 0 MOCK @@ -29,7 +31,7 @@ MOCK } teardown() { - rm -rf "$MOCK_DIR" "$BUNDLE_LOG" + rm -rf "$MOCK_DIR" "$BUNDLE_LOG" "$ENV_LOG" } @test "no overrides — runs fastlane release without args" { @@ -55,3 +57,21 @@ teardown() { [ "$status" -eq 0 ] grep -q "^exec fastlane release build_number:42 version_number:1.2.0$" "$BUNDLE_LOG" } + +@test "empty issuer ID — unset so fastlane treats the key as individual" { + APP_STORE_CONNECT_API_KEY_ISSUER_ID="" run bash "$SCRIPT" + [ "$status" -eq 0 ] + ! grep -q "^APP_STORE_CONNECT_API_KEY_ISSUER_ID=" "$ENV_LOG" +} + +@test "whitespace-only issuer ID — unset" { + APP_STORE_CONNECT_API_KEY_ISSUER_ID=" " run bash "$SCRIPT" + [ "$status" -eq 0 ] + ! grep -q "^APP_STORE_CONNECT_API_KEY_ISSUER_ID=" "$ENV_LOG" +} + +@test "issuer ID set — passed through to fastlane" { + APP_STORE_CONNECT_API_KEY_ISSUER_ID="abc-123" run bash "$SCRIPT" + [ "$status" -eq 0 ] + grep -q "^APP_STORE_CONNECT_API_KEY_ISSUER_ID=abc-123$" "$ENV_LOG" +}