Skip to main content

ipa-forge

License: GPL v3 repo

A generic, data-driven iOS IPA patcher framework: extract a user-supplied .ipa, apply version-aware patches from external YAML definitions (binary byte patches, resource replacement, dylib injection), and re-sign the result into a standard-structure .ipa that AltStore Classic can install and refresh on a real iPhone.

Documentation

Full documentation is published at ipa-forge.nandan.fyi. Source lives in docs/ (a Fumadocs site, see docs/content/docs/ for the raw .mdx).

Doc What it covers
Installation pip/pipx install ipa-forge and a 1-minute smoke test
Adding a new app Port a new app end-to-end (the "give me an IPA" playbook)
Adding a feature Add a feature to a hook dylib (conventions)
Usage End-to-end workflow, full CLI + GUI reference, signing identity/profile setup
Patch reference Complete patch-definition reference — every operation type, field, matching rule, and the hooks: block
Troubleshooting Every error message mapped to its cause and fix
Reverse engineering forge analysis: class-dump, strings, symbols, security posture, version diffing for any IPA
Architecture Design rationale, the 17-stage pipeline, hard constraints, hook verification (for developers)
Extensibility How to add new patch operation types (for developers)
AltStore device testing Manual AltStore Classic device-test checklist

Patch sets

ipa-forge ships no patch definitions or reverse-engineered specifics for any third-party app — it's the engine, not a collection of mods. An app-specific patch set (YAML definition + optional hook dylib) lives under a patches/<app>/ directory you provide yourself; see Adding a new app for the shape one follows and how the engine discovers it.

Install

pipx install ipa-forge     # recommended: isolated, globally available `forge` command
# or: pip install ipa-forge   (inside your own virtualenv)

This installs the forge CLI — forge --version to confirm. Full install options (uv, editable/source installs for development) and requirements are at Installation.

Requirements

  • Python 3.11+
  • macOS with Xcode Command Line Tools (xcode-select -p should print a path) -- only for signing: real (non-dry-run) forge patch shells out to Apple's codesign/security tools, never reimplementing them. forge inspect/validate/patch --dry-run/analysis all work on Linux; see extensibility.
  • For real signing: a codesigning identity in your Keychain (security find-identity -v -p codesigning) and a matching .mobileprovision, obtained the normal way through Xcode or AltServer's own account pairing.

Quick start (novice — the GUI)

  1. Launch the GUI: forge gui → open http://127.0.0.1:8765
  2. Drop your .ipa into the box. The GUI detects the app and the matching patch set, shows a small warning if the patch set targets a different version (patching is still allowed — hook verification is the safety net), and presents one Patch button.
  3. Download the patched IPA (unsigned — ready for AltStore).

No YAML editing, no signing identity, no provisioning profiles needed for the AltStore path.

Quickstart (CLI)

# Inspect an IPA's bundle id, version, and executable inventory
forge inspect path/to/App.ipa

# Validate structure without touching anything
forge validate path/to/App.ipa

# Dry-run a patch definition (no mutation, no signing; --identity/--profile optional)
forge patch --ipa path/to/App.ipa --patches patches.yaml \
  --output patched.ipa --dry-run

# Patch and re-sign for real
forge patch --ipa path/to/App.ipa --patches patches.yaml \
  --identity "Apple Development" --profile path/to/profile.mobileprovision \
  --output patched.ipa --verbose

# Launch the local web GUI (wraps the same pipeline)
forge gui

--identity accepts either a full SHA-1 hash or a unique substring of the identity's name (as shown by security find-identity -v -p codesigning) -- it fails loudly, listing candidates, if the substring is ambiguous or matches nothing.

Try it against the repo's synthetic test fixture — no real app required, and no need to clone the repo, just download the files:

curl -LO https://raw.githubusercontent.com/nandan-varma/ipa-forge/main/fixtures/synthetic_app.ipa
curl -LO https://raw.githubusercontent.com/nandan-varma/ipa-forge/main/fixtures/patches/example.yaml
mkdir -p assets && curl -Lo assets/patched_asset.txt \
  https://raw.githubusercontent.com/nandan-varma/ipa-forge/main/fixtures/patches/assets/patched_asset.txt

forge patch --ipa synthetic_app.ipa --patches example.yaml \
  --identity "Apple Development" --profile <your .mobileprovision> \
  --output /tmp/patched.ipa --verbose

Writing a patch definition

target:
  bundle_id: "com.example.synthetic"
  version:
    exact: "1.0.0"          # or: { min: "1.0.0", max: "2.0.0" }

