Skip to content

Parse IPA plists for real (binary + XML), not string-matching#8

Merged
ethanzhoucool merged 2 commits into
mainfrom
feat/ipa-binary-plist
Jun 27, 2026
Merged

Parse IPA plists for real (binary + XML), not string-matching#8
ethanzhoucool merged 2 commits into
mainfrom
feat/ipa-binary-plist

Conversation

@ethanzhoucool

Copy link
Copy Markdown
Contributor

Tier 2, feature 1 from the scanner review. Makes the IPA inspector actually parse plists.

Problem

The inspector read Info.plist and PrivacyInfo.xcprivacy as strings and grepped for keys. Production builds ship binary plists (bplist00), so on real IPAs:

  • the bundle id was never extracted,
  • the empty-purpose-string and ATS checks were dead,
  • CFBundleVersion / icon checks false-fired.

It also did a single rc.Read into a header-sized buffer, which truncated Deflate'd entries and trusted the declared uncompressed size (a decompression-bomb vector).

Fix

  • Parse both plists with howett.net/plist (auto-detects binary vs XML) into a map; read CFBundleIdentifier, version keys, NSAppTransportSecurity, and usage-description strings from real values.
  • Read entries with io.ReadAll under a 16 MiB cap.
  • Treat a compiled Assets.car as a valid icon source, so asset-catalog apps no longer get a false "No app icon" CRITICAL.
  • Only warn on missing CFBundleDisplayName when CFBundleName is also absent (Apple's fallback).
  • Simplify the framework privacy-manifest path; drop dead arch-detection code.

Tests

In-memory IPAs with binary and XML plist fixtures, plus end-to-end verification against a real plutil-generated bplist00. go test -race green.

Adds howett.net/plist v1.0.1 (BSD-2).

Separately reviewed: decode types verified against the library, truncation confirmed to fail closed. No blocking issues.

ethanzhoucool and others added 2 commits June 26, 2026 14:18
Tier 2 feature. The IPA inspector read Info.plist / PrivacyInfo.xcprivacy as a
string and grepped for keys. Production builds ship BINARY plists (bplist00), so
the empty-purpose-string and ATS checks were dead, the bundle id was never
extracted, and version/icon checks false-fired on essentially every real IPA.

- Parse both plists with howett.net/plist (auto-detects binary vs XML) into a
  map; read CFBundleIdentifier, version keys, NSAppTransportSecurity, and the
  usage-description strings from actual values.
- Read zip entries with io.ReadAll under a 16 MiB cap instead of a single
  rc.Read into a header-sized buffer (the old read truncated Deflate'd entries
  and trusted UncompressedSize64 -- a decompression-bomb vector).
- Treat a compiled Assets.car as a valid icon source, so apps whose icons live
  in the asset catalog no longer get a false "No app icon" CRITICAL.
- Only warn about CFBundleDisplayName when CFBundleName is also absent; simplify
  the framework privacy-manifest path; drop dead arch-detection code.
- Tests build in-memory IPAs with binary and XML plist fixtures.

Adds howett.net/plist v1.0.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a compiled Assets.car is present but no loose AppIcon*.png, the no-icon
CRITICAL was fully suppressed — but greenlight can't decode Assets.car to confirm
an AppIcon set actually exists, so an icon-less catalog passed silently. Emit an
INFO in that case instead (still no false CRITICAL on the common asset-catalog
app, but the recall gap is surfaced). Test asserts the INFO.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ethanzhoucool ethanzhoucool merged commit 339b0f8 into main Jun 27, 2026
1 check passed
@ethanzhoucool ethanzhoucool deleted the feat/ipa-binary-plist branch June 27, 2026 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant