Skip to content

Skip unknown entitlement keys with a warning in SyncBundleID#327

Open
Badlazzor wants to merge 3 commits into
audit/service-type-by-keyfrom
harden/unknown-entitlement-skip
Open

Skip unknown entitlement keys with a warning in SyncBundleID#327
Badlazzor wants to merge 3 commits into
audit/service-type-by-keyfrom
harden/unknown-entitlement-skip

Conversation

@Badlazzor

Copy link
Copy Markdown
Contributor

Stacked on #326 (audit/service-type-by-key) → #325 (fix/healthkit-access-entitlement). Merge in order; this PR's diff will collapse to its own changes once the base PRs land.

Issue

autocodesign.Entitlement.Capability() hard-errors with unknown entitlement key: <key> whenever a project's entitlements file contains a key that is not in appstoreconnect.ServiceTypeByKey.

ProfileClient.SyncBundleID iterates the entire project entitlements map and returns the first such error unwrapped. As a result, one unrecognised key anywhere in the project kills the entire code signing phase, even when every other entitlement is well-known and in sync with App Store Connect:

failed to manage code signing: failed to ensure code signing assets:
failed to ensure profiles: failed to update bundle ID capabilities:
unknown entitlement key: <key>

This is the failure mode behind #325 (com.apple.developer.healthkit.access, iOS 17.5+). Apple adds entitlement keys routinely (see #326 for a recent batch); each addition is a latent, high-blast-radius break.

Intent

Decouple the step's stability from Apple's entitlement release cadence. An entitlement key that we have not yet classified should log a warning and be skipped, not abort the entire sync. Other entitlements on the bundle ID still get registered correctly.

