Skip to content

Sanitize X-MMe-Client-Info to avoid Apple's Xcode-based GSA block - #135

Open
Rogue911 wants to merge 1 commit into
NyaMisty:newfrom
Rogue911:fix/gsa-client-info-xcode-block-clean
Open

Rogue911 wants to merge 1 commit into
NyaMisty:newfrom
Rogue911:fix/gsa-client-info-xcode-block-clean

Conversation

@Rogue911

Copy link
Copy Markdown

Problem

Since ~early September 2026, every Apple ID sign-in through this project fails
with an HTTP 503 from gsa.apple.com/grandslam/GsService2, before Apple's
authentication logic is ever reached.

Root cause

Credit for the actual root cause goes to
altstoreio/AltStore#1790:
Apple's GSA edge rejects any request whose X-MMe-Client-Info header
contains the substring com.apple.dt.Xcode, independent of version,
User-Agent, or connection reuse. That PR fixed it for AltStore's own (Swift,
macOS) anisette code.

This project doesn't share that code — it fetches anisette data as JSON from
an external anisette server (AnisetteDataManager::FetchAnisetteData), and
the X-MMe-Client-Info value such servers commonly return still identifies
as Xcode, tripping the same block. I confirmed this independently with two
otherwise-identical requests directly against the live endpoint:

X-MMe-Client-Info containing "com.apple.dt.Xcode" -> 503 (blocked at the edge)
X-MMe-Client-Info containing "com.apple.akd"      -> 401 (reaches the real service)

Fix

Adds a small SanitizeClientInfo() helper in AnisetteDataManager.cpp that
rewrites that one substring at the single point anisette data enters this
program (right when the anisette server's JSON response is parsed), so every
downstream call site in AltSign that later forwards
anisetteData->deviceDescription() as the X-MMe-Client-Info header (there
are four of them) is covered without needing to touch vendored AltSign code
at all.

This is intentionally scoped to just this one issue. It does not include
the separate, already-known GrandSlam "reuse the same connection for 3
requests" fix (AltSign#52 /
#47), since that lives in
the vendored AltSign submodule rather than this project's own code, and
would need a different approach (e.g. extending
rewrite_altsign_source.py) to contribute cleanly. Happy to follow up with
that separately if useful.

Testing

Verified end-to-end against a real iPhone and a real Apple ID over several
days of testing (no device or account details included here). Before this
change, GrandSlam requests failed with HTTP 503 on the very first request.
After it, requests reach Apple's authentication service normally (structured
200/401 responses instead of an opaque 503 HTML page), and a full
sign-in + IPA install completes successfully.

Not tested on 32-bit ARM/x86 builds, only x86_64.

Since ~2026-09, Apple's GrandSlam authentication edge
(gsa.apple.com/grandslam/GsService2) rejects any request whose
X-MMe-Client-Info header contains the substring "com.apple.dt.Xcode"
with an immediate HTTP 503, regardless of User-Agent, request/response
plist content, or connection reuse.

This was independently reproduced with two direct requests against the
live endpoint, differing only in that header:

  X-MMe-Client-Info containing "com.apple.dt.Xcode" -> 503 (blocked)
  X-MMe-Client-Info containing "com.apple.akd"      -> 401 (reaches
                                                        the real service)

Root cause and fix credited to altstoreio/AltStore#1790, which patched
AltStore's own (Swift) anisette code on macOS. AltServer-Linux doesn't
share that code - it fetches anisette data as JSON from an external
anisette server (see AnisetteDataManager::FetchAnisetteData), and the
X-MMe-Client-Info value returned by anisette servers commonly still
identifies as Xcode, tripping the same block.

This adds a small SanitizeClientInfo() helper that rewrites that one
substring at the single point anisette data enters this program, so
every caller that later forwards deviceDescription() as the
X-MMe-Client-Info header (there are four call sites across AltSign)
is covered without touching vendored AltSign code.

Verified end-to-end against a real device and Apple ID over several
days of testing: requests that previously failed with HTTP 503 before
ever reaching Apple's authentication logic now get a normal, structured
response.
Ben-Diehlci pushed a commit to Ben-Diehlci/altserver-linux that referenced this pull request Sep 14, 2026
Both are on the path of the very first thing that will be run -- a hand-typed
CLI install of AltStore.ipa -- so they are worth clearing before that attempt
rather than debugging mid-bootstrap.

