62 Commits

  • build: fix compiler crash in release due to unimplemented() (#432)
    ## What does this PR do?
    
    Fixes #431. Since #389, `swift build -c release` crashes the compiler on
    Linux x86_64. The two `unimplemented()` calls in `UnimplementedSigner`
    (`Sources/XKit/Signer/Signer.swift`) set it off:
    - they pick the parameter-pack overload of `unimplemented`, with an
    empty pack;
    - that call, from an `async` function, hits a Swift compiler bug,
    reported as swiftlang/swift#94055: `CoroSplitPass` emits an invalid GEP,
    and LLVM's FunctionAttrs pass then crashes.
    
    This PR replaces the two calls with what `unimplemented()` does when
    called: `reportIssue(...)`, then `throw UnimplementedFailure(...)`. The
    test dependency keeps the same behavior, and nothing changes outside it.
    
    ## How was it tested?
    
    In upstream's own image (the `build-limd` stage of the `Dockerfile`, on
    `swift:6.4.2-jammy`), x86_64:
    - `swift build -c release --product xtool`: builds, and `xtool --help`
    runs. Without this change it crashes, as described in #431.
    - `swift test`: `Test run with 61 tests in 7 suites passed`.
    - The bisect, the reduction to these two calls, and the minimal
    reproducer are in #431 and swiftlang/swift#94055.
    
    Not tested: macOS, arm64, signing on a device with this exact commit.
    swiftlint was not run.
    
    ## AI tool usage
    
    How much of the code in this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI in writing code. I reviewed the
    finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
  • anisette: fix crash in date formatter (#422)
    ## What does this PR do?
    
    Password login can terminate with an illegal-instruction crash on Linux,
    leaving the user logged out. Anisette client time was formatted with
    DateFormatter and an explicitly assigned Gregorian calendar. That
    formatting path reaches Foundation’s NSCalendar bridging code, where the
    reported crash occurs.
    
    AnisetteData now formats client time with Date.ISO8601FormatStyle,
    avoiding the DateFormatter calendar bridge. The header retains its UTC
    timestamp format with whole seconds and a trailing Z. New parameterized
    tests check the complete header dictionary for the Unix epoch, a year
    boundary, leap day, and a timestamp with fractional seconds.
    
    Fixes #184
    
    ## How was it tested?
    
    Ran `swift test` locally on macOS for the xtool AnisetteData change; it
    fails the same way on the base branch, so the failure predates this
    change (it fails the same way on upstream/main).
    `swift build` failed because an async call in a defer body prevented
    compilation.
    `swift test` failed during compilation with the same async-in-defer
    error, so the tests did not run.
    `repo_lint` ran without reported output.
    `make lint SWIFTLINT_FLAGS='--reporter=github-actions-logging' | tee
    swiftlint.log` ran with no lint diagnostics shown in the transcript.
    
    The tests in `Tests/XToolTests/AnisetteDataTests.swift` fail without the
    fix in the xtool AnisetteData change (checked by reverting the source
    changes).
    
    ## AI tool usage
    
    How much of the code in this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI in writing code. I reviewed the
    finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    AI was used for assistance.
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Client timestamps now use a consistent UTC ISO 8601 format with dashed
    dates, colon-separated times, and no fractional seconds. This applies to
    timestamps with or without fractional seconds in the source date.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
  • ds: fix profile creation conflicts (#430)
    ## What does this PR do?
    
    Fixes issues with missing devices and duplicate names when creating new
    profiles. Supersedes #427 and #428.
    
    Fixes #425, fixes #426
    
    ## How was it tested?
    
    - [x] Ran locally
    - [x] Confirmed that free accounts also support multiple profiles as
    long as the names are different.
    
    ## AI tool usage
    
    How much of the code in this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI in writing code. I reviewed the
    finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Profile creation now uses only enabled devices that support iOS apps.
    * Existing profiles with the exact generated profile name are removed
    before a replacement is created, including when multiple matches exist.
      * Profile handling now stops when the operation is canceled.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    Co-authored-by: Valentin Brosseau <c4software@gmail.com>
  • gsa: make more reqs ephemeral (#417)
    ## What does this PR do?
    
    I'm not sure if this is needed but, for good measure, updates remaining
    GSA requests to use an ephemeral client (followup from #247)
    
    ## How was it tested?
    
    - [x] Tested flows involving all of these requests
    
    ## AI tool usage
    
    How much of the code in this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI in writing code. I reviewed the
    finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Improvements**
    * Selected account and lookup requests now use temporary HTTP
    connections. Existing request details, response handling, and progress
    reporting remain supported, while other requests continue through the
    standard connection flow. This change applies across the app’s related
    account and lookup operations; no new user-facing controls or workflows
    are introduced.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • gsa: more SMS 2FA fixes (#416)
    ## What does this PR do?
    
    Applies the 2FA SMS login fixes from #405
    
    ## How was it tested?
    
    - [x] 2FA SMS arrives
    - [x] Can log in with SMS code
    
    ## AI tool usage
    
    How much of the code in this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI in writing code. I reviewed the
    finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Improvements**
    * SMS verification requests now use an updated phone verification format
    and numeric phone-number IDs.
    * Code validation also uses numeric phone-number IDs, keeping the
    identifier format consistent across SMS authentication steps. The
    verification request and code-validation request now follow the same
    updated format.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    Co-authored-by: Corne Steenhuis <cornesteenhuis@hotmail.com>
  • gsa: GrandSlamMethod -> GrandSlamRequestContent (#415)
    ## What does this PR do?
    
    Renames GrandSlamMethod to GrandSlamRequestContent and adds support for
    json & PUT requests
    
    ## How was it tested?
    
    - [x] Still compiles
    - [x] `xtool auth`
    
    ## AI tool usage
    
    How much of the code in this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI in writing code. I reviewed the
    finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Requests can now send either JSON or XML bodies using POST or PUT. GET
    requests are sent without a body.
    * **Improvements**
    * Authentication and verification requests now use a consistent request
    format while retaining their existing behavior.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • signer: replace zsign with RCodesign (#389)
    ## What does this PR do?
    
    Replaces zsign with the
    [apple-codesign](https://github.com/indygreg/apple-platform-rs/tree/0ebbd2ef3b96d5adccc30b1cad4deea38cd735c4/apple-codesign)
    Rust crate. It's much more usable as a library (while zsign has bare
    prints all over the place + we had to fork it to maintain a public
    interface), it supports macOS-style bundles, and also has other features
    that we would've needed to further patch zsign for.
    
    ## How was it tested?
    
    - [x] Ran an ad-hoc signed app on the simulator (with a subsequent PR)
    - [x] Ran an app with App Groups on device, confirmed that the app group
    is present
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Updated Signing**
    * App signing now uses an updated signing engine and supports
    certificate-based and ad hoc signing.
    * Entitlement data, including data provided through entitlement files,
    is passed through during signing.
      * Deep signing is enabled in the development command.
    * **Validation**
    * Signing rejects invalid app or entitlement file paths and duplicate
    entitlement paths.
    * **Compatibility**
      * Signing contexts can be created without selecting a signer.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • ds: allow one app group on free teams (#284)
    ## What does this PR do?
    
    Resolves the FIXME in `DeveloperServicesCapability`: App Groups are no
    longer filtered out on free teams, and
    `DeveloperServicesAddAppOperation` assigns at most one group per app on
    a free team (the constraint the FIXME mentioned).
    
    Free teams can register and assign app groups through the Xcode
    DeveloperServices API (as AltStore/SideStore do), so a widget and its
    app can share `UserDefaults(suiteName:)` with a free Apple ID.
    
    ## How was it tested?
    
    Built on Linux from `main` (4d0c429). Installed a Flutter app with a
    WidgetKit extension on an iPhone (iOS 27.2) with a free Apple ID: the
    app gets `group.XTL-<team>.<id>` in its entitlements and the widget
    reads the app's data through it. `make lint` not run (no SwiftLint on
    this machine).
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
      * App Groups are now available to free teams.
    * Apps assigned to multiple App Groups are rejected with a clear error
    message.
    * Invalid app errors now include the app’s URL path, making it easier to
    identify which app could not be added.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • ds: fix assigning multiple app groups (#403)
    ## The bug
    
    `DeveloperServicesAssignAppGroupsOperation` sends one
    `assignApplicationGroupToAppId` request per app group, concurrently.
    That endpoint replaces the App ID's groups with the ones in the request,
    so an app whose entitlements list two or more groups ends up with only
    one of them on the App ID: whichever request Apple handles last. The
    provisioning profile then carries one group while the signed binary
    claims two, and the device refuses the install with
    `ApplicationVerificationFailed`.
    
    Setting the groups by hand in the developer portal does not help,
    because the next `xtool dev build --sign` sends the per-group requests
    again.
    
    ## The fix
    
    - `DeveloperServicesAssignAppGroupRequest` takes `groupIDs:
    [DeveloperServicesAppGroup.ID]` and sends `applicationGroups` as an
    array. The endpoint accepts an array; it was being given a single string
    before.
    - `DeveloperServicesAssignAppGroupsOperation.perform()` still finds or
    creates each group concurrently, then sends a single assign request
    carrying every group ID, and returns the group IDs in the order the
    entitlements listed them, so the rewritten entitlement is stable between
    builds.
    
    The only caller, `DeveloperServicesAddAppOperation`, is unchanged.
    
    ## Related
    
    #324 would report the resulting mismatch between the entitlements and
    the profile as a clear error at signing time; this change removes the
    cause, so an app with several groups gets a profile that grants all of
    them.
    
    ## Checked
    
    Built on Linux with the Swift 6.4 toolchain and used to sign an iOS app
    whose main target has two app groups and a Share extension. The app's
    `embedded.mobileprovision` now carries both groups and the device
    installs the app. The same app signed with the unpatched build got one
    group in the profile and `ApplicationVerificationFailed` on install.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    https://claude.ai/code/session_01UKzxgxaLwK7Ap5LMwj9MxH
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Apps can now be assigned multiple app groups in a single operation.
    The assignment replaces the app’s existing group associations with the
    selected groups, so any groups not included in the new assignment are no
    longer associated with the app.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: David Reinecke <david.reinecke@gmail.com>
    Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • utils: tweaks (#387)
    ## What does this PR do?
    
    Small utils refactors to make subsequent PRs cleaner.
    
    - Minor tweaks to how we determine / handle the xtool build version
    - Factor out TemporaryDirectory cache location
    
    ## How was it tested?
    
    - [x] Cache directory logic is unchanged
    - [x] Checked `xtool --version`
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Version information now distinguishes clean builds from builds with
    uncommitted changes, displaying a `-dirty` suffix when applicable.
    * Nonblocking file-locking operations are now available to external
    consumers.
    * **Changes**
    * Version reporting uses `XTOOL_VERSION` when set; otherwise, it uses
    available Git tag or commit information.
    * Temporary files now use the tool’s cache directory, honoring
    `XTL_TMPDIR` and platform cache settings. On Linux, the location follows
    `XDG_CACHE_HOME` when it is an absolute path; otherwise, the user cache
    directory is used.
      * `TMPDIR` is no longer used to select the temporary-file location.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • cli: better IO (#375)
    ## What does this PR do?
    
    Adds `Dependency`s for console IO. Also fixes #268 by explicitly
    handling non-pipe input.
    
    ## How was it tested?
    
    Ran on macOS + Linux in Docker. I have a future PR that overrides the
    dependencies in certain cases, which also appears to work correctly.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    Did not scrutinize the tests too much tbh but they look reasonable. Easy
    enough to update/remove them in the future if needed.
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Added support for reading command-line input, including asynchronous
    prompts, line-based responses, and graceful handling of end-of-input.
      * Added coordinated cleanup when commands finish.
    * **Improvements**
    * Command messages across authentication, device management,
    installation, SDK, and other workflows now use consistent output
    handling; displayed messages and command behavior remain unchanged.
    * Improved handling of complete output when the system temporarily
    cannot accept more data.
    * **Tests**
    * Added coverage for command input and output, including stream
    behavior, cancellation, and shutdown.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • gsa: update user agent (#406)
    ## What does this PR do?
    
    Fixes the latest source of HTTP 503s during Apple ID login
    
    ## How was it tested?
    
    - [x] `xtool auth login`
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Improvements**
    * Requests now use the user-agent value provided with device
    information, rather than a fixed value.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • entitlements: support Sign in with Apple (#377)
    ## What does this PR do?
    
    Hi! xtool didn't know about the `com.apple.developer.applesignin`
    entitlement, so every time I installed my app, the capability sync
    removed Sign in with Apple from the App ID. The new provisioning profile
    didn't have it, and the device refused the app ("A valid provisioning
    profile for this executable was not found").
    
    This maps the entitlement to the `APPLE_ID_AUTH` capability, as a
    primary App ID, so the capability stays and the profile includes it.
    
    ## How was it tested?
    
    I built xtool from this branch on macOS and installed my app (a SwiftPM
    app with `entitlementsPath` and the Sign in with Apple entitlement) on
    an iPhone with a paid team. The App ID kept the capability, the install
    worked, and native Sign in with Apple works in the app. Before the
    change the same install failed every time.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
      * Added support for configuring the Apple Sign In entitlement.
    * Apple Sign In is now available as a supported entitlement type,
    including serialization and configuration support.
      * Configures Apple ID app consent as the primary app consent option.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • gsa: use macOS Omnisette (#380)
    ## What does this PR do?
    
    Fixes #378, solving error -80046. Seems Apple now blocks
    Android-identifying clients from using gsa (at least the 2fa bits). Use
    Mac Anisette instead. cf
    https://github.com/nab138/iloader/commit/902597564315442f00ab299dcc758ee0e13b8fa8
    
    Alas, this does mean we have to retire xadi and switch back to
    Omnisette.
    
    ## How was it tested?
    
    - [x] Ran locally on macOS
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
      * Two-factor requests now use the device’s configured User-Agent.
    * **Updates**
    * Anisette requests now use the Omnisette provider and a new default
    server. The optional server URL override remains available.
    * The password-login warning now explains the virtual Mac and
    third-party service involved, clarifies that credentials are sent only
    to Apple, and recommends considering a secondary Apple ID.
    * **Behavior Changes**
      * The Android-specific ADI provider is no longer available.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • gsa: fall back to a trusted-device code when SMS 2FA fails (#349)
    `secondaryAuth` can't finish logging in: GSA rejects POST
    /auth/verify/phone/put?mode=sms with HTTP 403 ("Could not connect to
    iCloud"). The JSON flavor of those endpoints (GET /auth, PUT
    /auth/verify/phone/) answers 401 as well.
    
    GsService2/validate still accepts a code generated on a trusted device
    (Settings > Sign-In & Security > Get Verification Code), so ask for one
    instead of failing. Tested on WSL (Ubuntu 26.04) with an iOS 27 device.
    
    ## What does this PR do?
    
    <!-- Focus on *what* the PR does in essence. The *how* should come
    second, if it's relevant. -->
    
    ## How was it tested?
    
    <!-- e.g. Ran locally on macOS and added unit tests -->
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Two-factor authentication can fall back to a trusted device when an
    SMS request returns HTTP 403.
    * If trusted-device authentication also returns HTTP 403, users receive
    instructions for manually generating a verification code, and code
    validation can continue. Other errors are still reported.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • auth: clean up Anisette handling (#367)
    ## What does this PR do?
    
    Simplifies the Anisette handling code. Removes some unnecessary fields
    (including hard-coded Mac serial numbers, cf #348) from our http
    requests amongst other things.
    
    ## How was it tested?
    
    Tested on macOS:
    
    - [x] Can log in after resetting provisioning
    - [x] Can run `xtool ds teams`
    - [x] Can run `xtool ds certificates`
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    ## Summary by CodeRabbit
    
    * **Updates**
    * Anisette-backed requests now use device information provided with
    Anisette data, including an Android 14 device identity.
    * Developer service requests no longer include client-information and
    device-ID headers.
    * The Anisette command now displays fetched details in sorted order and
    uses the configured Anisette provider instead of prompting for
    provisioning data.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • certs: match certificates whose serial starts with 0 (#306)
    ## What does this PR do?
    
    Fixes #305 (thanks @0x53 for the detailed diagnosis).
    
    `Certificate.serialNumber()` stripped leading `0` *characters* from the
    hex string, so a serial like `0337…` became `337…` and never matched the
    `serialNumber` returned by the API. xtool then treated its stored
    certificate as missing: free teams are prompted to revoke a valid
    certificate on every run, and paid teams take the same path and try to
    create another certificate, which @McoreD confirmed fails with `409 …
    You already have a current Development certificate or a pending
    certificate request.`
    
    The character-level `drop` predates b6a40ca, which switched to
    fixed-width `%02hhX` formatting; since then it removes the significant
    zero of any first byte below `0x10`. swift-certificates already strips
    leading zero bytes (including DER sign padding) when parsing, so the
    `drop` isn't needed for padding.
    
    - `serialNumber()` now drops only leading zero *bytes*, matching the API
    and `openssl x509 -serial`.
    - The stored-certificate lookup uses a new
    `Certificate.hasSerialNumber(_:)`, which compares serials numerically
    (ignoring case and leading zeros), so it doesn't depend on Apple's exact
    formatting.
    - Profile creation no longer finds the signing certificate with
    `filter[serialNumber]`. The API lists serials with the leading zero
    (`0E41…`), but the filter only matches them without it (`E41…`), so once
    `serialNumber()` kept the zero the filter matched nothing and profile
    creation failed with a 409
    ([details](https://github.com/xtool-org/xtool/pull/306#issuecomment-6181157860)).
    It now fetches all pages of certificates and matches them with
    `hasSerialNumber(_:)`. Fix by @McoreD, with pagination added so a
    certificate on a later page isn't missed.
    
    ## How was it tested?
    
    - Added `Tests/XToolTests/CertificateTests.swift`: builds a cert with
    the serial from #305, round-trips it through DER, and checks
    `serialNumber()` and `hasSerialNumber(_:)`, plus a high-bit serial so
    DER sign padding doesn't leak into the string. The leading-zero test
    fails on `main` (returns `33742570D6F9684A23311711956A084`) and passes
    with this change.
    - `swift test` (17 tests) and `make lint` pass on macOS (Xcode 27, Swift
    6.4, `XTL_CI=1`).
    - @McoreD tested on Linux (Arch, Swift 6.4.1, Xcode 27 SDK, on top of
    1.22.2) with a paid team whose certificate serial starts with `0`: the
    stored certificate is reused, and provisioning, signing and install to
    an iPhone (iOS 27) succeed end to end (tested before pagination was
    added). `CertificateTests` also pass on Linux.
    - Not tested with a free team.
    
    To generate test certificates, the test target now depends on `Crypto`
    and `X509`, which XKit already uses.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Certificate serial numbers now preserve leading zero nibbles while
    omitting unnecessary padding.
    * Certificate matching now ignores letter case and leading zeros, and
    profile lookups find matching certificates even when the API’s
    serial-number filter treats leading-zero values differently.
    * Certificate lookups continue to use the existing fallback when no
    matching certificate is found.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: McoreD <McoreD@users.noreply.github.com>
    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
  • http: add newer Apple Root CAs (#340)
    ## What does this PR do?
    
    Adds Apple G2 & G3 Root CAs to Linux trust root. Fixes #339.
    
    Apple now uses gsd.apple.com for part of the auth flow, which uses the
    G3 root.
    
    ## How was it tested?
    
    Tried to log in on Linux. Failed before, works after.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Improved TLS certificate trust on Linux by recognizing Apple Root CA,
    G2, and G3 certificates when verifying secure connections. Existing TLS
    validation behavior remains unchanged.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • gsa: fix login using mismatched client-info (#337)
    ## What does this PR do?
    
    Fixes #329 (error `-80045` on gsa login).
    
    We were sending client info generated from Android Anisette, but
    claiming to be a Mac. Seems like GrandSlam no longer likes this
    mismatch.
    
    ## How was it tested?
    
    - [x] Run `xtool ds certificates` after updating -> logged out,
    provisioning reset
    - [x] Re-auth works
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    ## Summary by CodeRabbit
    
    * **Updates**
    * Authentication now identifies the client as a Pixel 7 running Android
    14 rather than using a macOS-style client description.
    * The provider ID is now consistent across platforms. Existing
    provisioning may be reset if its saved provider ID differs.
    * Before password login, a notice explains the emulated device and
    environment, recommends considering a secondary Apple ID, and says
    credentials are sent only to Apple.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • deps: update swiftlint (#299)
    ## What does this PR do?
    
    Updates SwiftLint to v0.65.1
    
    ## How was it tested?
    
    CI
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    * **Chores**
    * Removed inactive legacy networking implementations; active app
    behavior and user-facing capabilities are unchanged.
    * Updated the configured SwiftLint version and improved selection of its
    Linux download for supported processor architectures. macOS continues to
    use its existing download.
    * No changes to app features or user workflows are included in this
    update.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • package: bump deps (#281)
    ## What does this PR do?
    
    Bumps pointfree deps and explicitly disables OpenCombine so that we
    don't have different dependency graphs when building xtool on Darwin vs
    Linux hosts.
    
    ## How was it tested?
    
    A bunch of `swift package resolve`s
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    * **Chores**
    * Updated supporting package versions and refreshed command-line and
    file-operation handling.
    * These changes do not add or remove app features or alter its public
    interface. No action is needed to continue using the app.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • anisette: use XADI on macOS (#259)
    ## What does this PR do?
    
    Updates macOS build to use the fully-local XADI as the Anisette provider
    instead of Omnisette. Includes a one-time migration, logging the user
    out on macOS when they upgrade.
    
    ## How was it tested?
    
    General:
    
    - [x] Log in and confirm that there's no calls to omnisette
    - [x] `xtool dev` works
    
    Migration:
    
    - [x] Log into Apple ID using an old version of xtool
    - [x] Migrate to new xtool, run `xtool dev build -s` and confirm it
    fails due to being logged out
    - [x] Log in on new xtool, confirm that we stay logged in
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    * **New Features**
    * Authentication now supports tracking the anisette provider used to
    create Xcode tokens.
    * Xcode authentication tokens are automatically cleared when the
    provider changes, helping ensure users sign in again with current
    credentials.
    * Anisette provider identification is supported across macOS and Linux.
    
    * **Bug Fixes**
    * Improved authentication error messages, including clearer messaging
    for logout-related errors.
    * Prevented outdated Xcode authentication tokens from being reused after
    a provider change.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • anisette: use XADI Swift Package (#258)
    ## What does this PR do?
    
    The xadi repo (https://github.com/xtool-org/xadi) now vends an
    artifactbundle for xadi, so we don't need to separately build/install it
    during the xtool build. This also makes it super easy to support macOS
    later (#259).
    
    ## How was it tested?
    
    - [x] Confirmed I can sign in (including provisioning)
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
  • gsa: fix auth again (#257)
    ## What does this PR do?
    
    Fixes HTTP 503 on GsService… again (followup from #244)
    
    Seems like GSA has stopped accepting `com.apple.dt.Xcode` in the client
    info string. Use `akd` instead.
    
    ## How was it tested?
    
    - [x] Logging in before failed
    - [x] Logging in after succeeds
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
  • http: add some debugging knobs (#249)
    ## What does this PR do?
    
    Improves support for HTTP MITM
    
    ## How was it tested?
    
    Did some MITMing on macOS + Linux via Docker, using Proxyman and
    mitmproxy. Didn't test `XTL_HTTP_PROXY` but Sol pinkie promises me that
    it's correct.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
  • gsa: disregard keepalive (#247)
    GSA seems to, sometimes, reject HTTP requests made on a kept-alive
    connection with a 503. This was either the real root cause, or a second
    root cause, for #243. Fix this by creating a new HTTP connection for
    each GSA request.
    
    See also: https://github.com/rileytestut/AltSign/pull/52 and
    https://github.com/rileytestut/AltSign/pull/47
    
    Speculatively fixes #232
  • http: throw on unsuccessful responses (#246)
    Ref #243. `makeRequest` never inspected `response.status`, so a non-2xx
    body was
    returned as if it had succeeded. Every caller decodes that body as a
    property list
    or JSON, so Apple answering with an HTML error page surfaced as:
    
    ```
    Error: DecodingError.dataCorrupted: Data was corrupted. Debug description: The given
    data was not a valid property list.. Underlying error: ... Encountered unknown tag
    html on line 1
    ```
    
    which says nothing about what actually happened. In the case that
    prompted #243, GSA
    was returning `503 Service Temporarily Unavailable` for the `apptokens`
    operation. It
    now reads:
    
    ```
    Error: POST https://gsa.apple.com/grandslam/GsService2 failed: HTTP 503 Service
    Unavailable (text/html): <html> <head><title>503 Service Temporarily
    Unavailable</title></head> ...
    ```
    
    ### Notes
    
    - The response body is only included in the message for `text/html` and
    `text/plain`,
    i.e. gateway error pages. Bodies from the auth endpoints can carry
    account details
    (the 2FA requests use `Accept: application/x-buddyml`), and errors get
    printed
    verbatim in places such as `DevCommand.swift:164`, so those are left
    out. It also
      avoids emitting replacement characters for binary bodies.
    - The non-2xx path does not report progress, so `XADIProvider`'s
    download no longer
      prints `100%` immediately before failing.
    - Body collection moved to `collect(_:onProgress:)` so there is a single
    exit point.
      Buffer pre-sizing and progress reporting are unchanged.
    - `SDKBuilder` already does this check by hand on `send()`, so it is
    unaffected.
    
    ### Behaviour changes worth a look
    
    - `XADIProvider` writes the Apple Music APK download straight to disk; a
    failed
      download now throws instead of leaving a corrupt `applemusic.apk`.
    - The 2FA requests (`GrandSlamTrustedDeviceRequest`,
    `GrandSlamSMSAuthRequest`,
    `GrandSlamValidateRequest`) use no-op decoders, so a rejected request is
    currently
    discarded and xtool goes on to prompt for a code that was never sent —
    the symptom
      in #235. Those now fail loudly.
    
    ### Two things I left alone
    
    - `GrandSlamTwoFactorAuthenticateOperation.swift:47` catches
    `GrandSlamOperationError`
    with code `-21669` to report an incorrect verification code, but that
    error is only
    thrown by `GrandSlamOperationDecoder`, which the validate requests do
    not use, so the
    catch is unreachable. With this change a wrong code surfaces as a raw
    HTTP error
    instead. Mapping the status back to `incorrectVerificationCode` seemed
    like your call,
      since I do not know what GSA returns for a bad code.
    - If any endpoint reports errors in a decodable body under a non-2xx
    status, this guard
    would pre-empt it. I could not find one — `developerservices2` returns
    HTTP 200 with
      `resultCode`/`userString` in the body, which is untouched here.
    
    Built and linted on Linux (`swift build --product xtool`, `make lint`: 0
    violations).
    
    ---------
    
    Co-authored-by: Robetronica <noreply@Robetronica.com>
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • gsa: update user agent to fix auth (#244)
    Fixes #243. GrandSlam seems to be rejecting clients that self-report as
    really old.
  • Add build --sign option (#224)
    Running `xtool dev build --sign [--ipa]` will produce a codesigned
    app/ipa, without installing
  • Fix capability creation via public API (#220)
    Ref #216: the public API seems to now complain when passing in
    `capabilities`, but the field is still required when using the private
    API.
    
    Co-authored-by: sfusco <slf@stevenfusco.com>
  • Bump deployment to macOS 13, iOS 16 (#173)
    Necessary in order to pull in swift-subprocess
  • Clean up some entitlements code (#165)
    - We already do the `isFree` declarations in
    `DeveloperServiceCapability.swift`. Don't need to duplicate it on the
    entitlement types.
    - Add support for creating capabilities with raw names
  • Improve tmpdir management (#147)
    - We now have a single tmpdir "root" that can be recreated at launch to
    clean up old stragglers
    - The location of this tmpdir root can be controlled by `XTL_TMPDIR` or
    `TMPDIR`. In general the path is `$TMPDIR/sh.xtool`.
    
    With this change it should be possible to `export XTL_TMPDIR=/var/tmp`
    if `/tmp` doesn't have enough space, which fixes #23.
  • Update PublicStaging path (#132)
    Previously this was shadowing the user's cwd path onto the device, e.g.
    if you were installing from `/home/foo/bar` it would create
    `/home/foo/bar/PublicStaging` on the device. For uniformity with other
    tools like `ideviceinstaller`, clean this up and just create
    `/PublicStaging`.
  • Add support for App Extensions (#97)
    This pull request adds support for Apple's [App
    Extensions](https://developer.apple.com/app-extensions/).
    
    Closes: #45
    
    ---------
    
    Co-authored-by: ErrorErrorError <16653389+ErrorErrorError@users.noreply.github.com>
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • fix: resolve ASC invalid bearer token (#129)
    This pull request resolves potential clock drift between ASC and bearer
    token.
    
    Ref: https://github.com/xtool-org/xtool/pull/97#discussion_r2203584344
  • Drop the "Logging in" stage (#99)
    This was vestigial, and besides the fact that we don't actually do any
    work in this stage, we also never update the progress here so it gets
    stuck at 0%.
  • Make ALL response enums extensible (#92)
    Another bugfix for #64. Fifth… time's the charm?
  • FetchDeviceOperation: account for pagination (#91)
    - Always try to register the device and just return gracefully if it
    fails (prevents races, pagination issues)
    - Allow `SigningContext` (and therefore `AutoSigner`) to be created
    without a target device: just skip device registration.
  • DeveloperServices: support paginated requests (#89)
    Realized that we need to paginate due to #87
  • Fix duplicate App ID registration (#90)
    We pull the list of App IDs in `upsertApp` to see if we already created
    the App ID. This broke when the user had too many App IDs, since DS
    returned a paginated response and we only looked at the first page.
    
    We could paginate the whole list (as seen in #89) but that's slow.
    Instead, just filter the query to the expected App ID. This is a prefix
    search so we might still receive multiple responses, but it's much less
    likely that the user has like 10(? I'm not sure what the default/max
    pagination limit is) apps registered with the XTL-ABC.com.foo.bar prefix
    (they could have PlugIns but usually just a small number of them.)
    
    This is a tentative fix for #87.