ReviewOS

craft-native/craft

iOS: all 11 LINKED_PURPOSES permissions are written to Info.plist even when disabled

#424
Closed glennmichael123 opened this 6 days ago ·
6 days ago

Summary

renderUsageDescriptions() writes all eleven NS*UsageDescription keys into Info.plist for every generated project, regardless of the corresponding enable* flags. An app that enables none of them still ships a plist asking for speech recognition, the microphone, the camera, the photo library, location, contacts, calendars, Bluetooth, motion, Face ID and Apple Music.

There is no supported way to remove one. usageDescriptions only replaces a key's text.

Why it matters

Declaring permissions an app does not use, with non-specific purpose strings, is a documented App Review rejection reason. The generated strings are also what iOS shows a person at the permission prompt, so they are a privacy claim the app is making on the developer's behalf:

Bench Review asks for your contacts only in a feature that needs it.

That sentence explains nothing, and the app in question has no contacts feature at all.

Cause

In dist/ios/src/index.js (v0.0.187), the disabled branch falls back to a placeholder instead of omitting the key:

const own = config.usageDescriptions ?? {};
return entries.map(([enabled, key, value]) => {
  const custom = own[key]?.trim();
  if (custom)
    return [key, custom];
  if (enabled)
    return [key, value];
  const linked = LINKED_PURPOSES[key];
  return linked
    ? [key, `${config.appName} asks for ${linked} only in a feature that needs it.`]
    : null;
}).filter(entry => entry !== null)

The inconsistency is the giveaway: entries has 15 permissions, but LINKED_PURPOSES holds only 11 of them. The four that are absent from the table —

NSLocationAlwaysAndWhenInUseUsageDescription
NSLocalNetworkUsageDescription
NSHealthShareUsageDescription
NSHealthUpdateUsageDescription

— hit : null, get filtered, and are correctly omitted when disabled. So the generator already does the right thing, and LINKED_PURPOSES defeats it for the other eleven. That reads like an oversight rather than a deliberate choice: there is no obvious reason Health and Local Network should be omitted when unused while Contacts and Apple Music are not.

Reproduce

Generate any iOS project with none of the permission flags enabled — e.g. a config declaring only camera and filePicker capabilities — then:

grep -c UsageDescription Info.plist   # 11

Suggested fix

Omit the key when the capability is not enabled and no custom string was supplied:

if (custom)
  return [key, custom];
if (enabled)
  return [key, value];
return null;

LINKED_PURPOSES then has no role in the disabled path. If the generic sentence is meant as a fallback for an enabled capability that lacks specific wording, it belongs in the enabled branch instead.

Workaround

Patching the generated plist post-generation works and survives Xcode Cloud, since restoreShell() rewrites only Sources/*.swift and never touches Info.plist.

Version

craft-native@0.0.187

6 days ago

Implementation is pushed to main through 2d9a119 (eight focused commits, each touching 4–5 files).

Disabled permission APIs are now compiled out of both Swift hosts and app-specific Zig archives. Default purpose strings are omitted when unused; explicit custom descriptions remain supported. Permission dependencies (speech/microphone, video/camera/photos/microphone, heart-rate/Bluetooth, background location) are preserved.

Use an explicit CRAFT_IOS_RUNTIME_SOURCE (the Craft Zig source-package directory) and the pinned CRAFT_ZIG executable, or runtimeSource in the SDK. Builds validate the compiler, permission mask and archive hashes, preserve the old runtime on installation failure, and reject stale/unprofiled archives. Shell restoration updates permission gates and managed plist entries without rewriting unchanged Swift or app-owned files.

Local validation:

  • 800 SDK tests passed, 8 skipped; 306 focused builder/page-bridge/E2E protocol tests passed.
  • 283 real iOS simulator unit tests passed, zero skips.
  • Zig test:ios passed (9/9 build steps), plus 399 release-tooling tests.
  • Typecheck, package builds/43 publish-surface checks, formatting and lint passed (27 existing lint warnings, zero errors).
  • Real device-target Debug AND Release apps linked with disabled/enabled per-app Zig runtimes passed binary/API-name and compiled-plist inspection. CI now applies these checks to the template fixtures and both mobile E2E runtime legs.

Full remote CI and Mobile E2E are still pending: https://github.com/craft-native/craft/actions/runs/40569245672 https://github.com/craft-native/craft/actions/runs/40569245730

Keeping this issue open until those checks confirm the final head. No release or tag was created; this is a main-branch fix, not a newly published version. Binary inspection is not signed App Store acceptance. The builds also report the existing Zig iOS 18 versus app iOS 16 deployment-target warning; this change does not establish iOS 16 runtime compatibility.

5 days ago

Verified shipped in v0.0.377; closing as completed. The earlier pending-CI comment is now superseded.

Release source: 52779dd, containing the complete permission/runtime-profile changes through 2d9a119.

Independently downloaded @craft-native/ios@0.0.377 and craft-native@0.0.377, verified their registry SHA512 integrity, and confirmed the published host templates match the release source. Executed the published iOS builder unchanged: no enabled permissions produces zero UsageDescription keys; camera produces only camera and photo-library keys; an explicit custom contacts description remains supported.

Exact-release CI logs also confirm the linked MinimalProbe matches permission profile [] in both device Debug and Release builds, and CapabilityProbe matches its enabled profile in both configurations. Both mobile runtime legs pass profile inspection as well. This verifies the compiled-out API and generated-plist contract, not App Store approval or new iOS 16 deployment compatibility. No new release or tag was created for this verification.

Sign in to comment on this issue.