1. PR NyaMisty#135, X-MMe-Client-Info sanitization. Since ~2026-09 Apple's GSA edge
   rejects with an immediate 503 any request whose X-MMe-Client-Info contains
   "com.apple.dt.Xcode", and anisette servers commonly return exactly that --
   upstream AltStore still builds one containing com.apple.dt.Xcode/3594.4.19 as
   of v2.3.3, confirmed in the local clone. Rewritten to com.apple.akd at the
   single point where anisette data enters the program.

   This is an unverified third-party claim that cannot be tested without a real
   Apple ID, and it alters a header Apple sees, so it is defeatable at runtime:
   ALTSERVER_NO_CLIENTINFO_SANITIZE=1 disables it. If sign-in fails with the
   rewrite in place, toggle that before blaming the anisette server. It also
   logs when it actually rewrites something, so the logs say which mode was in
   effect.

2. argv parsing in AltServerMain.cpp. `case 'a'` had no break and fell through
   into `case 'p'`, so `-a ID` set the password to ID as well. With the
   conventional `-u -a -p` ordering it works by accident because -p overwrites
   afterwards; with `-p PASS -a ID` the password silently becomes the Apple ID
   and sign-in fails with an unrelated-looking error. All five argv pointers were
   also uninitialised while udid/appleID/password are read unconditionally at the
   install call, making a missing flag undefined behaviour. And 'h' was handled
   in the switch but absent from the optstring, so -h printed
   "?? getopt returned character code 077 ??" before the usage text.

   Added a guard that names exactly which required flags are missing instead.

Verified against a real build: -h prints usage cleanly; `-u X -a Y` with no -p
now reports "Missing: --password", which is the direct proof that -a no longer
sets it; all three flags together pass the guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ben-Diehlci pushed a commit to Ben-Diehlci/altserver-linux that referenced this pull request Sep 14, 2026
…-bearing

Phase 2 is up. The contract passes -- ten keys, all strings, HTTP 200, clock in
sync -- and more importantly the volume mapping is proven rather than assumed:
docker diff shows nothing identity-related on the container's writable layer,
while adi.pb and device.json are present host-side. The lib/-only mount that the
upstream README recommends was avoided, so a Portainer stack update will not
destroy the machine identity.

Records one result that retroactively justifies an earlier change: this server
returns com.apple.dt.Xcode/3594.4.19 in X-MMe-Client-Info, which is exactly the
substring Apple's GSA edge rejects with a 503. The PR NyaMisty#135 sanitizer applied in
f2540f3 is therefore load-bearing for sign-in rather than speculative, and
ALTSERVER_NO_CLIENTINFO_SANITIZE should stay unset.

Also notes adi.pb is mode ---x-w-rwt, written that way by Apple's closed-source
libCoreADI, so backups of it require sudo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ben-Diehlci pushed a commit to Ben-Diehlci/altserver-linux that referenced this pull request Sep 14, 2026
…nst real Apple infra

The com.apple.dt.Xcode -> com.apple.akd rewrite was applied on an unverified
third-party claim from a PR opened two days earlier. It is now reproduced in
both directions on this deployment, using the ALTSERVER_NO_CLIENTINFO_SANITIZE
escape hatch added for exactly this purpose:

  sanitizer ON  -> client-info says com.apple.akd        -> first GSA request 200
  sanitizer OFF -> client-info says com.apple.dt.Xcode   -> first GSA request 503

So Apple really does reject anything carrying that substring, and the rewrite
really does get past it. Keep it enabled. Worth reporting back on PR NyaMisty#135, whose
author had only a curl repro.

This also separates two problems that presented as one. The remaining failure is
a 429 on the SECOND GSA request: unaffected by client-info, identical across two
attempts 28 minutes apart, and present on the very first sign-in ever attempted
from this machine -- so it is not cumulative volume throttling, which would be
expected to move or escalate.

Records the leading hypothesis while it is investigated: X-Apple-I-MD is a
one-time password that regenerates per anisette fetch (observed directly -- two
fetches 82 seconds apart returned different values), but FetchAnisetteData is
called once and the result is used for BOTH GSA requests. The second request may
therefore be replaying a consumed OTP. That fits every observation and would be
fixable here rather than being an Apple-side wall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ben-Diehlci pushed a commit to Ben-Diehlci/altserver-linux that referenced this pull request Sep 14, 2026
…y#135

