Skip to content

Preserve Apple errors while retrying transient GrandSlam failures - #53

Open
ahmed-raza-shaikh wants to merge 1 commit into
rileytestut:notarizedfrom
ahmed-raza-shaikh:codex/gsa-auth-recovery
Open

Preserve Apple errors while retrying transient GrandSlam failures#53
ahmed-raza-shaikh wants to merge 1 commit into
rileytestut:notarizedfrom
ahmed-raza-shaikh:codex/gsa-auth-recovery

Conversation

@ahmed-raza-shaikh

@ahmed-raza-shaikh ahmed-raza-shaikh commented Sep 8, 2026

Copy link
Copy Markdown

Summary

GrandSlam sometimes returns an HTML error page during Apple ID sign-in. AltSign feeds that page to the property-list parser, so a transport/server failure appears as NSCocoaErrorDomain 3840 / Encountered unknown tag html. This change uses the modern AuthKit User-Agent and an isolated session for each GSA attempt, adds bounded recovery for unstructured HTTP 5xx responses, and preserves Apple's structured protocol results before considering retries.

This is a source-and-tests contribution against notarized, the line used by AltStore Classic. The companion AltStore integration PR #1786 consumes this dependency change.

Background

Existing work and why this proposal exists

This builds on existing investigations, rather than claiming the original discovery:

The additional reviewable pieces here are a monotonic time budget, protocol-result precedence over HTTP status, body-free error handling, and 17 executable regression scenarios. If maintainers prefer to fold these changes into #50/#51/#52 rather than merge another proposal, that would also address the goal. The existing proposals are still open; this PR does not imply they were merged or authored here.

Failure mechanism and evidence

The sign-in sequence sends SRP init, SRP complete, and apptokens requests through sendAuthenticationRequest. The original investigation observed a reused connection return two plist responses followed by an HTML 503. A later real installation attempt also produced a 503 after the per-request-session workaround was already installed. That latter observation is why this contribution adds bounded retries instead of assuming isolation alone guarantees success.

A local, credential-free HTTP/1.1 fixture independently reproduced the original failure: its first two requests on a connection return valid plists and subsequent requests return HTML 503. The original production method failed on the third exchange with Cocoa error 3840; the isolated-session method completed all three exchanges on separate connections. This proves the client behavior under that failure condition; it does not claim knowledge of Apple's internal load-balancer implementation or a universal two-request limit.

Changes

Isolate GSA transport and bound recovery

sendAuthenticationRequest continues to construct the same request body and headers, with the GSA User-Agent changed to the value proposed in #47. It delegates execution to a private sendGrandSlamRequest helper.

Each attempt:

  1. Checks the remaining time against a deadline based on ProcessInfo.systemUptime.
  2. Creates a fresh ephemeral URLSession and applies the remaining budget to both session timeout settings and the request timeout.
  3. Explicitly invalidates the session when its task completes.
  4. Returns transport errors immediately.
  5. Interprets a valid Response.Status plist using the existing Apple error/success semantics.
  6. Only if there is no usable structured protocol result, considers an HTTP 500–599 retry.

The policy is five total attempts, with delays of 1, 2, 4, and 8 seconds, constrained by 20 seconds per GSA exchange. A retry is not scheduled if its delay would exhaust the budget. This deadline is a deliberate operational bound, not a claim about the undocumented lifetime of anisette data; maintainer input on the budget is welcome.

Preserve protocol results

A structured Apple result takes precedence over the HTTP envelope. This preserves existing behavior for success and authentication challenges, including HTTP 409 with ec = 0, and avoids retrying real credential or anisette failures returned inside HTTP 503.

Existing mappings remain intact:

Result Behavior
-20101 / -22406 Existing incorrect-credentials error; no retry
-22421 Existing invalid-anisette error; no retry
Other nonzero ec with em Existing Apple error domain, code, and message; no retry
Valid Response.Status with zero or absent ec Existing successful protocol result, including challenge fields
Unstructured HTTP 500–599 Retry within the attempt/time limits
Cancellation, offline/network error, 4xx, malformed HTTP 200 Return an error without helper retries

The SRP payloads, key derivation, anisette generation, developer-portal requests, and two-factor code submission flows are unchanged. A 5xx response is not assumed to prove that Apple did not process the request.

Avoid leaking error-page contents

When an unusable response cannot be recovered, the user receives URLError.badServerResponse with the HTTP status when available. Neither the response body nor the property-list parser's underlying error is included. This avoids both the misleading format-error message and accidental disclosure of body content through an error object.

Testing

Added:

  • Tests/GrandSlamTransport/test_transport.py
  • Tests/GrandSlamTransport/README.md

Run on macOS with Python 3 and Xcode command-line tools:

python3 Tests/GrandSlamTransport/test_transport.py

The harness extracts the production Swift methods, changes only the endpoint to loopback, and compiles them with minimal model/error shims. A test-only URLProtocol injects cancellation and offline failures. No Apple account, certificate, password, verification code, or external authentication endpoint is used.

Result: all 17 scenarios passed. The persistent-503 case made exactly five attempts over approximately 16.1 seconds, within the 20-second budget. All fixture requests used separate connections, and every case observed one completion.

Coverage includes 503 recovery/exhaustion, 1/2/4/8 backoff, structured Apple errors on both 200 and 503, challenges/success on non-2xx responses, malformed HTML/array responses, cancellation/offline passthrough, in-flight timeout, insufficient/expired budgets, modern User-Agent, and absence of body/parser-error leakage. The global staged-file Semgrep and high-severity Bandit checks also passed.

Verification boundary

  • The equivalent production transport was built into both a local macOS AltServer and AltStore 2.2.1 (48) for iPhone during the recovery.
  • After applying the separately documented signing/UI compatibility steps, the reporter confirmed that AltStore opens, signs in, installs apps, and refreshes apps on an iPhone 15 Pro running iOS 27.0, using an Apple Silicon Mac on macOS 26.5.2.
  • The complete authentication source in this PR is byte-for-byte identical to the source in the device-validated recovery build (SHA-256 27f118687765b51fec0085d730d49b51943042624752c05ed084314ad7a388e6). The final PR source also has a fresh passing transport-suite run. This fixture tests macOS Foundation transport behavior, not live SRP or the actual numeric implementation of the shimmed error type.
  • Standalone SwiftPM validation initially lacked ldid's OpenSSL framework search path. Supplying that path let source compilation proceed, but the final link lacked the _alt_* corecrypto symbols normally provided by the workspace's separately built crypto library. Neither attempt is counted as a passing standalone package build. The successful recovery application builds used the Xcode workspace setup.
  • Windows, macOS 27 anisette generation, all account types, poor-network performance, and long-running refresh behavior have not been established by this test run.

Additional Notes

Deployment

AltSign is compiled into both sides of the Classic workflow. Rebuilding AltServer alone does not update the copy used by an existing iPhone installation. Consumers need coordinated Mac/iPhone builds and distribution of the patched iPhone IPA; otherwise the original format error can remain on-device.

This PR contains source and a local test harness only. It does not publish signed apps, signing identities, provisioning profiles, or raw device logs. It was prepared with AI assistance, reviewed against the production flow and upstream discussions, and validated with executed tests plus the reporter's explicit on-device confirmation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant