# How to Analyze the Entitlements Database Across Different Firmware Versions

> Analyze firmware entitlements across versions using ipsw ent to export to a database or ipsw diff to compare changes between builds. Gain insights into code signing evolution.

- Repository: [blacktop/ipsw](https://github.com/blacktop/ipsw)
- Tags: deep-dive
- Published: 2026-02-26

---

**Use the `ipsw ent` command to extract code-signing entitlements from IPSW files into a SQLite or PostgreSQL database, then query across versions or use `ipsw diff --ent` to compare entitlement changes between specific firmware builds.**

The `blacktop/ipsw` open-source tool provides a complete workflow to analyze the entitlements database across different firmware versions. By parsing Mach-O binaries inside IPSW files and normalizing entitlement data into a relational schema, security researchers can track how Apple’s code-signing permissions evolve between iOS releases.

## Extracting and Storing Entitlements

### Architecture

The extraction pipeline consists of four main components that transform raw IPSW data into a queryable database:

| Component | Role | Source File |
|---|---|---|
| **`ent` command** | Parses Mach-O binaries inside an IPSW, extracts normal or DER-encoded entitlements, and builds a map of `file → plist XML`. | [[`cmd/ipsw/cmd/ent.go`](https://github.com/blacktop/ipsw/blob/main/cmd/ipsw/cmd/ent.go)](https://github.com/blacktop/ipsw/blob/master/cmd/ipsw/cmd/ent.go) |
| **Entitlement parser** | Decodes the Apple DER wrapper when a binary only contains `EntitlementsDER` via the `DerDecode` function. | [[`internal/codesign/entitlements/entitlements.go`](https://github.com/blacktop/ipsw/blob/main/internal/codesign/entitlements/entitlements.go)](https://github.com/blacktop/ipsw/blob/master/internal/codesign/entitlements/entitlements.go) |
| **Database service** | Stores parsed entitlements in a relational schema that de-duplicates keys, values, and file paths for fast search. | [[`internal/commands/ent/database.go`](https://github.com/blacktop/ipsw/blob/main/internal/commands/ent/database.go)](https://github.com/blacktop/ipsw/blob/master/internal/commands/ent/database.go) |
| **Data model** | Defines tables `entitlement_keys`, `entitlement_values`, `paths`, `entitlements`, and `ipsws`. | [[`internal/model/ent.go`](https://github.com/blacktop/ipsw/blob/main/internal/model/ent.go)](https://github.com/blacktop/ipsw/blob/master/internal/model/ent.go) |

The extraction flow follows this sequence:

1. Open the IPSW and iterate over `AppOS`, `SystemOS`, and other partitions.
2. Open each Mach-O (fat or thin) and check for `CodeSignature`.
3. Read `.Entitlements` (plain XML) or `.EntitlementsDER` (DER-encoded).
4. Decode DER if needed using `DerDecode`.
5. Store the XML string in a map keyed by the relative file path.

After the map is built, the **DatabaseService** bulk-inserts data using `storeEntitlementsBulk`, which groups inserts into a single transaction per IPSW to populate:

- Unique **keys** (`entitlement_keys`)
- Unique **values** (`entitlement_values` with a short hash for deduplication)
- **Paths** (`paths`) representing the file within the IPSW
- **Entitlement rows** linking the above to a specific IPSW (`ipsws` table)

### CLI Example – Build a SQLite Database

Create a fresh SQLite database from multiple IPSW files:

```bash

# Create a database from one or more IPSWs (any number of builds)

ipsw ent \
    --sqlite entitlements.db \
    --ipsw iPhone16,1_18.2_22C150_Restore.ipsw \
    iPhone16,1_18.3_22D68_Restore.ipsw

```

- `--sqlite` specifies the local SQLite file path.
- `--ipsw` accepts a glob or list of IPSW paths; each file is processed sequentially.

For PostgreSQL deployments (useful for Supabase or shared analysis), replace `--sqlite` with `--pg-*` flags; the same relational schema applies.

## Querying the Entitlements Database

### Architecture

The search API resides in [`internal/commands/ent/operations.go`](https://github.com/blacktop/ipsw/blob/main/internal/commands/ent/operations.go). It constructs GORM queries based on the `EntitlementQuery` struct defined in [`internal/model/ent.go`](https://github.com/blacktop/ipsw/blob/main/internal/model/ent.go).

You can filter on:

| Field | Meaning |
|---|---|
| `Version` / `Build` | iOS version or build number of the IPSW |
| `Device` | Device identifier (e.g., `iPhone16,1`) |
| `KeyPattern` | SQL `LIKE` pattern for entitlement keys |
| `ValuePattern` | Pattern for the stringified value |
| `FilePath` | Relative file path inside the IPSW |

### CLI Examples

Search for specific entitlements across all imported firmware versions:

```bash

# Find every file that grants the "com.apple.private.set-launch-type.internal" key

ipsw ent \
    --sqlite entitlements.db \
    --key com.apple.private.set-launch-type.internal

```

Search for entitlement values containing specific strings:

```bash

# Search for all entitlements containing the string "LockdownMode"

ipsw ent \
    --sqlite entitlements.db \
    --value LockdownMode

```

Restrict searches to specific iOS versions:

```bash

# Show only entitlements from iOS 18.2 matching "sandbox"

ipsw ent \
    --sqlite entitlements.db \
    --version 18.2 \
    --key sandbox

```

Extract file paths for scripting:

```bash

# Output only file paths (useful for piping to other tools)

ipsw ent \
    --sqlite entitlements.db \
    --key platform-application \
    --file-only

```

The command outputs a table mapping **IPSW version → file → key → value**. For raw data processing, query the SQLite database directly:

```sql
SELECT i.version, p.path, k.key, v.value
FROM entitlements AS e
JOIN ipsws AS i   ON e.ipsw_id = i.id
JOIN paths AS p   ON e.path_id = p.id
JOIN entitlement_keys AS k ON e.key_id = k.id
JOIN entitlement_values AS v ON e.value_id = v.id
WHERE k.key LIKE '%sandbox%';

```

## Diffing Entitlements Between Firmware Versions

### Architecture

The diff engine lives in `pkg/diff`. When invoked with the `--ent` flag, the routine:

1. Extracts entitlements from both IPSWs using the same code path as the `ent` command.
2. Normalizes them into a map of `file → plistXML`.
3. Compares the two maps using generic diff utilities to generate a structured diff object.
4. Renders output as **Markdown**, **JSON**, or **HTML** via templates in [`pkg/diff/format.go`](https://github.com/blacktop/ipsw/blob/main/pkg/diff/format.go).

The relevant struct field is `Entitlements`, rendered under the "🔑 Entitlements" section in the final output.

### CLI Example – Visual Diff

Generate a Markdown diff between two iOS versions:

```bash

# Diff entitlements between iOS 18.2 and 18.3, output as Markdown

ipsw diff \
    iPhone16,1_18.2_22C150_Restore.ipsw \
    iPhone16,1_18.3_22D68_Restore.ipsw \
    --ent \
    --markdown \
    --output ./diffs

```

This produces a [`diff.md`](https://github.com/blacktop/ipsw/blob/main/diff.md) file containing a collapsible section with side-by-side entitlement comparisons and highlighted changes.

For quick console inspection, omit the output flags:

```bash
ipsw diff \
    old.ipsw new.ipsw \
    --ent

```

### Programmatic Diff (Go)

Embed entitlement comparison in a Go application using the public API:

```go
import (
    "github.com/blacktop/ipsw/pkg/diff"
)

cfg := &diff.Config{
    IpswOld:      "/path/to/old.ipsw",
    IpswNew:      "/path/to/new.ipsw",
    Entitlements: true, // enable entitlements diff
}
d := diff.New(cfg)

if err := d.Diff(); err != nil {
    // handle error
}
fmt.Println(d.String()) // console output
// Or d.Markdown()/d.ToJSON()/d.ToHTML() for rendered formats.

```

See [[`pkg/diff/diff.go`](https://github.com/blacktop/ipsw/blob/main/pkg/diff/diff.go)](https://github.com/blacktop/ipsw/blob/master/pkg/diff/diff.go) for the complete API implementation.

## Complete Workflow Example

```bash

# 0. Prepare a folder with the IPSWs you want to compare

mkdir -p ~/ipsws
cp *.ipsw ~/ipsws/

# 1. Build a SQLite DB containing all builds

ipsw ent --sqlite ~/ipsws/ent.db --ipsw ~/ipsws/*.ipsw

# 2. Quick search – "Which builds have the com.apple.private.set-launch-type.internal key?"

ipsw ent --sqlite ~/ipsws/ent.db --key com.apple.private.set-launch-type.internal

# 3. Diff two specific builds (e.g., 18.2 vs 18.3)

ipsw diff \
    ~/ipsws/iPhone16,1_18.2_22C150_Restore.ipsw \
    ~/ipsws/iPhone16,1_18.3_22D68_Restore.ipsw \
    --ent \
    --markdown \
    --output ~/ipsws/diffs

```

This workflow yields a searchable database (`ent.db`) spanning all imported firmware versions and a Markdown diff ([`diff.md`](https://github.com/blacktop/ipsw/blob/main/diff.md)) highlighting entitlement changes between specific releases.

## Key Source Files

| File | Description |
|---|---|
| [[`internal/commands/ent/ent.go`](https://github.com/blacktop/ipsw/blob/main/internal/commands/ent/ent.go)](https://github.com/blacktop/ipsw/blob/master/internal/commands/ent/ent.go) | High-level entry point for the `ent` command – orchestrates extraction and DB storage. |
| [[`internal/codesign/entitlements/entitlements.go`](https://github.com/blacktop/ipsw/blob/main/internal/codesign/entitlements/entitlements.go)](https://github.com/blacktop/ipsw/blob/master/internal/codesign/entitlements/entitlements.go) | Handles DER-encoded entitlements via `DerDecode`. |
| [[`internal/commands/ent/database.go`](https://github.com/blacktop/ipsw/blob/main/internal/commands/ent/database.go)](https://github.com/blacktop/ipsw/blob/master/internal/commands/ent/database.go) | Bulk insertion logic for keys, values, paths, and entitlements. |
| [[`internal/model/ent.go`](https://github.com/blacktop/ipsw/blob/main/internal/model/ent.go)](https://github.com/blacktop/ipsw/blob/master/internal/model/ent.go) | GORM data model defining tables and query structures. |
| [[`cmd/ipsw/cmd/ent.go`](https://github.com/blacktop/ipsw/blob/main/cmd/ipsw/cmd/ent.go)](https://github.com/blacktop/ipsw/blob/master/cmd/ipsw/cmd/ent.go) | CLI wiring – flag handling and user-facing command implementation. |
| [[`cmd/ipsw/cmd/diff.go`](https://github.com/blacktop/ipsw/blob/main/cmd/ipsw/cmd/diff.go)](https://github.com/blacktop/ipsw/blob/master/cmd/ipsw/cmd/diff.go) | CLI entry point for the diff command; enables `--ent` flag. |
| [[`pkg/diff/diff.go`](https://github.com/blacktop/ipsw/blob/main/pkg/diff/diff.go)](https://github.com/blacktop/ipsw/blob/master/pkg/diff/diff.go) | Core diff engine – builds diff objects for kernel, kexts, Mach-Os, and entitlements. |
| [[`pkg/diff/format.go`](https://github.com/blacktop/ipsw/blob/main/pkg/diff/format.go)](https://github.com/blacktop/ipsw/blob/master/pkg/diff/format.go) | Markdown/JSON/HTML rendering templates, including the “🔑 Entitlements” section. |

## Tips and Gotchas

- **DER vs. XML** – Some older binaries contain only `EntitlementsDER`. The `ent` command automatically falls back to `DerDecode` in [`internal/codesign/entitlements/entitlements.go`](https://github.com/blacktop/ipsw/blob/main/internal/codesign/entitlements/entitlements.go), so manual handling is unnecessary.

- **Large IPSW collections** – Use the bulk insert path (`storeEntitlementsBulk`) for speed; it groups inserts into a single transaction per IPSW rather than individual row commits.

- **Version filtering** – The `Version` field in the `ipsws` table is derived from the `BuildManifest` inside the IPSW. Queries using `--version` translate to `WHERE i.version = 'X.Y'`.

- **Cross-database queries** – If you store data in PostgreSQL (via `--pg-*` flags), the same CLI flags and SQL schema apply, enabling shared analysis across teams.

## Summary

- Extract entitlements from one or more IPSWs using `ipsw ent --sqlite dbfile --ipsw …` to populate a relational database.
- Query the database for specific keys, values, or file paths across all stored firmware versions.
- Compare entitlement changes between two specific builds using `ipsw diff old.ipsw new.ipsw --ent`.
- Leverage the normalized schema in [`internal/model/ent.go`](https://github.com/blacktop/ipsw/blob/main/internal/model/ent.go) for custom SQL analysis or export to downstream security tools.

## Frequently Asked Questions

### How do I handle DER-encoded entitlements in older firmware?

The `ipsw ent` command automatically detects and decodes DER-encoded entitlements using the `DerDecode` function in [`internal/codesign/entitlements/entitlements.go`](https://github.com/blacktop/ipsw/blob/main/internal/codesign/entitlements/entitlements.go). You do not need to specify any special flags; the tool transparently handles both XML and DER formats during extraction.

### Can I use PostgreSQL instead of SQLite for team collaboration?

Yes. Instead of `--sqlite`, supply the PostgreSQL connection flags (`--pg-host`, `--pg-port`, `--pg-user`, `--pg-pass`, `--pg-db`). The same schema defined in [`internal/model/ent.go`](https://github.com/blacktop/ipsw/blob/main/internal/model/ent.go) is used for both backends, allowing multiple analysts to query the same entitlements database concurrently.

### What is the performance impact of processing many IPSW files?

The tool uses `storeEntitlementsBulk` in [`internal/commands/ent/database.go`](https://github.com/blacktop/ipsw/blob/main/internal/commands/ent/database.go) to group inserts into a single transaction per IPSW. This bulk insertion approach minimizes SQLite lock contention and PostgreSQL round-trips, making it feasible to process hundreds of firmware builds into a single database efficiently.

### How can I export entitlement diffs for reporting?

When using `ipsw diff --ent`, add the `--markdown`, `--json`, or `--html` flags to generate structured output. The templates in [`pkg/diff/format.go`](https://github.com/blacktop/ipsw/blob/main/pkg/diff/format.go) render the "🔑 Entitlements" section containing side-by-side comparisons. Specify `--output ./diffs` to write the results to disk for inclusion in security audit reports.