npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ipa-resign

v0.0.3

Published

Resign iOS `.ipa` files with a **free Apple ID** so they install on a specific iPhone or iPad. Same trick AltStore / SideStore / Sideloadly use, but as a Node.js library you can drive from any tool and the shared, transport-agnostic **protocol core** behi

Readme

ipa-resign

Resign iOS .ipa files with a free Apple ID so they install on a specific iPhone or iPad. Same trick AltStore / SideStore / Sideloadly use, but as a Node.js library you can drive from any tool and the shared, transport-agnostic protocol core behind the browser app in the sibling apple-resign-web repo.

Runs on macOS, Linux, Windows and web.

What this is

If you download an .ipa (an EAS internal-distribution build, a TestFlight export, anything ad-hoc-signed), iOS will reject the install on your phone unless the IPA's provisioning profile lists your device's UDID. The fix is to re-sign the IPA with a development cert + profile that does include your device.

Apple gives every Apple ID a free 7-day development certificate for exactly this purpose. They just don't make it easy to use without Xcode. This library implements the protocol Xcode uses internally so any tool can resign IPAs without shelling out to Xcode — and without even running on a Mac.

Status

macOS, Linux, and Windows; free Apple ID; main .app only. See CLAUDE.md for architectural detail.

| Capability | Status | | ----------------------------------------- | --------------------------------------------------------- | | Sign in with Apple ID + password | ✅ | | Two-factor auth (trusted device + SMS) | ✅ | | Mint / revoke development certificates | ✅ | | Register a device for signing | ✅ | | Create / delete an App ID | ✅ | | Download a provisioning profile | ✅ | | Resign main .app (zsign, inside-out) | ✅ | | Repack into an installable .ipa | ✅ | | macOS / Linux / Windows | ✅ x64 + arm64 | | App extensions (.appex, WatchKit) | ❌ Free-account limit. --strip-extensions removes them. |

How it stays cross-platform

The same code runs on all three desktop OSes — no macOS-only tooling:

| Job | How | | --------------------------- | ------------------------------------------------------------------------- | | Code signing | zsign — C++ binary in bin/ | | Unzip / repack IPA | pure-Node yauzl + yazl (preserves modes + symlinks) | | Decode .mobileprovision | pure-Node PKCS#7 plist extraction | | Secret storage | 0600 JSON file at ~/.orbit/apple-resign/secrets.json | | Anisette (OTP) headers | Swift helper on macOS; Rust helper + Apple Music for Android libs elsewhere |

Architecture — one protocol core, two consumers

The Apple GrandSlam + developer-portal protocol lives here once and is shared by two front-ends: this Node CLI/library, and the browser app in the sibling apple-resign-web repo (which also esbuild-bundles into a native iOS WKWebView). A protocol fix lands in one place and both consumers get it.

The protocol is transport-agnostic — it depends only on a small injected Platform port (HTTP + anisette), never on fetch/node:/fs directly. Crypto goes through node-forge (sync + isomorphic). Each consumer wires its own adapter:

| | Node CLI (this repo) | Browser app (apple-resign-web) | | -------- | -------------------------------------------- | -------------------------------------------- | | HTTP | appleFetch + system-ca (Apple private roots) | fetch via the /api/apple-proxy CORS shim | | Crypto | node-forge | node-forge | | Anisette | native helper binary (bin/anisette*) | ipa-resign/anisette-browser (in-browser WASM) | | Signer | zsign binary (src/sign/codesign.ts) | ipa-resign/sign-browser (zsign-wasm) | | Storage | 0600 JSON secret file + state.json | localStorage |

Subpath exports

  • ipa-resign — the Node library/CLI surface (the Usage section below).
  • ipa-resign/core — the pure, transport-agnostic protocol (gsa, srp, devPortal, csr, errors, cert roots, entitlements) + the Platform port. ESM, no Node builtins; the browser app imports this and supplies a browser adapter.
  • ipa-resign/anisette-browser — the in-browser anisette generator (device attestation via WASM). Browser-only; consumed by the web app + its native WKWebView bundle.
  • ipa-resign/sign-browser — browser-side IPA re-signing via zsign compiled to WASM (the zsign-wasm package; its glue is served same-origin by the host app). Browser-only; the web counterpart to the Node zsign binary path.
  • ipa-resign/assets/anisette — the vendored anisette-js
    • Unicorn WASM the browser generator fetches at runtime (source of truth; the web app syncs these into its public/).

Internally the Node protocol modules (src/auth/gsa.ts, src/devPortal.ts, src/auth/srp.ts, src/sign/csr.ts) are thin adapters that wrap core with the Node Platform (src/platform.ts) and re-wrap bytes as Buffer for the CLI. Full design: TRANSPORT-AGNOSTIC-PLAN.md and ANISETTE-MIGRATION-PLAN.md.

Install

yarn install
yarn build   # dist/: bytecode CLI bundle (index.jsc) + the ipa-resign/core and
             # ipa-resign/anisette-browser subpath builds + bundled .d.ts

