Skip to content

Fix Apple ID sign in failing with a 3840 plist parse error - #49

Open
Calvin-Zikakis wants to merge 2 commits into
rileytestut:marketplacefrom
Calvin-Zikakis:fix/gsa-html-response-marketplace
Open

Fix Apple ID sign in failing with a 3840 plist parse error#49
Calvin-Zikakis wants to merge 2 commits into
rileytestut:marketplacefrom
Calvin-Zikakis:fix/gsa-html-response-marketplace

Conversation

@Calvin-Zikakis

@Calvin-Zikakis Calvin-Zikakis commented Sep 4, 2026

Copy link
Copy Markdown

Sign in fails with NSCocoaErrorDomain 3840 "Encountered unknown tag html on line 1", which AltServer shows as "could not sign in with your Apple ID". Reported in altstoreio/AltStore#1776, #1699, #1747.

What happens

Sign in makes three calls to GsService2. Apple returns an HTML error page for some of them, and sendAuthenticationRequest() hands the body straight to PropertyListSerialization with no check, so a server error surfaces as a parse failure that reads like bad credentials.

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's edge also pins a connection to a backend node, and once that node starts failing every later request on the same connection fails too. ALTAppleAPI uses one session for everything, so all three calls share a connection and whichever request lands after it sours is the one that dies. That is why this looks specific to apptokens when it is not.

Changes

Retry 5xx up to five times with backoff, each attempt on its own session so it opens a new connection. Retrying on the shared session does nothing, every attempt inherits the same dead node.

Report a failed parse as NSURLErrorBadServerResponse with the status, Content-Type and a body snippet instead of the raw 3840. ALTAppleAPI.m already does this for the other endpoints. The same helper covers the trusted device 2FA handler, which had the identical blind parse.

It parses first and only builds the better error on failure, rather than gating on the status code. A status check misses HTML served with a 200, and GSA reports its own status in the body as Status.hsc, so returning early could hide real error codes. Parsing first also means nothing that works today changes.

Numbers

25 full sign in attempts per row, on an affected Mac:

                                   old UA    new UA
one shared connection (current)      2/25     19/25
fresh connection per request         0/25     18/25
fresh connection + retry on 5xx      5/25     25/25

#47 does most of the work here and is worth taking alongside this. I said earlier that the User-Agent looked unrelated, which was wrong, that came from a sample far too small to see the effect.

Testing

AltServer built with this signed in first try on a machine that had never once completed it, and installed AltStore. Builds clean.

Branches

marketplace and notarized have diverged and neither contains the other. AltStore's classic branches pin notarized, so #50 is this same fix for that side. Both are needed.

#48 found the parse problem independently against master, which looks dormant, HEAD is from July 2020.

sendAuthenticationRequest() handed the GSA response body straight to
PropertyListSerialization without checking that the server actually sent
a plist. When gsa.apple.com returns an HTML error page — it serves one
from its own edge, e.g. a 401 "Authorization Required" page with
Server: Apple — the parse failure was forwarded verbatim, surfacing as
NSCocoaErrorDomain 3840 "Encountered unknown tag html on line 1".
AltServer wraps that as "could not sign in with your Apple ID", which
reads as a credential or anisette problem rather than a server error.

ALTAppleAPI.m already maps a failed parse to NSURLErrorBadServerResponse
for the non-auth endpoints; the auth path was the outlier.

Add a propertyListResponse() helper used by the GsService2 handler and
the trusted-device 2FA verify handler, which had the same blind parse.
It parses first and only enriches on failure, rather than gating on the
status code: a status gate cannot catch HTML returned with a 200, and
GSA mirrors its status into the body as Status.hsc alongside ec/em, so
bailing out before reading the body risks masking real error codes such
as -20101. Parsing first also means no currently-working request changes
behaviour.

On failure the error becomes NSURLErrorBadServerResponse carrying the
original parse error as NSUnderlyingErrorKey, plus the status,
Content-Type and a 256-byte body snippet in NSDebugDescriptionErrorKey.

Reported in altstoreio/AltStore#1776, #1699 and #1747.
Apple's GSA edge keeps a keep-alive connection pinned to a backend node.
When that node starts failing, every subsequent request on the same
connection returns 5xx and never recovers. Measured on an affected Mac,
six requests over one reused connection:

    200 200 503 503 503 503
    200 503 503 503 503 503
    200 200 200 200 503 503

while six requests each on a fresh connection fail independently at
roughly half:

    503 200 503 503 503 200
    200 503 503 200 200 503

ALTAppleAPI keeps a single session for every request, so authenticate()
sends init, complete and apptokens down one connection. init and complete
go through, the connection sours, and apptokens gets an HTML 503 that
surfaces as NSCocoaErrorDomain 3840. That matches the reports exactly, and
explains why the failure looked specific to apptokens when it is really
just whichever request lands after the connection goes bad.

Retry 5xx up to five times with exponential backoff, giving each attempt
its own session so it opens a new connection. Retrying on the shared
session is useless here, since every attempt inherits the same dead node.

Verified on a machine that had never once completed sign in: all three
calls returned 200 and apptokens returned its token on the first attempt.

Reported in altstoreio/AltStore#1776, #1699 and #1747.
@Calvin-Zikakis Calvin-Zikakis changed the title Report server errors instead of a plist parse failure during GSA auth Fix Apple ID sign in failing with a 3840 plist parse error Sep 4, 2026
@Calvin-Zikakis
Calvin-Zikakis force-pushed the fix/gsa-html-response-marketplace branch from 9199500 to a91fb9e Compare September 4, 2026 06:17
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