SLICE 2 of the setup portal. Pairing is where someone without background gets
stuck, and its failure modes are unusually opaque, so the wizard's job is not to
run commands -- it is to tell the three "nothing is showing up" cases apart:

  - tools missing, vs usbmuxd not running, vs no device on the cable
  - device present but LOCKED: `idevicepair validate` reports "a passcode is
    set", which reads like a permissions problem when it means "press the button"
  - device present but untrusted: needs `idevicepair pair` then TRUST on-device

It states plainly that a USB cable is required exactly once and cannot be avoided
(HAVE_WIRELESS_PAIRING is undefined in this build), while making clear that
refresh afterwards is wireless -- which is the surprising part for a project
whose whole point is wireless refresh. It also covers hypervisor USB passthrough,
since on this deployment the VM is the likeliest reason a plugged-in phone never
appears, and charge-only cables.

Once paired it does not stop at "done": it prompts to back up /var/lib/lockdown
and explains that BOTH files must be kept together, because restoring one without
the other yields a mismatched HostID/SystemBUID that iOS rejects with the same
generic error as every other lockdownd fault.

Verified: all routes respond, and on a host without the tools the wizard blocks
at step 1 rather than cascading misleading errors.

Also drafts docs/pr135-confirmation.md -- a comment for upstream PR NyaMisty#135. That PR
applied an unverified claim; this deployment reproduced it end-to-end in both
directions against live Apple infrastructure (503 with com.apple.dt.Xcode
present, 200 with it rewritten), which is materially stronger evidence than the
curl repro currently on the PR. The draft is honest that sign-in still fails
afterwards with an unrelated 429, so nobody concludes the patch is ineffective,
and notes the misleading "invalid response" error that masks both. Checked to
contain no Apple ID, UDID, machine identifier or hostname.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ben-Diehlci pushed a commit to Ben-Diehlci/altserver-linux that referenced this pull request Sep 15, 2026
Both are on the path of the very first thing that will be run -- a hand-typed
CLI install of AltStore.ipa -- so they are worth clearing before that attempt
rather than debugging mid-bootstrap.

1. PR NyaMisty#135, X-MMe-Client-Info sanitization. Since ~2026-09 Apple's GSA edge
   rejects with an immediate 503 any request whose X-MMe-Client-Info contains
   "com.apple.dt.Xcode", and anisette servers commonly return exactly that --
   upstream AltStore still builds one containing com.apple.dt.Xcode/3594.4.19 as
   of v2.3.3, confirmed in the local clone. Rewritten to com.apple.akd at the
   single point where anisette data enters the program.

   This is an unverified third-party claim that cannot be tested without a real
   Apple ID, and it alters a header Apple sees, so it is defeatable at runtime:
   ALTSERVER_NO_CLIENTINFO_SANITIZE=1 disables it. If sign-in fails with the
   rewrite in place, toggle that before blaming the anisette server. It also
   logs when it actually rewrites something, so the logs say which mode was in
   effect.

2. argv parsing in AltServerMain.cpp. `case 'a'` had no break and fell through
   into `case 'p'`, so `-a ID` set the password to ID as well. With the
   conventional `-u -a -p` ordering it works by accident because -p overwrites
   afterwards; with `-p PASS -a ID` the password silently becomes the Apple ID
   and sign-in fails with an unrelated-looking error. All five argv pointers were
   also uninitialised while udid/appleID/password are read unconditionally at the
   install call, making a missing flag undefined behaviour. And 'h' was handled
   in the switch but absent from the optstring, so -h printed
   "?? getopt returned character code 077 ??" before the usage text.

   Added a guard that names exactly which required flags are missing instead.

Verified against a real build: -h prints usage cleanly; `-u X -a Y` with no -p
now reports "Missing: --password", which is the direct proof that -a no longer
sets it; all three flags together pass the guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ben-Diehlci pushed a commit to Ben-Diehlci/altserver-linux that referenced this pull request Sep 15, 2026
…-bearing