yarn build is platform-agnostic — it just bundles TypeScript (via esbuild). It runs three steps: the bytecode CLI build, then build:core and build:anisette-browser (the two browser-consumable subpaths, which you can also run on their own). The native helper binaries are built separately because they require platform-specific toolchains:

# Build the macOS anisette helper (only works on macOS — needs swiftc, i.e. Xcode CLT).
# Produces bin/anisette as a universal arm64 + x86_64 Mach-O.
yarn build:helper:macos

# Cross-compile the Rust anisette helper for Windows + Linux from any host.
# Produces bin/anisette-{linux,win}-{x64,arm64}{.exe}. Requires zig + cargo-zigbuild
# for the Linux targets and cross + Docker for the Windows targets. See
# anisette/orbit-anisette-rs/README.md for setup.
yarn build:helper:rust

End users installing the package via npm don't run any of the helper scripts — the published package ships with the per-platform binaries already built (bin/anisette* for OTP headers, bin/zsign* for code signing). src/auth/anisette.ts and src/sign/codesign.ts pick the right binary at runtime by inspecting process.platform + process.arch.

On Linux and Windows, the anisette helper also needs Apple Music for Android's libstoreservicescore.so. The library fetches Apple's universal Apple Music APK on first resign and extracts the native libraries into your user data dir automatically — no manual setup. macOS links Apple's AOSKit.framework natively and skips this.

Usage

import {
  signInAsync,
  submitTwoFactorCodeAsync,
  resignIpaAsync,
  AppleTwoFactorRequiredError,
} from "ipa-resign";

// First-time sign-in. Apple pushes a 6-digit code to a trusted device or SMS.
try {
  await signInAsync({ appleId: "[email protected]", password: "…" });
} catch (err) {
  if (err instanceof AppleTwoFactorRequiredError) {
    const code = await promptUserFor2FACode(err.details);
    await submitTwoFactorCodeAsync({
      appleId: "[email protected]",
      password: "…",
      code,
    });
  } else {
    throw err;
  }
}
// The session is now persisted in the local secret file; future calls
// (resignIpaAsync, etc.) work without re-auth until the token expires.

const result = await resignIpaAsync({
  ipaPath: "/path/to/build.ipa",
  deviceUdid: "00008130-XXXXXXXXXXXXXXXX",
  deviceName: "Gabriel's iPhone",
  appleId: "[email protected]",
  outputIpaPath: "/path/to/resigned.ipa",
  stripExtensions: true, // see "Caveats"
  onProgress: (step, detail) => console.log(step, detail),
});

console.log(result.resignedIpaPath); // → /path/to/resigned.ipa
console.log(result.bundleId); // → the (possibly rewritten) bundle id
console.log(result.profileExpiresAt); // → Date, 7 days out

Install on the device using your favorite usbmuxd / installation_proxy client (libimobiledevice's ideviceinstaller, xcrun devicectl, Orbit's built-in usbmux stack, …). The library only resigns — it does not install.

Other exported helpers

import {
  signOutAsync,             // wipe the persisted session for an Apple ID
  listAppIdsForAccountAsync, // list App IDs on the primary team
  deleteAppIdForAccountAsync, // free a slot against the 10-per-7-days cap
} from "ipa-resign";