Changes

  1. Exported sentinel error (autocodesign.ErrUnknownEntitlementKey) in autocodesign/entitlement.go. Both Capability() and Equal() now wrap it with fmt.Errorf(\"%w: %s\", …). The human-readable error message is unchanged — unknown entitlement key: <key> — so any log-scraping stays compatible, while callers gain errors.Is wiring for programmatic handling.
  2. ProfileClient.SyncBundleID catches the sentinel with errors.Is, logs a warning that identifies the offending key and points at this repo, and continues the loop to process the rest of the project's entitlements instead of aborting.
  3. Regression test (TestCapability_UnknownKeyReturnsSentinel) in autocodesign/entitlement_test.go confirms sentinel wrapping + key attribution in the error message.

Equal() (called from checkBundleIDEntitlements) is not currently reachable with an unknown key because its caller gates on AppearsOnDeveloperPortal(), which already returns false for unknown keys. The sentinel is wired there anyway for consistency — so if that gate is ever relaxed, the behavior stays defensive.

Risk / tradeoff

Today: an unrecognised entitlement = every affected build fails with an opaque error.

After this PR: an unrecognised entitlement = a warning in the log, the bundle ID is synced with every other capability, the build proceeds. If the unknown key did need ASC registration, the downstream provisioning step will fail with a more targeted error (missing capability on a specific profile) that is easier to attribute than today's "unknown entitlement key".

In other words: we trade a guaranteed hard-break for a soft-break with better attribution. The step becomes resilient to Apple's additions until the allow-list catches up.

Test plan

  • go test ./autocodesign/...
  • go vet ./autocodesign/...
  • New TestCapability_UnknownKeyReturnsSentinel passes
  • Integration: inject a synthetic unknown key into a project's entitlements, run xcode-archive@6.x, confirm warning is logged and other capabilities sync correctly

🤖 Generated with Claude Code

The entitlement (introduced with iOS 17.5 / Xcode 15.3) declares which
HealthKit data categories an app accesses. It is a companion to the base
`com.apple.developer.healthkit` key and does not require a separate
capability registration in App Store Connect.

Without this entry, `Entitlement.Capability()` returned an
`unknown entitlement key` error, which propagated up through
`SyncBundleID` / `ensureBundleID` and broke `xcode-archive@6.x`
automatic code signing for any target with HealthKit on iOS 17.5+.

Mapping to `Ignored` matches the treatment of similar metadata-only
companion keys (`icloud-container-identifiers`,
`ubiquity-container-identifiers`).
Apple has added several entitlement keys since `ServiceTypeByKey` was
last extended. Any key missing from the map hard-errors
`Entitlement.Capability()` with `unknown entitlement key: …`, aborting
`SyncBundleID` / `ensureBundleID` during automatic code signing.

Adds three entries that are safe to treat as `Ignored` (do not appear
on the developer portal, no App Store Connect registration required):

- `com.apple.developer.kernel.extended-virtual-addressing`
- `com.apple.developer.kernel.increased-memory-limit`
- `com.apple.developer.authentication-services.credential-provider-ui`

Adds a regression test (`TestCapability_IgnoredKeys`) covering both
`Capability()` and `AppearsOnDeveloperPortal()` for each new key.

Out of scope (needs maintainer decision):
- `com.apple.developer.weatherkit` — WeatherKit is a registrable
  service in App Store Connect; likely needs a new `CapabilityType`
  constant, not `Ignored`.
- `com.apple.developer.matter.allow-setup-payload` — Matter support
  entitlement; ASC registration requirements unclear.
Apple routinely adds new entitlement keys (most recently
`com.apple.developer.healthkit.access`, `kernel.increased-memory-limit`,
etc.). Any key not present in `ServiceTypeByKey` causes
`Entitlement.Capability()` to hard-error and `SyncBundleID` to abort the
entire code signing phase, which means a single freshly-introduced key
breaks every build that happens to declare it until the allow-list is
extended and a new step release is cut.

This change:

- Introduces `autocodesign.ErrUnknownEntitlementKey` as an exported
  sentinel, and wraps it with `fmt.Errorf("%w: %s", …)` in both
  `Capability()` and `Equal()`. Callers that have existing
  `errors.Is(err, ErrUnknownEntitlementKey)` handling continue to work;
  the human-readable message is unchanged (`unknown entitlement key:
  <key>`).
- Updates `ProfileClient.SyncBundleID` to catch the sentinel, log a
  warning identifying the key, and continue syncing the remaining
  entitlements instead of aborting. Other entitlements in the project
  still get registered correctly.

Tradeoff: if Apple adds an entitlement that genuinely requires App Store
Connect registration and it is not yet in `ServiceTypeByKey`, the step
will log a warning and move on rather than erroring out. The resulting
provisioning profile will miss that one capability — but every other
capability on the bundle ID is now synced correctly, instead of the
current behavior where *no* capability gets synced. A missing capability
surfaces as a later-stage provisioning error with clearer attribution
than today's opaque `unknown entitlement key` message.

Adds a regression test (`TestCapability_UnknownKeyReturnsSentinel`)
covering sentinel wrapping and key attribution.
// ICloudIdentifiersEntitlementKey ...
const ICloudIdentifiersEntitlementKey = "com.apple.developer.icloud-container-identifiers"

// ErrUnknownEntitlementKey is returned by Entitlement.Capability and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this comment about which methods return the error is likely to become outdated. Maybe omit that part.

cap, err := ent.Capability()
if err != nil {
if errors.Is(err, autocodesign.ErrUnknownEntitlementKey) {
log.Warnf("Skipping unknown entitlement key %q while syncing bundle ID capabilities. If this is a recognised Apple entitlement that should be registered on App Store Connect, please report it to bitrise-io/go-xcode so it can be added to ServiceTypeByKey.", key)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This repo tracks App Store Connect API errors (see tracker.go), and I think it would make sense to add something similar here so that we set up some alerting to get notified when the inevitable new entitlement gets added. What do you think?

@Badlazzor Badlazzor force-pushed the audit/service-type-by-key branch from 6ee72be to 789e080 Compare July 8, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants