Guard iOS debug signing with live CORS (#181)

Verify the stable API and exact Capacitor iOS CORS contract before signing device-debug IPAs, with regression coverage and troubleshooting guidance.
This commit is contained in:
Jeppe B
2026-07-20 14:37:17 +02:00
committed by GitHub
parent a930bd35a5
commit 8ddac065c6
3 changed files with 71 additions and 0 deletions
+49
View File
@@ -171,6 +171,55 @@ jobs:
- name: Install dependencies
run: npm ci --legacy-peer-deps
- name: Verify stable API and Capacitor iOS CORS
shell: bash
run: |
set -euo pipefail
api_base="${IOS_DEBUG_API_URL%/}"
curl --fail --silent --show-error --location \
--connect-timeout 10 --max-time 20 \
--header 'Accept: application/json' \
--output /dev/null \
"$api_base/ping"
cors_headers="$RUNNER_TEMP/ios-debug-cors-headers.txt"
cors_body="$RUNNER_TEMP/ios-debug-cors-body.txt"
cors_status="$(curl --silent --show-error \
--connect-timeout 10 --max-time 20 \
--request OPTIONS \
--header 'Origin: capacitor://localhost' \
--header 'Access-Control-Request-Method: POST' \
--header 'Access-Control-Request-Headers: authorization,content-type' \
--dump-header "$cors_headers" \
--output "$cors_body" \
--write-out '%{http_code}' \
"$api_base/ping")"
if [[ ! "$cors_status" =~ ^2[0-9][0-9]$ ]]; then
echo "Stable API rejected the Capacitor iOS CORS preflight with HTTP $cors_status" >&2
sed -n '1,20p' "$cors_body" >&2
exit 1
fi
if ! grep -Eiq '^access-control-allow-origin:[[:space:]]*capacitor://localhost[[:space:]]*$' "$cors_headers"; then
echo "Stable API did not allow the exact capacitor://localhost origin" >&2
exit 1
fi
if ! grep -Eiq '^access-control-allow-credentials:[[:space:]]*true[[:space:]]*$' "$cors_headers"; then
echo "Stable API did not allow credentialed Capacitor requests" >&2
exit 1
fi
if ! grep -Eiq '^access-control-allow-methods:.*[[:space:],]POST([[:space:],]|$)' "$cors_headers"; then
echo "Stable API did not allow POST from the Capacitor origin" >&2
exit 1
fi
if ! grep -Eiq '^access-control-allow-headers:.*[[:space:],]Authorization([[:space:],]|$)' "$cors_headers"; then
echo "Stable API did not allow the Authorization header from the Capacitor origin" >&2
exit 1
fi
if ! grep -Eiq '^access-control-allow-headers:.*[[:space:],]Content-Type([[:space:],]|$)' "$cors_headers"; then
echo "Stable API did not allow the Content-Type header from the Capacitor origin" >&2
exit 1
fi
- name: Build stable production web payload
env:
RELEASE_COMMIT_SHA: ${{ env.RESOLVED_SOURCE_SHA }}
+18
View File
@@ -348,6 +348,24 @@ an already installed development build from launching.
or been revoked.
- Collect syslog and crash reports before reinstalling so evidence is preserved.
### App reports `No response was received`
- Keep the API base set to `https://api-v2.truckwash.io/master/api`. The bare
`https://api-v2.truckwash.io` host is the public gateway, not the application
API base.
- Confirm `https://api-v2.truckwash.io/master/api/ping` responds before
investigating the device or app.
- Capacitor serves bundled iOS content from `capacitor://localhost`. The stable
API must return `Access-Control-Allow-Origin: capacitor://localhost` for that
exact origin, including authenticated preflight requests.
- The signing workflow checks API reachability and this CORS contract before
compiling or signing. If it fails, deploy the backend CORS policy fix before
dispatching another IPA; do not replace the API URL or use an unsupported
HTTP/HTTPS `iosScheme` workaround.
- An already-built IPA starts using a corrected server-side CORS policy without
modification. Build and install a higher version when recording a verified
device-test result for the fix.
### iOS beta or new major iOS version breaks device tools
- Record the device model, exact iOS version, helper error, source SHA, and IPA
+4
View File
@@ -485,6 +485,10 @@ describe("repository device-debug configuration", () => {
expect(workflow).toContain("name: mobile-device-debug");
expect(workflow).toContain("RELEASE_COMMIT_SHA: ${{ env.RESOLVED_SOURCE_SHA }}");
expect(workflow).toContain("IOS_DEBUG_CERTIFICATE_BASE64: ${{ secrets.IOS_DEBUG_CERTIFICATE_BASE64 }}");
expect(workflow).toContain("Origin: capacitor://localhost");
expect(workflow).toContain("Stable API did not allow the exact capacitor://localhost origin");
expect(workflow).toContain("Stable API did not allow credentialed Capacitor requests");
expect(workflow).toContain("Stable API did not allow the Authorization header from the Capacitor origin");
expect(workflow).not.toContain("IOS_DEBUG_KEYCHAIN_PASSWORD");
expect(workflow).not.toMatch(/upload-app|notarytool|transporter/iu);
});