Phase 2 is up. The contract passes -- ten keys, all strings, HTTP 200, clock in
sync -- and more importantly the volume mapping is proven rather than assumed:
docker diff shows nothing identity-related on the container's writable layer,
while adi.pb and device.json are present host-side. The lib/-only mount that the
upstream README recommends was avoided, so a Portainer stack update will not
destroy the machine identity.

Records one result that retroactively justifies an earlier change: this server
returns com.apple.dt.Xcode/3594.4.19 in X-MMe-Client-Info, which is exactly the
substring Apple's GSA edge rejects with a 503. The PR NyaMisty#135 sanitizer applied in
04dd7ee is therefore load-bearing for sign-in rather than speculative, and
ALTSERVER_NO_CLIENTINFO_SANITIZE should stay unset.

Also notes adi.pb is mode ---x-w-rwt, written that way by Apple's closed-source
libCoreADI, so backups of it require sudo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ben-Diehlci pushed a commit to Ben-Diehlci/altserver-linux that referenced this pull request Sep 15, 2026
…nst real Apple infra

The com.apple.dt.Xcode -> com.apple.akd rewrite was applied on an unverified
third-party claim from a PR opened two days earlier. It is now reproduced in
both directions on this deployment, using the ALTSERVER_NO_CLIENTINFO_SANITIZE
escape hatch added for exactly this purpose:

  sanitizer ON  -> client-info says com.apple.akd        -> first GSA request 200
  sanitizer OFF -> client-info says com.apple.dt.Xcode   -> first GSA request 503

So Apple really does reject anything carrying that substring, and the rewrite
really does get past it. Keep it enabled. Worth reporting back on PR NyaMisty#135, whose
author had only a curl repro.

This also separates two problems that presented as one. The remaining failure is
a 429 on the SECOND GSA request: unaffected by client-info, identical across two
attempts 28 minutes apart, and present on the very first sign-in ever attempted
from this machine -- so it is not cumulative volume throttling, which would be
expected to move or escalate.

Records the leading hypothesis while it is investigated: X-Apple-I-MD is a
one-time password that regenerates per anisette fetch (observed directly -- two
fetches 82 seconds apart returned different values), but FetchAnisetteData is
called once and the result is used for BOTH GSA requests. The second request may
therefore be replaying a consumed OTP. That fits every observation and would be
fixable here rather than being an Apple-side wall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ben-Diehlci pushed a commit to Ben-Diehlci/altserver-linux that referenced this pull request Sep 15, 2026
…y#135

SLICE 2 of the setup portal. Pairing is where someone without background gets
stuck, and its failure modes are unusually opaque, so the wizard's job is not to
run commands -- it is to tell the three "nothing is showing up" cases apart:

  - tools missing, vs usbmuxd not running, vs no device on the cable
  - device present but LOCKED: `idevicepair validate` reports "a passcode is
    set", which reads like a permissions problem when it means "press the button"
  - device present but untrusted: needs `idevicepair pair` then TRUST on-device

It states plainly that a USB cable is required exactly once and cannot be avoided
(HAVE_WIRELESS_PAIRING is undefined in this build), while making clear that
refresh afterwards is wireless -- which is the surprising part for a project
whose whole point is wireless refresh. It also covers hypervisor USB passthrough,
since on this deployment the VM is the likeliest reason a plugged-in phone never
appears, and charge-only cables.

Once paired it does not stop at "done": it prompts to back up /var/lib/lockdown
and explains that BOTH files must be kept together, because restoring one without
the other yields a mismatched HostID/SystemBUID that iOS rejects with the same
generic error as every other lockdownd fault.

Verified: all routes respond, and on a host without the tools the wizard blocks
at step 1 rather than cascading misleading errors.

Also drafts docs/pr135-confirmation.md -- a comment for upstream PR NyaMisty#135. That PR
applied an unverified claim; this deployment reproduced it end-to-end in both
directions against live Apple infrastructure (503 with com.apple.dt.Xcode
present, 200 with it rewritten), which is materially stronger evidence than the
curl repro currently on the PR. The draft is honest that sign-in still fails
afterwards with an unrelated 429, so nobody concludes the patch is ineffective,
and notes the misleading "invalid response" error that masks both. Checked to
contain no Apple ID, UDID, machine identifier or hostname.

Co-Authored-By: Claude Opus 5 <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.

1 participant