patches:
  - id: "zero-marker-bytes"
    type: binary_replace
    executable: "TestApp"
    arch: "arm64"             # required for fat/universal binaries
    pattern: "ca fe f0 0d"    # space-separated hex, ?? = wildcard byte
    replacement: "00 00 00 00"
    expected_matches: 1        # fails loudly on 0 or >1 matches

  - id: "swap-asset"
    type: resource_replace     # also: resource_add, resource_remove
    path: "asset.txt"           # bundle-relative
    source: "assets/patched_asset.txt"  # relative to the patch definition file

  - id: "inject-hook"
    type: dylib_inject
    executable: "TestApp"
    arch: "arm64"
    install_name: "@rpath/libInjectable.dylib"   # the dylib must already be in the bundle
    load_command: "LC_LOAD_DYLIB"                 # or LC_LOAD_WEAK_DYLIB

  - id: "set-verbose-logging"
    type: plist_edit
    action: "set"                  # or "remove" (no value needed)
    key: "CFBundleDisplayName"
    value: "Patched App"
    path: "Info.plist"             # bundle-relative; defaults to Info.plist

See fixtures/patches/example.yaml and fixtures/patches/example_dylib_inject.yaml for complete, working examples run by the test suite against fixtures/synthetic_app.ipa.

This is a teaser. The full reference — every operation type (binary_replace, resource_replace/add/remove, dylib_inject, plist_edit), every field, version-matching semantics, the dry-run gate, ordering rules, and a common-mistakes checklist — is in patch-reference.

Getting certificates and provisioning profiles for AltStore

AltStore Classic re-signs and installs apps using your own Apple ID's development credentials, obtained through AltServer's own pairing flow -- ipa-forge doesn't manage your Apple account. How to find your codesigning identity and a matching .mobileprovision, and how ipa-forge selects profiles (exact match, wildcard, per-extension), is documented in usage. In short:

  1. Pair AltServer with your device (or use a certificate + profile from your own Xcode account).
  2. Locate your identity: security find-identity -v -p codesigning.
  3. Locate a profile under ~/Library/MobileDevice/Provisioning Profiles/*.mobileprovision.
  4. The profile's application-identifier must authorize the IPA's bundle id (exactly or via a wildcard TEAMID.* profile).

forge patch validates the profile up front (expiry, bundle-id match) and fails with an actionable error before touching your IPA if it doesn't qualify.

Testing

pytest tests/                    # everything, including real codesign signing
pytest tests/ -m "not macos"     # skip real-signing tests (e.g. on Linux)

fixtures/synthetic_app.ipa is a real, from-scratch iOS app built via scripts/rebuild_fixture.sh against the iOS SDK -- a main executable, a linked framework, an unlinked standalone dylib (the dylib-injection target), and a resource file. It's checked in as a binary artifact; re-run the script only if you need to change its shape.

Real-device AltStore Classic install/launch/refresh cannot be automated in this environment -- see altstore-device-testing for the manual checklist.

Disclaimer

For educational and research purposes only. You must supply your own legally obtained .ipa. ipa-forge is not affiliated with, endorsed by, or sponsored by Apple or by the vendor of any application you choose to patch. It distributes no copyrighted app binaries and no reverse-engineered specifics of any third-party app — only a generic patching engine that operates on YAML you supply. Patching or sideloading an app may violate that app's Terms of Service; that risk is yours to assess.

License

GPLv3-or-later. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ipa_forge-0.1.1.tar.gz (122.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ipa_forge-0.1.1-py3-none-any.whl (110.0 kB view details)

Uploaded Python 3

File details

Details for the file ipa_forge-0.1.1.tar.gz.

File metadata

  • Download URL: ipa_forge-0.1.1.tar.gz
  • Upload date:
  • Size: 122.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for ipa_forge-0.1.1.tar.gz
Algorithm Hash digest
SHA256 053103d423463fb005e27baf76df19fdafbf1985f11dca3c82a2113f4bdddddb
MD5 cffaa2f85b1474aa12b1d0fce737c170
BLAKE2b-256 bf37eac5209aadf6f24c2722aaf8b7852a29e095cdd90a36edee94d17b3f5b77

See more details on using hashes here.

File details

Details for the file ipa_forge-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: ipa_forge-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 110.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for ipa_forge-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 86e9c6366b6a337d08a6e61916ae73517f21268ae191bc2fdbb18df4948d3a5f
MD5 db22ece14af031283f5a7014d337b602
BLAKE2b-256 96c47803905dd12757ca3123f55a92f7c15cbd47343104e311ff66261a9d0410

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page