Each pipeline step is also exported for power use — see src/devPortal.ts, src/sign/*, and src/auth/*.

How the free-Apple-ID flow works

  1. Sign in (Grand Slam Authentication). SRP-6a (Apple-flavored) + anisette over gsa.apple.com/grandslam/GsService2. On success, Apple returns an encrypted SPD blob that decrypts to { adsid, GsIdmsToken, sk, c, … }.
  2. Two-factor auth. Trusted-device path validates via GET /grandslam/GsService2/validate; SMS path uses POST /auth/verify/phone/securitycode. After validate, the next sign-in completes without challenge.
  3. Mint per-service token. Another /grandslam/GsService2 POST with o: apptokens returns an AES-256-GCM et payload (with a "XYZ" magic prefix). Decrypt with sk to get the token for com.apple.gs.xcode.auth. This token is X-Apple-GS-Token for all dev-portal calls.
  4. Mint development cert. Generate a 2048-bit RSA keypair, PKCS#10 CSR, POST it to services/QH65B2/ios/submitDevelopmentCSR.action. Apple returns a certificateId (cert bytes come from a separate services/v1/certificates query). Store the cert + key in the local secret file; zsign reads the PEMs directly at sign time.
  5. Register device. addDevice.action with the iPhone UDID. Idempotent.
  6. Create / reuse App ID. Bundle ids you don't own (host.exp.Exponent, com.facebook.*) are reserved globally; rewrite with a .orbit<8hex> suffix derived from (teamId, originalBundleId) and remember the mapping in ~/.orbit/apple-resign/state.json.
  7. Download provisioning profile. downloadTeamProvisioningProfile.action returns a .mobileprovision listing your cert + your device UDID + the App ID.
  8. Resign. Unzip (pure-Node) → strip non-allowlisted entitlements → hand the cert PEM, key PEM, profile, and entitlements to zsign, which recurses into Frameworks/, PlugIns/, etc. and signs each Mach-O inside-out.
  9. Repack. Zip Payload/ back into a .ipa (pure-Node, preserving Unix modes + symlinks). Done.

The full HTTP exchange is in src/auth/gsa.ts and src/devPortal.ts. The signing recipe is in src/sign/codesign.ts.

Caveats

  • No app extensions. Apple's free-account cap of 10 App IDs per 7-day window means apps with .appex / WatchKit targets aren't viable to resign with the same Apple ID more than once or twice a week. stripExtensions: true removes PlugIns/*.appex and Watch/* before signing — the main app loses push, share extensions, widgets, etc., but installs and runs.
  • Bundle id gets rewritten. Reserved bundle ids (host.exp.Exponent, com.example.something-style ids someone else already registered) get a .orbit<8hex> suffix. The original is in state.json::appIdMap and reused on subsequent resigns. Apps that read their own Bundle.main.bundleIdentifier for analytics / deep links will see the new id.
  • Entitlements are stripped. Anything outside the free-account allowlist (application-identifier, keychain-access-groups, com.apple.developer.team-identifier, get-task-allow, com.apple.security.application-groups) is removed. Apps that require push / iCloud / HealthKit / etc. will install but crash on launch — this is a fundamental free-account limitation. See src/sign/entitlements.ts.
  • 7-day expiry. The provisioning profile dies after 7 days. Re-run resignIpaAsync — it reuses the cached cert and App ID, just downloads a fresh profile and re-signs (under 10 seconds for cached state).
  • First-install trust prompt. iOS requires the user to tap "Trust" once per Apple ID in Settings → General → VPN & Device Management. Xcode bypasses this via the misagent service on the device; this library doesn't talk to misagent yet, so the prompt is unavoidable today.
  • Apple Music APK download (Linux / Windows). Anisette on non-macOS hosts requires Apple Music for Android's libstoreservicescore.so, fetched from Apple's CDN on first resign. The host needs outbound network access to apps.mzstatic.com once; the libraries are cached in the user data dir thereafter.

Errors

All errors extend a binary-compatible InternalError class (same shape as Expo's common-types). Discriminate via error.code:

| Code | When | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | APPLE_AUTH_REQUIRED | No persisted session for this Apple ID. Run signInAsync(). | | APPLE_BAD_CREDENTIALS | Apple rejected the password or 2FA code. | | APPLE_TWO_FACTOR_REQUIRED | error.details has { authMode: 'trustedDevice' \| 'sms', … }. Apple pushed a code; call submitTwoFactorCodeAsync(). | | APPLE_RESIGN_UNSUPPORTED_IPA | error.details.reason is extensions / watchapp / multiple-apps. Set stripExtensions: true to remove the offender. | | APPLE_RESIGN_QUOTA_EXCEEDED | Hit the 10-App-IDs-per-7-days cap. Wait or delete older entries with deleteAppIdForAccountAsync() / via developer.apple.com. | | APPLE_RESIGN_FAILED | Catch-all for anything else (network errors, zsign failures, …). Run with DEBUG=expo:apple-resign* for protocol traces. |

Local storage

  • 0600 JSON file at ~/.orbit/apple-resign/secrets.json (no OS keyring — the Keychain was rejected because each pkg rebuild changes the binary's code-directory hash and macOS re-prompts; keys are prefixed expo.orbit.apple-resign.*) — GSA refresh token, cert private key (PEM), cert (PEM).
  • ~/.orbit/apple-resign/state.json — non-secret: teamId, appleId, appIdMap (original bundle id → assigned bundle id), certIds (per teamId), lastProfile. Safe to delete; will be regenerated on next resign.
  • <user data dir>/orbit/apple-resign/anisette/ssc (Linux / Windows only) — extracted Apple Music for Android SSC libraries, plus an apk-metadata.json recording the APK's etag / version so we can detect when Apple ships a new one. At most once every 24h a resign re-checks Apple's APK headers and re-extracts if it changed (best-effort — a failed check never blocks the resign). Safe to delete; re-downloaded on next resign. Override the location with ORBIT_ANISETTE_LIB (which also disables the auto-update check, since you manage that directory); force an immediate check with ORBIT_ANISETTE_FORCE_UPDATE_CHECK=1.

Credits

  • SideStore/apple-private-apis — the most modern, actively-maintained reference for Apple's auth protocol. The SRP-6a port is verified against their vendored rustcrypto-srp fork, and the Rust anisette helper builds on their omnisette.
  • rileytestut/AltSign — the original. Especially ALTAppleAPI+Authentication.m for the apptokens / AES-GCM dance.
  • Dadoum/Provision — anisette generator + the provision protocol details.
  • zhlynn/zsign — the cross-platform code signer used to re-sign the app bundle.