What changed, and why it matters
This commit adds a new automated GitHub Actions workflow to build and upload an iOS version of the Skylight Wallet app. It handles code signing and App Store submission. The workflow itself is a normal CI/CD addition, but it stores sensitive signing credentials as GitHub secrets and writes them to disk during the build. There is no direct vulnerability in the diff, but it increases the app's attack surface by introducing a new mobile platform build path and handling high-value secrets in automation.
Review the workflow for least-privilege secret access, ensure the `DEMO_MODE=true` flag is intentional for production releases, verify that the `environment: Release` protection rules require manual approval, and audit runner cleanup to confirm signing keys and provisioning profiles are not retained in caches or artifacts. Consider pinning third-party actions to specific commit hashes.
Security signals we found
New CI/CD workflow handling Apple code-signing certificates and provisioning profiles
Secrets used: BUILD_CERTIFICATE_BASE64, BUILD_PROVISION_PROFILE_BASE64, P12_PASSWORD, KEYCHAIN_PASSWORD, APP_STORE_CONNECT_KEY_ID, APP_STORE_CONNECT_ISSUER_ID, APP_STORE_CONNECT_PRIVATE_KEY
Build command includes `--dart-define=DEMO_MODE=true`, which may enable demo/test behavior in a release build
Workflow modifies Xcode project signing configuration at build time with `sed`
New iOS artifact path introduced to release pipeline
Evidence from the diff
The patch adds a build-ios job to .github/workflows/release.yml that runs on macOS, installs Flutter, CocoaPods, and Rust, imports Apple code-signing certificates and provisioning profiles from base64-encoded GitHub secrets, rewrites Xcode project signing settings via sed, builds an iOS IPA with flutter build ipa --dart-define=DEMO_MODE=true, and uploads the result to App Store Connect using an API key stored in secrets. It also adds ios/ExportOptions.plist to .gitignore. No application source code is changed. The workflow uses standard GitHub secret patterns and does not leak credentials in logs, but it does place decrypted signing material on the runner filesystem and enables a new release artifact path.
Changed components
.github/workflows/release.ymlios/.gitignoreiOS release build pipelineApple code signing and App Store Connect upload processInspect captured patch +102 / −0
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d1aad3a..b2515e9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -156,6 +156,107 @@ jobs:
name: windows-x64
path: windows/Output/*.exe
+ build-ios:
+ name: iOS
+ runs-on: macos-latest
+ environment: Release
+ needs: version
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+ flutter-version: 3.41.0
+
+ - name: Install CocoaPods
+ run: brew install cocoapods
+
+ - name: Set up Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: aarch64-apple-ios
+
+ - name: Import code signing
+ env:
+ BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
+ BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
+ P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
+ KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
+ run: |
+ CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
+ PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+ echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
+ echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
+ security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
+ security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
+ security list-keychains -d user -s $KEYCHAIN_PATH
+ security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
+ mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
+ cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision
+
+ - name: Configure Xcode for manual signing
+ run: |
+ PP_PATH="$HOME/Library/MobileDevice/Provisioning Profiles/build_pp.mobileprovision"
+ security cms -D -i "$PP_PATH" -o "$RUNNER_TEMP/profile.plist"
+ PROFILE_UUID=$(plutil -extract UUID raw "$RUNNER_TEMP/profile.plist")
+ sed -i '' 's/CODE_SIGN_STYLE = Automatic;/CODE_SIGN_STYLE = Manual;/g' ios/Runner.xcodeproj/project.pbxproj
+ sed -i '' "s/PROVISIONING_PROFILE_SPECIFIER = \"\";/PROVISIONING_PROFILE_SPECIFIER = \"$PROFILE_UUID\";/g" ios/Runner.xcodeproj/project.pbxproj
+ sed -i '' 's/"CODE_SIGN_IDENTITY\[sdk=iphoneos\*\]" = "iPhone Developer";/"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution";/g' ios/Runner.xcodeproj/project.pbxproj
+ cat > ios/ExportOptions.plist << 'EOF'
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+ <plist version="1.0">
+ <dict>
+ <key>method</key>
+ <string>app-store</string>
+ <key>provisioningProfiles</key>
+ <dict>
+ <key>org.magicgrants.skylightwallet</key>
+ <string>PROFILE_UUID_PLACEHOLDER</string>
+ </dict>
+ <key>signingCertificate</key>
+ <string>Apple Distribution</string>
+ <key>signingStyle</key>
+ <string>manual</string>
+ </dict>
+ </plist>
+ EOF
+ sed -i '' "s/PROFILE_UUID_PLACEHOLDER/$PROFILE_UUID/g" ios/ExportOptions.plist
+
+ - name: Build IPA
+ run: |
+ flutter pub get
+ flutter build ipa --dart-define=DEMO_MODE=true --release --export-options-plist=ios/ExportOptions.plist
+
+ - name: Prepare artifact
+ run: |
+ VERSION='${{ needs.version.outputs.version }}'
+ mkdir -p dist
+ cp -v build/ios/ipa/*.ipa "dist/skylight-wallet-${VERSION}.ipa"
+
+ - name: Upload to App Store Connect
+ env:
+ APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
+ APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
+ APP_STORE_CONNECT_PRIVATE_KEY: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }}
+ run: |
+ mkdir -p ~/.appstoreconnect/private_keys
+ echo "$APP_STORE_CONNECT_PRIVATE_KEY" > ~/.appstoreconnect/private_keys/AuthKey_${APP_STORE_CONNECT_KEY_ID}.p8
+ chmod 600 ~/.appstoreconnect/private_keys/AuthKey_${APP_STORE_CONNECT_KEY_ID}.p8
+ VERSION='${{ needs.version.outputs.version }}'
+ xcrun altool --upload-app \
+ --type ios \
+ --file "dist/skylight-wallet-${VERSION}.ipa" \
+ --apiKey "$APP_STORE_CONNECT_KEY_ID" \
+ --apiIssuer "$APP_STORE_CONNECT_ISSUER_ID"
+
release:
name: Sign + Release
runs-on: ubuntu-latest
diff --git a/ios/.gitignore b/ios/.gitignore
index 90cc183..2fcf959 100644
--- a/ios/.gitignore
+++ b/ios/.gitignore
@@ -27,6 +27,7 @@ Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
build/
+ExportOptions.plist
# Exceptions to above rules.
!default.mode1v3
Why this scored 21/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.