Skip to content

Use a separate connection for each GrandSlam request - #52

Merged
rileytestut merged 2 commits into
rileytestut:notarizedfrom
sshane:grandslam-connection-fix
Sep 8, 2026
Merged

Use a separate connection for each GrandSlam request#52
rileytestut merged 2 commits into
rileytestut:notarizedfrom
sshane:grandslam-connection-fix

Conversation

@sshane

@sshane sshane commented Sep 6, 2026

Copy link
Copy Markdown

Signing in has been failing since roughly 2026-08-31 with NSCocoaErrorDomain 3840, "The data couldn't be read because it isn't in the correct format."

Cause

Apple's edge in front of gsa.apple.com now serves at most two requests per connection and answers everything after that with a 503 HTML error page.

Authenticating takes three requests, and URLSession sends them over one pooled connection:

o=init       200
o=complete   200   ec=0  hsc=200      <- password accepted
o=apptokens  503   <html>503 Service Temporarily Unavailable ... Apple</html>

The password is accepted; only the token exchange fails. The 503's HTML body is then parsed as a property list, which is where the 3840 error comes from — the message describes the parser, not the failure.

The 503 is generated at the edge rather than by GrandSlam: successful responses carry Content-Type: text/x-xml-plist and X-Apple-I-Retry-Request-UUID, while the 503 has Content-Type: text/html and neither.

Reproducing

Any POST body works, valid or not — only the position in the connection matters:

# 3rd request on a reused connection 503s; the first two do not
curl --http1.1 -w 'http=%{http_code}\n' -o /dev/null \
  -X POST -H 'Content-Type: text/x-xml-plist' --data-binary @body.plist \
  https://gsa.apple.com/grandslam/GsService2 \
  --next -w 'http=%{http_code}\n' -o /dev/null \
  -X POST -H 'Content-Type: text/x-xml-plist' --data-binary @body.plist \
  https://gsa.apple.com/grandslam/GsService2 \
  --next -w 'http=%{http_code}\n' -o /dev/null \
  -X POST -H 'Content-Type: text/x-xml-plist' --data-binary @body.plist \
  https://gsa.apple.com/grandslam/GsService2

# http=200
# http=200
# http=503

The same request on a fresh connection returns 200. Retrying does not help, because the retry reuses the same pooled connection.

Changes

  1. Use a separate connection for each GrandSlam request. Each request gets its own ephemeral session, so apptokens arrives first on a fresh connection.
  2. Report 5xx responses instead of failing to parse them. The status code is currently never checked, so any server error surfaces as a parse failure. This is independent of the first commit and useful on its own.

Verified end to end against a real Apple ID: initcompleteapptokens all return 200, and sign-in and refresh work again on both AltServer and on-device AltStore.

This looks like the cause behind the recent reports in altstoreio/AltStore#1776, #1777, #1781, #1782 and SideStore/SideStore#1459, #1464, #1465, which are all the same 3840 error.

Targeting notarized since that is what AltStore Classic pins; marketplace compiles the SRP paths out.

sshane and others added 2 commits September 5, 2026 17:03
Apple's edge began serving at most two requests per connection around
2026-08-31, answering everything after that with a 503 HTML error page.
Authenticating takes three requests -- init, complete, and apptokens --
and URLSession sends them over one pooled connection, so apptokens was
rejected every time and signing in always failed at the last step.

Give each request its own session, and therefore its own connection.

Verified by replaying a captured request: identical bytes succeed on a
fresh connection and return 503 as the third request on a reused one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrnGBkYZDRb57McNgLxCaE
The response body is parsed as a property list before the status code is
checked, so an HTML error page from Apple surfaces as NSCocoaErrorDomain
3840, "The data couldn't be read because it isn't in the correct format."
That describes the parser rather than the failure, and sends users looking
at their Apple ID, their anisette server, and their OS version.

Check for a 5xx first and report it as what it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrnGBkYZDRb57McNgLxCaE
@sshane

sshane commented Sep 6, 2026

Copy link
Copy Markdown
Author

Measured the per-connection allowance more precisely, comparing this PR's approach against the User-Agent change proposed in #47 (marketplace) and #51 (notarized).

old = akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0                  (current)
new = AuthKit/1 (Macintosh; OS X 26.5.2) (com.apple.dt.Xcode/26.0)   (#47 / #51)

6 requests down one connection, 3 trials each:

old   200 503 503 503 503 503
old   200 503 503 503 503 503
old   200 200 503 503 503 503     allowance 1-2, varies
new   200 200 200 200 503 503     allowance 4

So #47/#51 does raise the allowance, but the current string sometimes allows only one request, not two.

20 requests each on their own fresh connection, current string: 20/20 200, no failures. I could not reproduce the ~20-25% fresh-connection failure rate cited in #51; it may be IP or account specific.

That is why this PR uses a connection per request rather than raising the allowance: it holds whatever Apple sets the limit to, and needs no User-Agent change.

@CircuitSerein

Copy link
Copy Markdown

Independent confirmation of both the diagnosis and the fix, from a separate machine, network and Apple ID.

Reproduced the connection allowance with an intentionally invalid account name (probe-not-a-real-account@example.invalid), so no credentials are involved and Apple answers every request with an ordinary plist error rather than anything account-specific. Same result: requests 1 and 2 on a connection return 200 text/x-xml-plist, request 3 and every later one returns 503 text/html, and curl reports num_connects=0 for the reused ones. o=apptokens sent first on a fresh connection returns a normal plist, and four requests down one connection to developerservices2.apple.com are all 200, so this is specific to the gsa.apple.com edge and not to any endpoint, account or anisette state. Details in altstoreio/AltStore#1782.

Applied the same change — one NSURLSession per GrandSlam request, invalidated in the completion handler — as an injected hook rather than a source build, on macOS 27.0 (26A5421a), AltServer 1.7.2 (build 90), iPhone on iOS 27, free Apple ID. Sign-in, viewDeveloper, listTeams, addDevice, certificate issuance, app ID creation, provisioning profile download and app installation all succeed. Before the change, apptokens returned the HTML 503 on every attempt.

One data point worth adding for the AltStore side: the identical hook is needed inside AltStoreCore.framework. With it injected into AltStore.app, signing in on the device and installing an .ipa from within AltStore work again; without it, an AltServer-only fix leaves on-device sign-in failing with the same 3840, which is what several reports in #1782 are hitting.

Also worth separating out: the stale Xcode/3594.4.19 client identity from altstoreio/AltStore#1772 is a real inconsistency, but it is not this bug. With a coherent <Mac17,2> <macOS;27.0;26A5421a> <com.apple.AuthKit/1 (com.apple.dt.Xcode/25183.54.10)> string the apptokens request still returned the HTML 503 until connections stopped being reused — consistent with the measurement above showing #47/#51 only raises the allowance rather than removing the limit.

@rileytestut

Copy link
Copy Markdown
Owner

Amazing thank you much! I've merged this with some minor changes, will release an update ASAP with this fix 🙂

@sshane
sshane deleted the grandslam-connection-fix branch September 8, 2026 23:04
Calvin-Zikakis added a commit to Calvin-Zikakis/AltSign that referenced this pull request Sep 8, 2026
Apple's edge serves at most two requests per connection and 503s
everything after that. Signing in takes three requests over one pooled
session, so apptokens always fails and the HTML 503 gets parsed as a
plist, which is where the 3840 comes from.

Same fix as rileytestut#52, which landed on notarized. This branch still has the bug.
kabiroberai added a commit to xtool-org/xtool that referenced this pull request Sep 9, 2026
GSA seems to, sometimes, reject HTTP requests made on a kept-alive
connection with a 503. This was either the real root cause, or a second
root cause, for #243. Fix this by creating a new HTTP connection for
each GSA request.

See also: rileytestut/AltSign#52 and
rileytestut/AltSign#47

Speculatively fixes #232
@kisnner26

Copy link
Copy Markdown

Great to hear a release is coming. One thing worth checking before you cut it, since it may affect whether the release actually resolves this for people:

This bug looks like it has two independent triggers, and #52 addresses one of them. The other is the stale User-Agent (akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0), which is #51 and still open.

What's been measured so far, and what hasn't:

  • Modern UA + fresh connection — works. On macOS 27 I ran both changes as a runtime patch against stock 1.7.2 and apptokens went 503 → 200 on the first attempt, with the full developer-portal flow completing afterwards. Several people in iPadOS 26.2 Unable to install Altstore altstoreio/AltStore#1699 and #1776 have since confirmed the same combination on Windows and on macOS 10.15.
  • Modern UA + pooled connection — unreliable. @kswartz26 patched only the UA on Windows and still saw mostly failures with occasional success, which is what you'd expect if the request keeps landing on the same pinned node.
  • Stale UA + fresh connection — as far as I can tell, nobody has tested this in isolation. That's exactly the configuration a release carrying only Use a separate connection for each GrandSlam request #52 would ship.

So I can't tell you whether #52 alone is sufficient — it might well be, if connection pinning is the dominant factor. But it's the one combination without data behind it, and it's the one about to go out. If #51 is uncontroversial, shipping both together would remove the question entirely.

Happy to test stale-UA-plus-fresh-connection on my setup and report back if that's useful — I have the instrumentation still in place and can isolate the variable. Just say the word and I'll post the result here.

Also flagging, in case it's not on your radar: 1.7.5 (published yesterday) still carries the stale UA in both architecture slices of Contents/MacOS/AltServer, and it landed about seven hours before this merge — so anyone who updates to it, including people who had patched 1.7.2 by hand, is back to the unfixed state.

choiys080 added a commit to choiys080/isideload that referenced this pull request Sep 10, 2026
Apple's edge in front of gsa.apple.com began (~2026-08-31) serving only a
couple of requests per TLS connection and 503ing the rest. isideload reused
one pooled connection for the 3 login requests, so later ones failed. Set
pool_max_idle_per_host(0) so each request opens a fresh connection. Mirrors
rileytestut/AltSign#52.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

4 participants