Sign-in is returning 403: Apple now requires a SAP signature on authenticate - #88
Draft
Tardisyuan wants to merge 3 commits into
Draft
Tardisyuan wants to merge 3 commits into
Tardisyuan wants to merge 3 commits into
Conversation
The browser-side signer runs four binaries from a 2013 OS X release under emulation. They carry no credentials and are identical for everyone, so they are plain static files — but they are 38 MB and they are Apple's, so they stay out of the image and out of git. The backend fetches them from Apple's software update CDN the first time the signer asks and keeps them in DATA_DIR/sap, so a fresh deployment needs nothing done to it by hand. They live inside a xar container holding a bzip2-compressed cpio archive; reading it the way ipatool does — locate Payload in the table of contents, range-request from where its bzip2 stream resumes, put the header back in front, skip to the cpio, stop once the four are out — costs a fraction of the package. Assets are verified by SHA-256 rather than size. A file of the right length but wrong contents loads fine and then fails deep inside the emulator with nothing pointing back at the download. Verdicts are cached until a file's size or mtime changes, so the status endpoint stays cheap to poll, and a corrupt file is replaced rather than skipped. Also proxies the two setup endpoints. Neither carries credentials — the only identity in the handshake is the device's hardware id, the guid already sent in the clear — so this is the same kind of proxy as /api/bag and leaves intact the guarantee that the server never sees Apple credentials. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Apple began requiring a SAP-signed X-Apple-ActionSignature on authenticate in August. The bag says so directly: urlBag.sign-sap-request lists MZFinance: authenticate, auth/v1: native and auth/v1/native: fast, and all three answer an unsigned request with 403 and an empty body about 6 ms in, before looking at credentials. Ported from ipatool's internal/sap. macho.ts loads the guest images, length.ts decodes x86-64 instruction lengths, engine.ts wraps unicorn.js, shims.ts and platform.ts stand in for the macOS the guest expects, machine.ts drives the four entry points, and signer.ts runs the setup protocol. unicorn.js is the same Unicorn 2.1.4 ipatool loads, built to WebAssembly. Two things that build forces: a guest call is bounded by instruction count alone, since a non-zero timeout makes Unicorn spawn a timer thread it cannot create; and it aborts inside QEMU's Tiny Code Interpreter on a long basic block, so machine.ts splits long blocks itself by planting a HLT at a safe boundary and resuming from it. README.md in that directory carries the measurements and the reasoning. None of the setup involves credentials — the only identity is the hardware id, which is the guid already sent in the clear — so it can happen well before anyone types a password. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wires the signer into sign-in: authenticate prepares one and sends X-Apple-ActionSignature with the request. Preparing it runs about ten million emulated instructions — roughly 35 s on WebKit, 115 s on Chrome — and each signature a few million more, so both happen in a Web Worker rather than freezing the tab. The signer starts preparing as soon as there is an account to bind it to, so a button pressed later usually finds it ready, and SapStatus shows whatever progress is under way rather than leaving a button looking dead. A signer is bound to the hardware id it was initialised with, so it is rebuilt when that changes rather than signing with another account's identity. And re-authenticating refuses immediately when an account has no password rather than spending two minutes preparing a signer it cannot use. Vite needs worker.format "es": the worker loads unicorn.js dynamically, and a code-splitting build cannot emit that as IIFE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Tested successfully on Synology NAS + Docker + HTTPS reverse proxy + Chrome.
PR #88 fixed Apple ID authentication (HTTP 403 empty body).
After applying PR #87 as well, Version History loads correctly and
downloads start successfully through the fallback path.
So for my setup, #88 + #87 together fix the full flow.
Thanks!
Vào Thứ 3, 1 thg 9, 2026 vào lúc 17:36 Tardisyuan <
***@***.***> đã viết:
… *Opening this as a draft to ask whether you want the approach, not to push
it.* Issues are disabled on this repo, so a PR is the only way I could
find to raise it. Happy to close it and leave just the analysis if you
would rather solve this differently.
Sign-in is broken for everyone
The UI reports errors.auth.emptyBody. The underlying response is 403 with Content-Length:
0, returned in about 6 ms — refused at the edge before credentials are
looked at.
Reproducible with plain curl, outside the app entirely:
curl -sS -o /dev/null -D - -X POST --http1.1 \
-A 'Configurator/2.17 (Macintosh; OS X 15.2; 24C5089c) AppleWebKit/0620.1.16.11.6' \
-H 'Content-Type: application/x-apple-plist' \
--data-binary @body.plist \
'https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate?guid=<guid>'
HTTP/1.1 403 Apple WebObjects 5.9.7-2
Content-Length: 0
apple-timing-app: 6 ms
Same with application/x-www-form-urlencoded, with and without ?guid=,
with X-Apple-Store-Front added, over HTTP/2, and against p{pod}-buy.
Reproduced from a residential IP, so it is not IP reputation, a WAF, or the
Wisp tunnel.
What changed
The live bag says it outright. urlBag.sign-sap-request lists these as
requiring a SAP-signed X-Apple-ActionSignature:
MZFinance: ['authenticate']
auth/v1: ['native']
auth/v1/native: ['fast']
urlBag.sign-sap-version = 200
urlBag.sign-sap-setup = https://fpinit.itunes.apple.com/v1/signSapSetup/legacy
urlBag.sign-sap-setup-cert <https://fpinit.itunes.apple.com/v1/signSapSetup/legacyurlBag.sign-sap-setup-cert> = https://s.mzstatic.com/sap/setupCert.plist
All three sign-in endpoints are covered, so switching between them does
not help.
Separately: urlBag.authenticateAccount currently returns the legacy
buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate, not
auth.itunes.apple.com, which makes normalizeAuthURL() in
frontend/src/apple/bag.ts inert today. Worth knowing regardless of what
happens to this PR.
ipatool hit exactly this — #522
<majd/ipatool#522>, #523
<majd/ipatool#523> — and fixed it in v2.4.0
<https://github.com/majd/ipatool/releases/tag/v2.4.0> by running Apple's
own signing code, taken from a 2013 OS X update package, under a CPU
emulator.
A signature does fix it
Sending the same deliberately wrong credentials with and without one
separates the cases cleanly:
without signature: HTTP 403, empty body
with signature: HTTP 200, 326 bytes, a parseable plist
customerMessage: MZFinance.BadLogin.Configurator_message
The second is the ordinary wrong-password answer — the request reached the
credential check. No account is needed to establish that, since what is
being tested is how far the request gets.
What this branch does
Ports that approach to run entirely client-side, so the server still never
sees Apple credentials. The SAP handshake carries none itself: the only
identity in it is the device's hardware id, which is the guid already sent
in the clear, so it can complete before anyone types a password.
Three commits: the backend fetching and serving the binaries, the signer
itself, and wiring it into sign-in behind a Web Worker.
frontend/src/apple/sap/README.md has the details and the measurements.
What you would be taking on
These are your calls, which is why this is a draft:
- *It runs Apple's proprietary binaries.* CommerceKit, CommerceCore,
CoreFP and CoreFP.icxs are fetched from Apple's software update CDN and
executed under emulation. ipatool made that call; this project has not had
to.
- *Deployment footprint.* ~38 MB per deployment in the data directory,
a ~2 MB dynamic chunk for unicorn.js, and three new outbound hosts (
swcdn.apple.com, s.mzstatic.com, fpinit.itunes.apple.com).
- *Speed.* Setup is ~35 s on WebKit and ~115 s on Chrome; each
signature 4–12 s. It runs in a worker, starts in the background, and
reports progress, but every sign-in pays it.
- *Maintenance.* ~3,600 lines: a Mach-O loader with the dyld
bind/rebase opcodes, an x86-64 instruction length decoder, the emulator
shims. Two fragile external anchors — the update package URL and a byte
offset within it — which ipatool shares.
- *Untested on real iOS devices.* It works under Playwright's WebKit
with an iPhone 15 profile, but the guest mapping is 144 MB plus 38 MB of
assets, and whether a phone tolerates that is unknown.
Also unrelated but noticed while working here:
downloads.package.downloadFailed and settings.data.exportFailed are
referenced by t() but missing from every locale, so those paths show the
raw key.
🤖 Generated with Claude Code <https://claude.com/claude-code>
------------------------------
You can view, comment on, or merge this pull request online at:
#88
Commit Summary
- 9ae63e9
<9ae63e9>
Fetch and serve the Apple binaries the SAP signer needs
- 6a78e6c
<6a78e6c>
Add a browser-side SAP signer
- 93b47d5
<93b47d5>
Sign the authenticate request, off the main thread
File Changes
(36 files <https://github.com/Lakr233/AssppWeb/pull/88/files>)
- *M* backend/package-lock.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-f89763fc11c1bc48ac3c7ce8a148fbbbc5c2062393ee78664a70273d714daf5c>
(61)
- *M* backend/package.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-495707834ca4b862f9acdfbac70d279023d2c059da13db59594e61ed3354fed5>
(1)
- *M* backend/src/index.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-c20200a666b149a045e365999f581db8e04687cfc9569bdf4c9477cab954d324>
(2)
- *A* backend/src/routes/sap.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-85845e56657c5365050ed9c2a71c4997c69d55ad4005de7d061c3880b4c0432c>
(194)
- *A* backend/src/services/sapAssets.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-8455ef2dca79bc80069b506a62c288cb53b4c6f831248927334395435a2b81d7>
(348)
- *A* backend/src/types/unbzip2-stream.d.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-4991871a09ef370bb1380d1893d60f2e87b65f8935556e334b727d7dffe608ba>
(5)
- *M* frontend/package-lock.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-4a2d9aa3e849b134993936ca81b83fb139edd2b0218077ab0f403b8c4803c62a>
(7)
- *M* frontend/package.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-da6498268e99511d9ba0df3c13e439d10556a812881c9d03955b2ef7c6c1c655>
(1)
- *M* frontend/src/apple/authenticate.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-d83ce59d808ed02d3934bb74fe19309183018e3794d1b1b6652b062616abaf31>
(20)
- *A* frontend/src/apple/sap/README.md
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-6a70be04e04cccb10cbf5189d5d5ef01e9209596436f234d8f300812fc7d1b80>
(152)
- *A* frontend/src/apple/sap/assets.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-c16eeff3b346b4bce19e50c14848cf828c6318d294ae99ed2563d94803a38a86>
(173)
- *A* frontend/src/apple/sap/client.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-b71a895aaf60da1cb671bb6ace071a01ad08d90e70d41702a449059ce7744746>
(183)
- *A* frontend/src/apple/sap/engine.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-e372b22396ba8e090e7fc9acedd35e8fba9e5d6f5ec3c90457f01cd4962211c6>
(197)
- *A* frontend/src/apple/sap/length.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-df24467067705804e234fa0df07db15e6e8e799fe959985043abb45cedd632fc>
(388)
- *A* frontend/src/apple/sap/machine.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-1558b42231e6782bb876bea54ab44ed3cbfc23eb3ee65a41f7fd193e985cbaf7>
(594)
- *A* frontend/src/apple/sap/macho.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-a1eff6d8741926f8994c145b1960e7e5c8b3818200cddf2165469397e20c8525>
(556)
- *A* frontend/src/apple/sap/platform.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-4697869f747ee563da984136c33b8991614851dfdda8f943c1d5fd3930276a54>
(271)
- *A* frontend/src/apple/sap/shims.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-cf64199630ba2e16ac841cc25525015f6e9a99c6e9152732d3aacfe7b2c57135>
(424)
- *A* frontend/src/apple/sap/signer.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-70e2cacc69f439eeb68f86411f3e8569cb94632a5e6d275da9dd71efb0808256>
(208)
- *A* frontend/src/apple/sap/unicorn-js.d.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-0cd87b88b555df53e872b13fb3c2a9507efeb3200ab809512258eadc656565ba>
(8)
- *A* frontend/src/apple/sap/worker.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-f878b1426f8743b9080eb24382049607167d6816488a3a356649f89b10d8e247>
(104)
- *M* frontend/src/components/Account/AccountDetail.tsx
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-99eade0d3ee87aa6588367f8ad65d7b0f86028ad8054059196f883561a293e6d>
(12)
- *M* frontend/src/components/Account/AddAccountForm.tsx
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-daaaafeb3b3fe769e475b373ed09e2a7fcfa5bbc02d8c3024b7d8939d1634218>
(2)
- *M* frontend/src/components/Search/ProductDetail.tsx
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-6b2cae98a9a3c7e172d69b6f80868cb74d4d9aba3962f2a6002b652348f72652>
(4)
- *A* frontend/src/components/common/SapStatus.tsx
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-2155a47f1d62633477a0962c3915aaac4f15c537588e01810cac74f087518e2b>
(35)
- *A* frontend/src/hooks/useSapWarmup.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-042830b88a4fa955f1370c70a44d2c5edd4979c39892ee6365d196868c937120>
(33)
- *M* frontend/src/locales/en-US.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-887624116e3b13efe9c8042122f7e5b482f82cedefc4db841f23186aa05a396e>
(5)
- *M* frontend/src/locales/ja.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-81c60deb4465b8b6d4e6b619967cf691fa33fb590e28c1421355ed9efb018ac1>
(7)
- *M* frontend/src/locales/ko.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-16ae3baf52ef1e504d3540ef36ae51cec76ad571517aeee7484597e91963eb1d>
(5)
- *M* frontend/src/locales/ru.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-1b1a51fe9466ee16b644fc148d1f09fd766e1bb7dc73e0357cb83c44200b40e7>
(7)
- *M* frontend/src/locales/zh-CN.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-3431a965cf458e3bbcfe7f18c561c997580f3aef2198cc0192be7e84cb596266>
(5)
- *M* frontend/src/locales/zh-TW.json
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-ae1a643dc290191c41070153b7afbcbcbb7d588588df43134e4c7d515eecfcbb>
(5)
- *A* frontend/src/store/sap.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-21752c1fdb92fa1fdb3174019c511242d5e427b4d2b0e0361ea942aaaf717911>
(54)
- *M* frontend/vite.config.ts
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-97c6e2c429ec483132c584137a18a1ade2ff6c3f4f6485f4a83db604d0323d7c>
(3)
- *A* tools/fetch-sap-assets.mjs
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-7d2e1ee1f999b8daabeb0545cd2fc9b1e744f852defada4baf9c742c4e3a608f>
(124)
- *A* tools/webkit-check.mjs
<https://github.com/Lakr233/AssppWeb/pull/88/files#diff-96585d5b511f0b4a25c8f583f363549e32ed873f047a9f10dd3b3f8a26b83138>
(90)
Patch Links:
- https://github.com/Lakr233/AssppWeb/pull/88.patch
- https://github.com/Lakr233/AssppWeb/pull/88.diff
—
Reply to this email directly, view it on GitHub
<#88?email_source=notifications&email_token=ATHE52MPPYWGOABUXBZ3XAL5M2RBRA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DIMJRGIYTGMRUG2THEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVRTG633UMVZF6Y3MNFRWW>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ATHE52OO73MQXIYQ6XX7DGL5M2RBRAVCNFSNUABGKJSXA33TNF2G64TZHMYTCNJSGU2DSMBRGI5US43TOVSTWNJTGEYDONRZGE4DRILWAI>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/ATHE52LRYB5BOYMKUXBBHIL5M2RBRA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DIMJRGIYTGMRUG2THEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVJTG633UMVZF62LPOM>
and Android
<https://github.com/notifications/mobile/android/ATHE52MGZREGWLJ6YAI6PIT5M2RBRA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DIMJRGIYTGMRUG2THEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVZTG633UMVZF6YLOMRZG62LE>.
Download it today!
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
--
*Duong Hong Phuong *
Mobile:* +84**933.747.991*
Email : ***@***.*** ***@***.***>*
======================================================================
|
hubo1989
added a commit
to hubo1989/AssppWeb
that referenced
this pull request
Sep 4, 2026
…cator The upstream draft PR (Lakr233#88) solved the same problem with an npm unicorn build; three of its UX decisions are worth taking regardless of engine: - Signer singleton bound to the deviceIdentifier. Rebuilding per authenticate() call copied the 22.5 MB asset bundle into a fresh worker on every attempt (2FA retries included); the singleton reuses the worker and SAP session across attempts and rebuilds only when the account or bag endpoints change. - Background warmup (useSapWarmup): once an account exists, fetch the bag and prepare the signer so sign-in usually finds it ready. The one-time asset download dominates preparation; later runs are served from the Cache API. - Inline progress (SapStatus) wired to a zustand store, with strings in all five locales. Also documents the upstream finding that the live bag hands out the legacy MZFinance authenticate endpoint and all advertised endpoints are covered by sign-sap-request.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opening this as a draft to ask whether you want the approach, not to push it. Issues are disabled on this repo, so a PR is the only way I could find to raise it. Happy to close it and leave just the analysis if you would rather solve this differently.
Sign-in is broken for everyone
The UI reports
errors.auth.emptyBody. The underlying response is403withContent-Length: 0, returned in about 6 ms — refused at the edge before credentials are looked at.Reproducible with plain curl, outside the app entirely:
Same with
application/x-www-form-urlencoded, with and without?guid=, withX-Apple-Store-Frontadded, over HTTP/2, and againstp{pod}-buy. Reproduced from a residential IP, so it is not IP reputation, a WAF, or the Wisp tunnel.What changed
The live bag says it outright.
urlBag.sign-sap-requestlists these as requiring a SAP-signedX-Apple-ActionSignature:All three sign-in endpoints are covered, so switching between them does not help.
Separately:
urlBag.authenticateAccountcurrently returns the legacybuy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate, notauth.itunes.apple.com, which makesnormalizeAuthURL()infrontend/src/apple/bag.tsinert today. Worth knowing regardless of what happens to this PR.ipatool hit exactly this — #522, #523 — and fixed it in v2.4.0 by running Apple's own signing code, taken from a 2013 OS X update package, under a CPU emulator.
A signature does fix it
Sending the same deliberately wrong credentials with and without one separates the cases cleanly:
The second is the ordinary wrong-password answer — the request reached the credential check. No account is needed to establish that, since what is being tested is how far the request gets.
What this branch does
Ports that approach to run entirely client-side, so the server still never sees Apple credentials. The SAP handshake carries none itself: the only identity in it is the device's hardware id, which is the guid already sent in the clear, so it can complete before anyone types a password.
Three commits: the backend fetching and serving the binaries, the signer itself, and wiring it into sign-in behind a Web Worker.
frontend/src/apple/sap/README.mdhas the details and the measurements.What you would be taking on
These are your calls, which is why this is a draft:
swcdn.apple.com,s.mzstatic.com,fpinit.itunes.apple.com).Also unrelated but noticed while working here:
downloads.package.downloadFailedandsettings.data.exportFailedare referenced byt()but missing from every locale, so those paths show the raw key.🤖 Generated with Claude Code