Skip to content

Fix Apple ID sign in failing with a 3840 plist parse error (notarized line) - #50

Closed
Calvin-Zikakis wants to merge 2 commits into
rileytestut:notarizedfrom
Calvin-Zikakis:fix/gsa-retry-classic
Closed

Calvin-Zikakis wants to merge 2 commits into
rileytestut:notarizedfrom
Calvin-Zikakis:fix/gsa-retry-classic

Conversation

@Calvin-Zikakis

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

Copy link
Copy Markdown

Same fix as #49, ported to notarized.

marketplace and notarized have diverged and neither contains the other, so #49 does not reach anyone on the classic branches. AltStore's classic (AltServer 1.8b1, AltStore 2.3b2) and classic_v2.3b1 both pin db8e0eb here, so this is the side that reaches AltServer and non EU AltStore users.

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 NSCocoaErrorDomain 3840 "Encountered unknown tag html on line 1" and 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.

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.

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

The User-Agent matters more than anything else here. #47 fixes that on marketplace, but there is no equivalent for this branch yet, so notarized is still on the old UA even with this merged. Worth doing both.

Testing

Verified on a Mac and iPhone that had both been failing:

  1. AltServer built from classic_v2.3b1 with this patch signed in first try and installed AltStore.
  2. AltStore 2.3b1 built with this patch, sideloaded, refreshed apps fine where stock 2.2.1 failed every time.

Builds clean for iOS.

…as a plist

Port of the marketplace-branch fix onto the notarized line, which AltStore's
classic branches pin.

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. ALTAppleAPI uses a single shared session, so
authenticate() sends init, complete and apptokens down one connection: the
first two succeed, the connection sours, and apptokens gets an HTML 503 that
surfaces as NSCocoaErrorDomain 3840 "Encountered unknown tag html on line 1".

Retry 5xx up to five times with exponential backoff, each attempt on its own
session so it opens a new connection, and route the response through a helper
that reports NSURLErrorBadServerResponse with the status, Content-Type and a
body snippet instead of an opaque parse failure.
That handler had the identical blind parse, so an HTML error page there still
surfaced as an opaque 3840. Brings the notarized-line port to parity with the
marketplace one.
izaankml added a commit to izaankml/AltSign that referenced this pull request Sep 5, 2026
Since early September 2026 GrandSlam intermittently answers the sign-in
requests with an HTML 503 page instead of a plist, which AltSign fed
straight to PropertyListSerialization and surfaced as the opaque
"Encountered unknown tag html on line 1" (NSCocoaError 3840).

Two changes fix it:

- Send the modern AuthKit User-Agent. Apple now rejects the obsolete
  akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0 string a large fraction of the
  time; the AuthKit string current akd sends is accepted (measured on an
  affected Mac: old string 503'd 12/100, AuthKit 0/100).

- Use a fresh ephemeral URLSession per request and retry. GrandSlam's
  edge assigns a keep-alive connection to one backend node; once that
  node starts failing, every later request on the same connection 503s
  and does not recover. Sign-in sends init, complete and apptokens over
  one shared session, so the third request always failed. A new
  connection per attempt, plus a bounded 5xx retry (up to 5 attempts,
  1/2/4/8s backoff, within the ~30s anisette window), resolves it.

Root cause and approach from altstoreio/AltStore#1776 (Calvin-Zikakis)
and the upstream fixes in rileytestut#47/rileytestut#50/rileytestut#51.
izaankml added a commit to izaankml/AltSign that referenced this pull request Sep 5, 2026
Since early September 2026 GrandSlam intermittently answers the sign-in
requests with an HTML 503 page instead of a plist, which AltSign fed
straight to PropertyListSerialization and surfaced as the opaque
"Encountered unknown tag html on line 1" (NSCocoaError 3840).

Two changes fix it:

- Send the modern AuthKit User-Agent. Apple now rejects the obsolete
  akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0 string a large fraction of the
  time; the AuthKit string current akd sends is accepted (measured on an
  affected Mac: old string 503'd 12/100, AuthKit 0/100).

- Use a fresh ephemeral URLSession per request and retry. GrandSlam's
  edge assigns a keep-alive connection to one backend node; once that
  node starts failing, every later request on the same connection 503s
  and does not recover. Sign-in sends init, complete and apptokens over
  one shared session, so the third request always failed. A new
  connection per attempt, plus a bounded 5xx retry (up to 5 attempts,
  1/2/4/8s backoff, within the ~30s anisette window), resolves it.

Root cause and approach from altstoreio/AltStore#1776 (Calvin-Zikakis)
and the upstream fixes in rileytestut#47/rileytestut#50/rileytestut#51.
@Calvin-Zikakis

Copy link
Copy Markdown
Author

Closing this, #52 covers it and landed first.

Two bits from here weren't in that fix, so I split them out rather than leave this sitting:

#54 gives the 2FA code verification its own connection too, it still uses the pooled session
#51 ports the User-Agent change to this branch

#49 is the same fix as #52 for marketplace, which still has the bug.

@himanshubiolitec

Copy link
Copy Markdown

Browser sign-in at appleid.apple.com works fine with the same credentials, so
the account itself is healthy. Fails identically with both the built-in Anisette
provider and a self-hosted anisette-v3-server, so Anisette does not appear to be
the cause.

Likely root cause

The AltStore/AltSign project has diagnosed what looks like the same issue:

#50

Their finding: 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. Because sign-in is three
sequential requests (init, complete, apptokens) sent over one shared session,
the first requests can succeed and then the connection sours.

They also found that an outdated User-Agent
(akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0) caused Apple to drop the connection
after the second of the three sign-in requests, and replaced it with a current
AuthKit User-Agent.

Suggested fix

Their fix was to retry 5xx responses with exponential backoff, opening a new
session for each attempt
rather than reusing the existing one. The new
connection appears to be the important part — a plain retry on the same session
does not recover.

Relevant code here looks to be _gsa_request in findmy/reports/account.py.

Environment

  • findmy: 0.9.4
  • Python: 3.9
  • OS: Windows 11
  • Anisette: both built-in and dadoum/anisette-v3-server v2.2.2

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