Skip to content

Surface HTTP errors from GSA auth instead of a plist parse failure - #48

Open
oddharsh wants to merge 1 commit into
rileytestut:masterfrom
oddharsh:fix/gsa-http-error-plist-parse
Open

Surface HTTP errors from GSA auth instead of a plist parse failure#48
oddharsh wants to merge 1 commit into
rileytestut:masterfrom
oddharsh:fix/gsa-http-error-plist-parse

Conversation

@oddharsh

@oddharsh oddharsh commented Sep 3, 2026

Copy link
Copy Markdown

What

sendAuthenticationRequestWithParameters:anisetteData:completionHandler: in ALTAppleAPI+Authentication.m parses the GSA auth response body as a plist without first checking the HTTP status code. When gsa.apple.com/grandslam/GsService2 returns a non-2xx response (observed: a plain 503 Service Temporarily Unavailable HTML page) instead of the expected plist, that HTML gets fed straight into NSPropertyListSerialization, which fails with:

Encountered unknown tag html on line 1
Error Domain=NSCocoaErrorDomain Code=3840 kCFPropertyListOldStyleParsingError

That's the parser's generic "this isn't a plist" error — it gives no indication the actual cause is a transient server error on Apple's end. Users just see AltServer/AltStore report "could not sign in... the data is not in the correct format", which reads as a local/config problem and sends people chasing anisette or account issues instead of just retrying later.

How I found it

Traced a live repro (AltServer 1.7.2, macOS 27 beta) by hooking CFPropertyListCreateWithData and +[NSPropertyListSerialization propertyListWithData:options:format:error:] in-process to capture the raw response bytes on parse failure. The captured body was:

<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body>
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>Apple</center>
</body>
</html>

with the failing call originating from AltSign-Dynamic via sendAuthenticationRequestWithParameters:anisetteData:completionHandler:, confirming the response is passed to the plist parser unconditionally.

Fix

Check NSHTTPURLResponse.statusCode before attempting to parse. On a non-2xx status, return a clear ALTAppleAPIErrorUnknown error describing the HTTP status instead of surfacing the plist parse failure.

Testing

Verified against the captured 503 response locally (confirmed the existing code path throws the "unknown tag html" parse error on that input, and that the new status check intercepts it before parsing). I don't have a way to build/run the full AltSign target here, so this hasn't been exercised through Xcode — flagging that for review.

gsa.apple.com/grandslam/GsService2 occasionally responds to the auth
request with a plain HTTP error page (e.g. 503 Service Temporarily
Unavailable) instead of a plist body. sendAuthenticationRequestWithParameters:
fed that HTML straight into NSPropertyListSerialization, which failed with
"Encountered unknown tag html on line 1" / kCFPropertyListOldStyleParsingError
- a confusing, low-level error that gives no hint the real issue is a
transient server error on Apple's end.

Check the response status code before parsing and return a clear,
actionable error when it's non-2xx.
@Calvin-Zikakis

Calvin-Zikakis commented Sep 4, 2026

Copy link
Copy Markdown

Heads up, this is against master, but that branch looks dormant. HEAD is 1c44cfd from July 2020 and still has the ObjC ALTAppleAPI+Authentication.m.

As far as I can tell no AltStore branch pins it. main pins 4746156, develop and patreon pin 5b216f4, marketplace (the default) pins 790b9cc, and 1.7 pins db120fc. All of those have Sources/ALTAppleAPI+Authentication.swift and no .m file.

I also checked a shipped binary rather than just the pointers. The AltSign-Dynamic.framework inside AltServer 1.7.2 exports _OBJC_CLASS_$__TtC7AltSign10GSAContext, the Swift GSAContext that only exists on that lineage, and has no sendAuthenticationRequestWithParameters selector.

So this probably will not reach the builds people are reporting on. I opened #49 with the Swift equivalent against marketplace, crediting this one. Riley would know whether master is still maintained for anything else.

Two thoughts on the status code check:

  1. It will not catch HTML served with a 200.
  2. GSA puts its status in the body as Status.hsc next to ec/em. If Apple ever returns a non-2xx carrying a real error code, returning early would hide things like -20101. I could not force that case so it may not matter, but parsing first and only building the error on failure avoids it.

requestTrustedDeviceTwoFactorCode's verify handler has the same blind parse if you want to grab it too.

@Calvin-Zikakis

Calvin-Zikakis commented Sep 4, 2026

Copy link
Copy Markdown

Update, and a correction to the guess I made above.

I put logging in a build and ran it on an affected Mac. Your 503 is the real thing. The 401 I mentioned was from malformed test requests I was sending, so ignore that part.

The third GSA call is what fails:

o=init       HTTP 200  text/x-xml-plist  ec=0
o=complete   HTTP 200  text/x-xml-plist  ec=0
o=apptokens  HTTP 503  text/html   <html>503 Service Temporarily Unavailable ... Apple</html>

Apple ID, password and anisette are all fine, Apple accepts them twice. Only apptokens for com.apple.gs.xcode.auth gets bounced.

Not rate limiting either, which I had also guessed. init and complete go through from the same IP in the same second, and adding a 6 second delay before apptokens changed nothing. Same result three runs in a row.

The same 503 is hitting SideStore, open since Aug 7 with an identical payload. Apple's developer status page shows everything green and GrandSlam is not listed there, so there is nothing to wait on.

None of this changes either patch, HTML still should not go to the plist parser. But neither of us is fixing sign in, we are just making it readable. Probably worth saying that in both descriptions so people do not expect a login fix.

@Calvin-Zikakis

Copy link
Copy Markdown

Correcting myself again, we found the actual cause.

Apple's GSA edge pins a connection to a backend node, and once that node starts failing every later request on the same connection returns 5xx and never recovers:

one reused connection:  200 200 503 503 503 503
                        200 503 503 503 503 503
fresh connection each:  503 200 503 503 503 200

ALTAppleAPI uses one URLSession for everything, so sign in sends init, complete and apptokens down a single connection. The first two succeed, the connection goes bad, and the third gets the HTML 503 we both saw. It is not specific to apptokens, that request is just always third.

So my earlier guesses were both wrong: not rate limiting, not IP based. It is connection reuse.

The fix is to retry 5xx with each attempt on its own session, so it opens a new connection. Plain retry on the shared session does nothing, I tried it and it failed five times in a row on the same dead connection. With a fresh connection per attempt, sign in went through first try on a machine that had never completed it.

That is on #49 now. Worth doing on this branch too if master is still built for anything, since the status code check alone will report the 503 clearly but the sign in will still fail.

@Calvin-Zikakis

Copy link
Copy Markdown

One correction to what I wrote above. I said GSA returned 200 for the old akd UA, the new AuthKit one and no UA at all, so the UA was unrelated. That came from a 6 request sample and it was wrong.

Re-measured interleaved, 100 requests each:

old  akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0        100 requests, 12x 503
new  AuthKit/1 (Macintosh; OS X 26.5.2) (...26.0)   100 requests,  0x 503

So #47 is addressing a real trigger. The connection reuse problem is still underneath it, a reused connection sours with the new UA too, just later, so both changes are worth having.

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.

2 participants