@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,496 +1,399 @@
|
|
|
1
|
-
# Keychain Sharing
|
|
1
|
+
# Keychain Sharing Across Apps and Extensions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Access groups are the one mechanism for letting several apps and extensions read the
|
|
4
|
+
same Keychain items. Getting them right takes three things together: the exact Team ID
|
|
5
|
+
prefix, an entitlement on every target that participates, and an explicit
|
|
6
|
+
`kSecAttrAccessGroup` in the code.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Behaviour described here holds through iOS 18 and macOS Sequoia 15. It is based on
|
|
9
|
+
Apple's "Sharing Access to Keychain Items Among a Collection of Apps", TN3137 (On Mac
|
|
10
|
+
keychain APIs and implementations), the iCloud Keychain chapter of the Platform
|
|
11
|
+
Security Guide, "Configuring Keychain Sharing", and guidance from Apple DTS engineers
|
|
12
|
+
on the developer forums (updated May 2025).
|
|
10
13
|
|
|
11
14
|
## Contents
|
|
12
15
|
|
|
13
|
-
- [How
|
|
14
|
-
- [Two
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- [
|
|
26
|
-
|
|
27
|
-
- [Required Entitlements Matrix](#required-entitlements-matrix)
|
|
28
|
-
- [The macOS Keychain Split](#the-macos-keychain-split)
|
|
29
|
-
- [Cross-platform macOS support with `kSecUseDataProtectionKeychain`](#cross-platform-macos-support-with-ksecusedataprotectionkeychain)
|
|
30
|
-
- [Migrating Items Between Access Groups](#migrating-items-between-access-groups)
|
|
31
|
-
- [Lifecycle Edge Cases](#lifecycle-edge-cases)
|
|
32
|
-
- [Keychain items persist after app uninstall](#keychain-items-persist-after-app-uninstall)
|
|
33
|
-
- [App transfers between teams break keychain access](#app-transfers-between-teams-break-keychain-access)
|
|
34
|
-
- [Cross-developer sharing is impossible via access groups](#cross-developer-sharing-is-impossible-via-access-groups)
|
|
35
|
-
- [Platform-Specific Patterns](#platform-specific-patterns)
|
|
36
|
-
- [watchOS](#watchos)
|
|
37
|
-
- [Widget Extensions (WidgetKit)](#widget-extensions-widgetkit)
|
|
38
|
-
- [Build and Distribution Considerations](#build-and-distribution-considerations)
|
|
39
|
-
- [Debugging When Keychain Sharing Breaks](#debugging-when-keychain-sharing-breaks)
|
|
40
|
-
- [Essential Error Codes](#essential-error-codes)
|
|
41
|
-
- [Debugging Checklist](#debugging-checklist)
|
|
42
|
-
- [Test Matrix](#test-matrix)
|
|
43
|
-
- [Security Threat Model Notes](#security-threat-model-notes)
|
|
44
|
-
- [What Changed in 2024-2026](#what-changed-in-20242026)
|
|
45
|
-
- [Cross-References](#cross-references)
|
|
46
|
-
- [Conclusion](#conclusion)
|
|
47
|
-
- [Summary Checklist](#summary-checklist)
|
|
48
|
-
|
|
49
|
-
## How Access Groups Work
|
|
50
|
-
|
|
51
|
-
Every app belongs to one or more **access groups** - string identifiers that tag which processes can read and write specific keychain items. An app can belong to many groups, but each keychain item belongs to **exactly one**. The `securityd` daemon enforces access by checking the calling process's entitlements against the item's group at runtime.
|
|
52
|
-
|
|
53
|
-
The system constructs a virtual array of access groups for each app by concatenating three sources **in this exact order**:
|
|
54
|
-
|
|
55
|
-
1. **Keychain access groups** from the `keychain-access-groups` entitlement
|
|
56
|
-
2. **Application identifier** - automatically generated as `TeamID.BundleID` (e.g., `SKMME9E2Y8.com.example.MyApp`)
|
|
57
|
-
3. **App groups** from the `com.apple.security.application-groups` entitlement (iOS 8+)
|
|
58
|
-
|
|
59
|
-
**The first item in this concatenated list becomes the default access group.** When `SecItemAdd` is called without specifying `kSecAttrAccessGroup`, the item lands in that default group. When `SecItemCopyMatching` is called without specifying a group, the search spans **all** groups the app belongs to. This ordering means a keychain access group can be the default (it appears first), but an app group can never be the default because the application identifier always precedes it.
|
|
60
|
-
|
|
61
|
-
Example for an app with one keychain group and one app group:
|
|
62
|
-
|
|
63
|
-
```text
|
|
64
|
-
[SKMME9E2Y8.com.example.SharedItems, ← keychain access group (default)
|
|
65
|
-
SKMME9E2Y8.com.example.MyApp, ← application identifier (automatic)
|
|
66
|
-
group.com.example.AppSuite] ← app group
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**Sharing is restricted to a single development team.** Apps from different developer teams cannot share keychain items through access groups. The Team ID prefix on every group identifier, enforced through code-signed provisioning profiles, prevents cross-team access. The only way different developers' apps can share credentials is through iCloud Keychain + Associated Domains (password autofill based on web domain ownership), which is an entirely different mechanism.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## Two Entitlements, Two Formats, Different Purposes
|
|
16
|
+
- [How access groups work](#how-access-groups-work)
|
|
17
|
+
- [Two entitlements with two formats](#two-entitlements-with-two-formats)
|
|
18
|
+
- [Code](#code)
|
|
19
|
+
- [Setting up every target](#setting-up-every-target)
|
|
20
|
+
- [macOS has two keychains](#macos-has-two-keychains)
|
|
21
|
+
- [Moving an item to another group](#moving-an-item-to-another-group)
|
|
22
|
+
- [Lifecycle surprises](#lifecycle-surprises)
|
|
23
|
+
- [Platform notes](#platform-notes)
|
|
24
|
+
- [Signing and distribution](#signing-and-distribution)
|
|
25
|
+
- [When sharing breaks](#when-sharing-breaks)
|
|
26
|
+
- [Threat model](#threat-model)
|
|
27
|
+
- [2024 to 2026](#2024-to-2026)
|
|
28
|
+
- [Related](#related)
|
|
29
|
+
- [Checklist](#checklist)
|
|
74
30
|
|
|
75
|
-
|
|
31
|
+
## How access groups work
|
|
76
32
|
|
|
77
|
-
|
|
33
|
+
An app can belong to many access groups. Every item belongs to exactly one.
|
|
34
|
+
`securityd` compares the calling process's entitlements with the item's group on every
|
|
35
|
+
call.
|
|
78
36
|
|
|
79
|
-
|
|
37
|
+
The system builds the app's list of groups in this order:
|
|
80
38
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
85
|
-
<plist version="1.0">
|
|
86
|
-
<dict>
|
|
87
|
-
<key>keychain-access-groups</key>
|
|
88
|
-
<array>
|
|
89
|
-
<string>$(AppIdentifierPrefix)com.example.SharedItems</string>
|
|
90
|
-
</array>
|
|
91
|
-
</dict>
|
|
92
|
-
</plist>
|
|
93
|
-
```
|
|
39
|
+
1. Entries of the `keychain-access-groups` entitlement.
|
|
40
|
+
2. The application identifier, `TeamID.BundleID`, added automatically.
|
|
41
|
+
3. Entries of `com.apple.security.application-groups` (iOS 8 and later).
|
|
94
42
|
|
|
95
|
-
The
|
|
43
|
+
The first entry in that list is the default: `SecItemAdd` without
|
|
44
|
+
`kSecAttrAccessGroup` puts the item there. `SecItemCopyMatching` without a group
|
|
45
|
+
searches all of the app's groups.
|
|
96
46
|
|
|
97
|
-
|
|
47
|
+
Because the application identifier always comes before app groups, a keychain access
|
|
48
|
+
group can be the default but an app group never can.
|
|
98
49
|
|
|
99
|
-
|
|
50
|
+
Sharing is limited to one development team; the signed provisioning profile enforces
|
|
51
|
+
the Team ID prefix. Between different teams the only route is iCloud Keychain
|
|
52
|
+
together with Associated Domains, which serves password autofill rather than shared
|
|
53
|
+
app data.
|
|
100
54
|
|
|
101
|
-
|
|
102
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
103
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
104
|
-
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
105
|
-
<plist version="1.0">
|
|
106
|
-
<dict>
|
|
107
|
-
<key>com.apple.security.application-groups</key>
|
|
108
|
-
<array>
|
|
109
|
-
<string>group.com.example.AppSuite</string>
|
|
110
|
-
</array>
|
|
111
|
-
</dict>
|
|
112
|
-
</plist>
|
|
113
|
-
```
|
|
55
|
+
## Two entitlements with two formats
|
|
114
56
|
|
|
115
|
-
|
|
57
|
+
Mixing up Keychain Sharing and App Groups is the most common mistake.
|
|
116
58
|
|
|
117
|
-
|
|
59
|
+
| | Keychain Sharing | App Groups |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| Entitlement | `keychain-access-groups` | `com.apple.security.application-groups` |
|
|
62
|
+
| Value in entitlements | `$(AppIdentifierPrefix)com.example.vault` | `group.com.example.suite` |
|
|
63
|
+
| Resolved value | `A1B2C3D4E5.com.example.vault` | `group.com.example.suite` (no Team ID) |
|
|
64
|
+
| Shares | Keychain items | Containers, `UserDefaults(suiteName:)`, IPC, and on iOS also Keychain items |
|
|
118
65
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
| **Entitlement key** | `keychain-access-groups` | `com.apple.security.application-groups` |
|
|
122
|
-
| **Format** | `$(AppIdentifierPrefix)com.example.shared` | `group.com.example.shared` |
|
|
123
|
-
| **Team ID prefix** | Yes (automatic via build variable) | No (`group.` prefix instead) |
|
|
124
|
-
| **Shares** | Keychain items only | Containers, UserDefaults, IPC, and keychain items (iOS only) |
|
|
125
|
-
| **Can be default group** | Yes (if first in array) | No |
|
|
126
|
-
| **macOS keychain sharing** | Yes (with data protection keychain) | No |
|
|
66
|
+
`$(AppIdentifierPrefix)` is replaced with `TEAMID.` at signing time. Code never sees
|
|
67
|
+
that substitution, so it must use the full resolved string.
|
|
127
68
|
|
|
128
|
-
|
|
69
|
+
Since iOS 8 an app group name can be used as a `kSecAttrAccessGroup` value, though
|
|
70
|
+
never as the default. On macOS app groups cannot act as keychain access groups; that
|
|
71
|
+
is iOS and iPadOS only. On macOS, Keychain Sharing groups work only in the data
|
|
72
|
+
protection keychain.
|
|
129
73
|
|
|
130
|
-
|
|
74
|
+
Both entitlements can coexist. If you only need shared credentials, add Keychain
|
|
75
|
+
Sharing. If the apps already share an App Group, it can carry Keychain items on iOS as
|
|
76
|
+
long as every call names it explicitly.
|
|
131
77
|
|
|
132
|
-
## Code
|
|
78
|
+
## Code
|
|
133
79
|
|
|
134
|
-
|
|
80
|
+
Writing to a shared group:
|
|
135
81
|
|
|
136
82
|
```swift
|
|
137
83
|
import Security
|
|
138
84
|
|
|
139
|
-
|
|
140
|
-
let
|
|
141
|
-
|
|
142
|
-
let password = "s3cretT0ken".data(using: .utf8)!
|
|
143
|
-
let addQuery: [String: Any] = [
|
|
144
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
145
|
-
kSecAttrService as String: "com.example.authService",
|
|
146
|
-
kSecAttrAccount as String: "user@example.com",
|
|
147
|
-
kSecAttrAccessGroup as String: accessGroup,
|
|
148
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
149
|
-
kSecValueData as String: password
|
|
150
|
-
]
|
|
151
|
-
|
|
152
|
-
let status = SecItemAdd(addQuery as CFDictionary, nil)
|
|
153
|
-
guard status == errSecSuccess else {
|
|
154
|
-
print("Keychain add failed: \(status)") // -34018 = missing entitlement
|
|
155
|
-
return
|
|
85
|
+
enum SharedVault {
|
|
86
|
+
static let group = "A1B2C3D4E5.com.example.vault"
|
|
87
|
+
static let service = "com.example.vault.session"
|
|
156
88
|
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
The Team ID must be the **literal 10-character string** from the Apple Developer account, not a build variable - `$(AppIdentifierPrefix)` only works in entitlements plists, not in Swift code.
|
|
160
89
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
Xcode's Keychain Sharing UI shows `com.example.SharedItems` without the prefix, which misleads developers and AI generators alike. **In code, the full `TEAMID.com.example.SharedItems` string is always required.**
|
|
179
|
-
|
|
180
|
-
### App extension reading a shared keychain item
|
|
181
|
-
|
|
182
|
-
The extension target must have its own Keychain Sharing capability with the same group:
|
|
183
|
-
|
|
184
|
-
```swift
|
|
185
|
-
// In a widget extension, share extension, or other app extension
|
|
186
|
-
let teamID = "SKMME9E2Y8"
|
|
187
|
-
let accessGroup = "\(teamID).com.example.SharedItems"
|
|
188
|
-
|
|
189
|
-
let readQuery: [String: Any] = [
|
|
190
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
191
|
-
kSecAttrService as String: "com.example.authService",
|
|
192
|
-
kSecAttrAccount as String: "user@example.com",
|
|
193
|
-
kSecAttrAccessGroup as String: accessGroup,
|
|
194
|
-
kSecReturnData as String: true
|
|
195
|
-
]
|
|
196
|
-
|
|
197
|
-
var result: AnyObject?
|
|
198
|
-
let status = SecItemCopyMatching(readQuery as CFDictionary, &result)
|
|
199
|
-
if status == errSecSuccess, let data = result as? Data {
|
|
200
|
-
let token = String(data: data, encoding: .utf8)
|
|
201
|
-
// Use the shared token
|
|
90
|
+
func publishSessionToken(_ token: Data) -> OSStatus {
|
|
91
|
+
let identity: [String: Any] = [
|
|
92
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
93
|
+
kSecAttrAccessGroup as String: SharedVault.group,
|
|
94
|
+
kSecAttrService as String: SharedVault.service,
|
|
95
|
+
kSecAttrAccount as String: "active-user"
|
|
96
|
+
]
|
|
97
|
+
var insert = identity
|
|
98
|
+
insert[kSecValueData as String] = token
|
|
99
|
+
insert[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
100
|
+
|
|
101
|
+
let added = SecItemAdd(insert as CFDictionary, nil)
|
|
102
|
+
guard added == errSecDuplicateItem else { return added } // -34018: entitlement missing
|
|
103
|
+
let changes: [String: Any] = [kSecValueData as String: token]
|
|
104
|
+
return SecItemUpdate(identity as CFDictionary, changes as CFDictionary)
|
|
202
105
|
}
|
|
203
106
|
```
|
|
204
107
|
|
|
205
|
-
|
|
108
|
+
The Team ID in code is the literal ten-character string. `$(AppIdentifierPrefix)`
|
|
109
|
+
works only inside the entitlements plist.
|
|
206
110
|
|
|
207
111
|
```swift
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
// The main app has it, but extensions are SEPARATE executable targets.
|
|
211
|
-
// Result: errSecMissingEntitlement (-34018)
|
|
112
|
+
// Broken: group without the Team ID prefix
|
|
113
|
+
let wrongGroup = "com.example.vault"
|
|
212
114
|
```
|
|
213
115
|
|
|
214
|
-
|
|
116
|
+
This fails with `errSecMissingEntitlement` (-34018) on iOS 13 and later, and with
|
|
117
|
+
`errSecItemNotFound` (-25300) on earlier versions. Xcode's Keychain Sharing editor
|
|
118
|
+
shows the name without its prefix, which is why this keeps happening.
|
|
215
119
|
|
|
216
|
-
|
|
120
|
+
Reading it from an extension:
|
|
217
121
|
|
|
218
122
|
```swift
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
]
|
|
228
|
-
|
|
123
|
+
func readSessionTokenInExtension() -> Data? {
|
|
124
|
+
let query: [String: Any] = [
|
|
125
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
126
|
+
kSecAttrAccessGroup as String: SharedVault.group,
|
|
127
|
+
kSecAttrService as String: SharedVault.service,
|
|
128
|
+
kSecAttrAccount as String: "active-user",
|
|
129
|
+
kSecReturnData as String: true,
|
|
130
|
+
kSecMatchLimit as String: kSecMatchLimitOne
|
|
131
|
+
]
|
|
132
|
+
var found: CFTypeRef?
|
|
133
|
+
guard SecItemCopyMatching(query as CFDictionary, &found) == errSecSuccess else { return nil }
|
|
134
|
+
return found as? Data
|
|
135
|
+
}
|
|
229
136
|
```
|
|
230
137
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
- The user must have iCloud Keychain enabled and be signed into the same Apple ID on all target devices.
|
|
236
|
-
- Synchronization is orthogonal to on-device sharing: an item can be both in a shared access group and synchronizable across devices.
|
|
138
|
+
An extension that lacks its own Keychain Sharing capability gets -34018 here even
|
|
139
|
+
when the host app has the capability. Every executable target (app, widget, share
|
|
140
|
+
extension, notification service extension) needs the entitlement itself. Frameworks
|
|
141
|
+
carry no entitlements; only the targets that link them do.
|
|
237
142
|
|
|
238
|
-
|
|
239
|
-
// ✅ Query that finds both sync and non-sync items
|
|
240
|
-
let findQuery: [String: Any] = [
|
|
241
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
242
|
-
kSecAttrService as String: "com.example.authService",
|
|
243
|
-
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny,
|
|
244
|
-
kSecReturnData as String: true
|
|
245
|
-
]
|
|
246
|
-
```
|
|
143
|
+
### iCloud sync
|
|
247
144
|
|
|
248
|
-
|
|
145
|
+
Syncing is a separate, per-item opt-in:
|
|
249
146
|
|
|
250
147
|
```swift
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
148
|
+
func publishSyncedPreferenceKey(_ secret: Data) -> OSStatus {
|
|
149
|
+
let identity: [String: Any] = [
|
|
150
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
151
|
+
kSecAttrAccessGroup as String: SharedVault.group,
|
|
152
|
+
kSecAttrService as String: "com.example.vault.sync",
|
|
153
|
+
kSecAttrAccount as String: "reader-settings-key",
|
|
154
|
+
kSecAttrSynchronizable as String: kCFBooleanTrue as Any
|
|
155
|
+
]
|
|
156
|
+
var insert = identity
|
|
157
|
+
insert[kSecValueData as String] = secret
|
|
158
|
+
insert[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
159
|
+
|
|
160
|
+
let added = SecItemAdd(insert as CFDictionary, nil)
|
|
161
|
+
guard added == errSecDuplicateItem else { return added }
|
|
162
|
+
return SecItemUpdate(identity as CFDictionary,
|
|
163
|
+
[kSecValueData as String: secret] as CFDictionary)
|
|
164
|
+
}
|
|
260
165
|
```
|
|
261
166
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
---
|
|
265
|
-
|
|
266
|
-
## Cross-Target Entitlements Setup
|
|
167
|
+
Rules for synced items:
|
|
267
168
|
|
|
268
|
-
|
|
169
|
+
- Apple documents that a synchronizable item may not use an accessibility value
|
|
170
|
+
ending in `ThisDeviceOnly`. `SecItemAdd` rejects the combination with `errSecParam`
|
|
171
|
+
(-50); nothing is stored and nothing syncs. Use `AfterFirstUnlock` or `WhenUnlocked`.
|
|
172
|
+
- Every query, update and delete that should see synced items must include
|
|
173
|
+
`kSecAttrSynchronizable: true` or `kSecAttrSynchronizableAny`; without it, synced
|
|
174
|
+
items are excluded from the match.
|
|
175
|
+
- Sync needs iCloud Keychain turned on and the same Apple Account on each device. It
|
|
176
|
+
is independent of which access group the item is in.
|
|
177
|
+
- `kSecAttrSynchronizable` defaults to false. Nothing syncs unless you ask for it item
|
|
178
|
+
by item. Synced items are end-to-end encrypted; Apple cannot read them.
|
|
269
179
|
|
|
270
|
-
|
|
180
|
+
## Setting up every target
|
|
271
181
|
|
|
272
|
-
|
|
273
|
-
2. Add the desired group identifier (e.g., `com.example.shared`). Xcode auto-prefixes with Team ID in the entitlements file.
|
|
274
|
-
3. **Repeat for every extension target** - select the extension target, add Keychain Sharing, add the **exact same** group identifier.
|
|
275
|
-
4. For App Groups: add the App Groups capability to each target and use the same `group.` identifier.
|
|
182
|
+
Extensions do not inherit capabilities from the app that contains them.
|
|
276
183
|
|
|
277
|
-
|
|
184
|
+
1. Select the app target, open Signing & Capabilities, click + Capability, add
|
|
185
|
+
Keychain Sharing and enter the group. Xcode applies the Team ID prefix.
|
|
186
|
+
2. Do exactly the same for each extension target.
|
|
187
|
+
3. If App Groups are used too, add the same `group.` identifier to each target.
|
|
278
188
|
|
|
279
|
-
| Target
|
|
280
|
-
|
|
|
281
|
-
|
|
|
282
|
-
|
|
|
283
|
-
|
|
|
284
|
-
|
|
|
189
|
+
| Target | keychain-access-groups | application-groups |
|
|
190
|
+
| --- | --- | --- |
|
|
191
|
+
| Main app | `A1B2C3D4E5.com.example.vault` (first entry, so the default) | `group.com.example.suite` |
|
|
192
|
+
| Share extension | `A1B2C3D4E5.com.example.vault` | `group.com.example.suite` |
|
|
193
|
+
| Widget extension | `A1B2C3D4E5.com.example.vault` | `group.com.example.suite` |
|
|
194
|
+
| Notification extension | `A1B2C3D4E5.com.example.vault` | `group.com.example.suite` |
|
|
285
195
|
|
|
286
|
-
|
|
196
|
+
Each target is signed with its own provisioning profile.
|
|
287
197
|
|
|
288
|
-
##
|
|
198
|
+
## macOS has two keychains
|
|
289
199
|
|
|
290
|
-
|
|
200
|
+
- **File-based keychain.** The legacy implementation: `.keychain-db` files and
|
|
201
|
+
`SecAccess` ACLs. On macOS it is where `SecItem` calls go by default. It has no
|
|
202
|
+
iCloud Keychain, no biometrics, no Secure Enclave keys and no access groups.
|
|
203
|
+
- **Data protection keychain.** The iOS implementation, on macOS since 10.9 through
|
|
204
|
+
iCloud Keychain. It has access groups, `SecAccessControl`, sync, Touch ID and
|
|
205
|
+
Secure Enclave keys. It is available only in a user login context, so `launchd`
|
|
206
|
+
daemons cannot use it.
|
|
291
207
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
**Data protection keychain** - originated on iOS and arrived on macOS via iCloud Keychain in 10.9. Uses keychain access groups + `SecAccessControl`, supports iCloud sync, Touch ID/Face ID, and Secure Enclave. Available only in user-login contexts - **`launchd` daemons cannot use it**.
|
|
295
|
-
|
|
296
|
-
### Cross-platform macOS support with `kSecUseDataProtectionKeychain`
|
|
208
|
+
On macOS `kSecAttrAccessGroup` is ignored without an error unless the call targets the
|
|
209
|
+
data protection keychain:
|
|
297
210
|
|
|
298
211
|
```swift
|
|
299
|
-
|
|
300
|
-
kSecClass as String:
|
|
301
|
-
kSecAttrService as String:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
kSecUseDataProtectionKeychain as String: true,
|
|
305
|
-
kSecValueData as String: password
|
|
212
|
+
let macQuery: [String: Any] = [
|
|
213
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
214
|
+
kSecAttrService as String: SharedVault.service,
|
|
215
|
+
kSecAttrAccessGroup as String: SharedVault.group,
|
|
216
|
+
kSecUseDataProtectionKeychain as String: true
|
|
306
217
|
]
|
|
307
|
-
let status = SecItemAdd(query as CFDictionary, nil)
|
|
308
218
|
```
|
|
309
219
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
Two ways to target the data protection keychain on macOS: set `kSecUseDataProtectionKeychain` to `true`, or set `kSecAttrSynchronizable` to `true` (which also enables iCloud sync). Mac Catalyst and iOS Apps on Mac use data protection exclusively - the flag is ignored there.
|
|
220
|
+
Either `kSecUseDataProtectionKeychain: true` or `kSecAttrSynchronizable: true` routes
|
|
221
|
+
a call there. Mac Catalyst apps and iOS apps running on a Mac always use it.
|
|
313
222
|
|
|
314
|
-
| Platform
|
|
315
|
-
|
|
|
316
|
-
|
|
|
317
|
-
|
|
|
318
|
-
| **macOS (AppKit)** | Legacy file-based | No (by default) | `kSecUseDataProtectionKeychain: true` |
|
|
223
|
+
| Platform | Default keychain | Access groups by default | Flag needed |
|
|
224
|
+
| --- | --- | --- | --- |
|
|
225
|
+
| iOS, iPadOS, Mac Catalyst | Data protection | Yes | No |
|
|
226
|
+
| macOS (AppKit) | File-based | No | `kSecUseDataProtectionKeychain` |
|
|
319
227
|
|
|
320
|
-
|
|
228
|
+
TN3137 describes the file-based keychain as on the road to deprecation;
|
|
229
|
+
`SecKeychainCreate` is deprecated as of the macOS 12 SDK. Only `launchd` daemons with no
|
|
230
|
+
user context should keep using it.
|
|
321
231
|
|
|
322
|
-
|
|
232
|
+
## Moving an item to another group
|
|
323
233
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
1. **Read**: Retrieve the complete item from its original access group via `SecItemCopyMatching`.
|
|
329
|
-
2. **Add**: Call `SecItemAdd` with the new `kSecAttrAccessGroup`.
|
|
330
|
-
3. **Delete**: Only after `SecItemAdd` returns `errSecSuccess`, delete the original item via `SecItemDelete`.
|
|
331
|
-
|
|
332
|
-
If the add operation fails, the original item remains untouched, preventing data loss. This pattern is safe because it never deletes until the new copy is confirmed.
|
|
333
|
-
|
|
334
|
-
---
|
|
335
|
-
|
|
336
|
-
## Lifecycle Edge Cases
|
|
337
|
-
|
|
338
|
-
### Keychain items persist after app uninstall
|
|
339
|
-
|
|
340
|
-
This behavior is undocumented but has been consistent since iOS's early days. Apple attempted to delete keychain items on app removal in iOS 10.3 beta but rolled it back before release due to compatibility issues. Quinn "The Eskimo!" has warned this behavior **could change without notice**. If shared keychain items exist between App A and App B, deleting App A leaves all shared items intact for App B. Even deleting all apps in a shared group does not remove orphaned items - only a factory reset clears them reliably.
|
|
341
|
-
|
|
342
|
-
A common workaround for detecting fresh installs (since `UserDefaults` _are_ wiped on uninstall):
|
|
234
|
+
`SecItemUpdate` cannot change `kSecAttrAccessGroup`. To move an item, read it with
|
|
235
|
+
`SecItemCopyMatching`, add a copy in the new group, and delete the original only after
|
|
236
|
+
that add returned `errSecSuccess`. If the add fails, the original is untouched.
|
|
343
237
|
|
|
344
238
|
```swift
|
|
345
|
-
func
|
|
346
|
-
let
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
239
|
+
func moveItem(account: String, from oldGroup: String, to newGroup: String) -> Bool {
|
|
240
|
+
let source: [String: Any] = [
|
|
241
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
242
|
+
kSecAttrService as String: SharedVault.service,
|
|
243
|
+
kSecAttrAccount as String: account,
|
|
244
|
+
kSecAttrAccessGroup as String: oldGroup
|
|
245
|
+
]
|
|
246
|
+
var lookup = source
|
|
247
|
+
lookup[kSecReturnData as String] = true
|
|
248
|
+
var value: CFTypeRef?
|
|
249
|
+
guard SecItemCopyMatching(lookup as CFDictionary, &value) == errSecSuccess,
|
|
250
|
+
let secret = value as? Data else { return false }
|
|
251
|
+
|
|
252
|
+
var target = source
|
|
253
|
+
target[kSecAttrAccessGroup as String] = newGroup
|
|
254
|
+
target[kSecValueData as String] = secret
|
|
255
|
+
target[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
256
|
+
let added = SecItemAdd(target as CFDictionary, nil)
|
|
257
|
+
guard added == errSecSuccess || added == errSecDuplicateItem else { return false }
|
|
258
|
+
|
|
259
|
+
return SecItemDelete(source as CFDictionary) == errSecSuccess
|
|
356
260
|
}
|
|
357
261
|
```
|
|
358
262
|
|
|
359
|
-
|
|
360
|
-
> Key point: The pattern above handles the basic sharing-context case; the canonical file covers multi-version migration coordination, safe deletion ordering, and CI implications.
|
|
361
|
-
|
|
362
|
-
### App transfers between teams break keychain access
|
|
363
|
-
|
|
364
|
-
Items are tied to the original Team ID. If an app is transferred to another developer account, keychain items stored under the old Team ID become inaccessible. Recommended workaround: transfer the app back, release an update that exports/migrates keychain data to an external store, then transfer again.
|
|
365
|
-
|
|
366
|
-
### Cross-developer sharing is impossible via access groups
|
|
367
|
-
|
|
368
|
-
The Team ID prefix enforcement, through code-signed provisioning profiles, prevents apps from different teams from accessing each other's keychain items. Cross-developer credential sharing requires iCloud Keychain + Associated Domains (password autofill based on web domain ownership).
|
|
263
|
+
## Lifecycle surprises
|
|
369
264
|
|
|
370
|
-
|
|
265
|
+
- Keychain items survive uninstalling the app. This is long-standing but
|
|
266
|
+
undocumented; an iOS 10.3 beta deleted them on removal and the change was reverted.
|
|
267
|
+
Apple DTS has warned it could change without notice.
|
|
268
|
+
- Deleting app A leaves group items in place for app B. Deleting every app in the group
|
|
269
|
+
still leaves the items as orphans; only erasing the device reliably removes them.
|
|
270
|
+
- A fresh install therefore can find old items. UserDefaults is removed on uninstall,
|
|
271
|
+
so a flag there tells a first launch apart:
|
|
371
272
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
## Build and Distribution Considerations
|
|
385
|
-
|
|
386
|
-
The entitlement format and Team ID prefix rules are consistent across all build configurations: development, Ad Hoc, TestFlight, and App Store distribution. The Team ID is inherent to the developer account and does not change between configurations.
|
|
387
|
-
|
|
388
|
-
However, the specific **provisioning profile** for each distribution type dictates which entitlements are allowed and embeds the correct `AppIdentifierPrefix`. Verify that the provisioning profile for each build type correctly authorizes the required access groups.
|
|
389
|
-
|
|
390
|
-
**Legacy account caveat:** Most modern accounts use the Team ID as the App ID prefix, but legacy accounts (pre-June 2011) may have per-app prefixes that differ from the Team ID. Adding capabilities like Associated Domains to one target but not another has been reported to change the prefix, causing `-34018` errors. Ensure all targets sharing a keychain group have identical capabilities.
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
|
-
## Debugging When Keychain Sharing Breaks
|
|
395
|
-
|
|
396
|
-
### Essential Error Codes
|
|
397
|
-
|
|
398
|
-
| Code | Constant | Meaning |
|
|
399
|
-
| ---------- | ----------------------------- | ---------------------------------------------------------------- |
|
|
400
|
-
| **0** | `errSecSuccess` | Operation succeeded |
|
|
401
|
-
| **-25299** | `errSecDuplicateItem` | Item exists; use `SecItemUpdate` instead |
|
|
402
|
-
| **-25300** | `errSecItemNotFound` | No match found; also returned pre-iOS 13 for unauthorized groups |
|
|
403
|
-
| **-34018** | `errSecMissingEntitlement` | App lacks entitlement for the specified access group |
|
|
404
|
-
| **-25308** | `errSecInteractionNotAllowed` | Device locked and item requires `WhenUnlocked` access |
|
|
405
|
-
| **-50** | `errSecParam` | Invalid parameter (missing `kSecClass`, wrong value types) |
|
|
406
|
-
|
|
407
|
-
Starting with **iOS 13**, querying an unauthorized access group returns the explicit `errSecMissingEntitlement` (-34018) instead of the ambiguous `errSecItemNotFound`. This makes debugging significantly easier on modern OS versions.
|
|
408
|
-
|
|
409
|
-
### Debugging Checklist
|
|
410
|
-
|
|
411
|
-
**1. Verify entitlements on the built binary** - not the `.entitlements` source file:
|
|
412
|
-
|
|
413
|
-
```bash
|
|
414
|
-
codesign -d --entitlements :- /path/to/YourApp.app
|
|
415
|
-
codesign -d --entitlements :- /path/to/YourExtension.appex
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
Compare the `keychain-access-groups` arrays - they must contain a common group.
|
|
419
|
-
|
|
420
|
-
**2. Inspect the provisioning profile:**
|
|
421
|
-
|
|
422
|
-
```bash
|
|
423
|
-
security cms -D -i YourApp.app/embedded.mobileprovision
|
|
273
|
+
```swift
|
|
274
|
+
func purgeLeftoversOnFreshInstall() {
|
|
275
|
+
let marker = "vault.didLaunch"
|
|
276
|
+
guard !UserDefaults.standard.bool(forKey: marker) else { return }
|
|
277
|
+
let scoped: [String: Any] = [
|
|
278
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
279
|
+
kSecAttrService as String: SharedVault.service,
|
|
280
|
+
kSecAttrAccessGroup as String: SharedVault.group
|
|
281
|
+
]
|
|
282
|
+
SecItemDelete(scoped as CFDictionary)
|
|
283
|
+
UserDefaults.standard.set(true, forKey: marker)
|
|
284
|
+
}
|
|
424
285
|
```
|
|
425
286
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
- `
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
287
|
+
Scope the delete by service and group; never wipe everything in a shared group,
|
|
288
|
+
because sibling apps may still be installed and using it. The complete launch
|
|
289
|
+
sequence is in [migration-legacy-stores.md](migration-legacy-stores.md).
|
|
290
|
+
- Transferring an app to another developer team orphans its items under the old Team
|
|
291
|
+
ID. The only workaround is to transfer back, ship an update that exports or migrates
|
|
292
|
+
the data somewhere else, then transfer again.
|
|
293
|
+
- Access groups cannot be shared with another developer's apps. Use iCloud Keychain
|
|
294
|
+
with Associated Domains for that.
|
|
295
|
+
|
|
296
|
+
## Platform notes
|
|
297
|
+
|
|
298
|
+
- **watchOS.** Since watchOS 2 the watch has its own keychain, not joined to the
|
|
299
|
+
iPhone's through access groups. Share through iCloud Keychain sync
|
|
300
|
+
(`kSecAttrSynchronizable: true`, watchOS 6.2+) or send data over WatchConnectivity.
|
|
301
|
+
Add Keychain Sharing to the WatchKit extension target, not the WatchKit app target.
|
|
302
|
+
- **Widgets.** Put the capabilities on the widget target itself. Keep auth tokens in
|
|
303
|
+
the shared keychain group, not in `UserDefaults(suiteName:)`.
|
|
304
|
+
- **App Group containers** get only standard file protection
|
|
305
|
+
(`NSFileProtectionCompleteUntilFirstUserAuthentication`). The Keychain is the safer
|
|
306
|
+
home for credentials.
|
|
307
|
+
|
|
308
|
+
## Signing and distribution
|
|
309
|
+
|
|
310
|
+
- The entitlement format and Team ID prefix are the same for development, Ad Hoc,
|
|
311
|
+
TestFlight and App Store builds.
|
|
312
|
+
- Each distribution profile decides which entitlements are allowed and embeds the
|
|
313
|
+
`AppIdentifierPrefix`. Check that every one of them authorizes the groups.
|
|
314
|
+
- Accounts created before June 2011 may have per-app prefixes that differ from the
|
|
315
|
+
Team ID. There are reports that adding a capability (Associated Domains, for example)
|
|
316
|
+
to one target but not another changes that target's prefix and produces -34018. Keep
|
|
317
|
+
capabilities identical across targets that share a group.
|
|
318
|
+
|
|
319
|
+
## When sharing breaks
|
|
320
|
+
|
|
321
|
+
| Status | Meaning |
|
|
322
|
+
| --- | --- |
|
|
323
|
+
| 0 `errSecSuccess` | Worked |
|
|
324
|
+
| -25299 `errSecDuplicateItem` | Item exists; update it instead |
|
|
325
|
+
| -25300 `errSecItemNotFound` | No match; before iOS 13 also the result for a group you are not entitled to |
|
|
326
|
+
| -34018 `errSecMissingEntitlement` | The target is not entitled to that group (iOS 13+) |
|
|
327
|
+
| -25308 `errSecInteractionNotAllowed` | Device locked and the item is WhenUnlocked |
|
|
328
|
+
| -50 `errSecParam` | Bad query: missing `kSecClass`, wrong value types, or sync combined with ThisDeviceOnly |
|
|
329
|
+
| -128 `errSecUserCanceled` | The user dismissed an authentication prompt |
|
|
330
|
+
|
|
331
|
+
From iOS 13 an unauthorized group reports -34018 rather than -25300.
|
|
332
|
+
|
|
333
|
+
Steps:
|
|
334
|
+
|
|
335
|
+
1. Inspect the entitlements actually signed into the build:
|
|
336
|
+
`codesign -d --entitlements :- MyApp.app` and the same for `MyWidget.appex`. The
|
|
337
|
+
arrays must share the group.
|
|
338
|
+
2. Decode the embedded profile: `security cms -D -i MyApp.app/embedded.mobileprovision`.
|
|
339
|
+
Check `keychain-access-groups`, `com.apple.security.application-groups` and
|
|
340
|
+
`com.apple.developer.team-identifier`.
|
|
341
|
+
3. Test on a physical device. The Simulator lacks real provisioning and can hide
|
|
342
|
+
entitlement problems.
|
|
343
|
+
4. Watch the device in Console.app filtered on "keychain"; failed entitlement checks
|
|
344
|
+
name the group.
|
|
345
|
+
5. Compare App ID prefixes across all targets.
|
|
346
|
+
|
|
347
|
+
| Test | Expected |
|
|
348
|
+
| --- | --- |
|
|
349
|
+
| Team-ID group item read by app, share extension, widget | All succeed |
|
|
350
|
+
| Item in a `group.` group | Works only with explicit `kSecAttrAccessGroup` |
|
|
351
|
+
| Synced item (not ThisDeviceOnly) | Appears on a second device on the same account |
|
|
352
|
+
| Extension without the entitlement | -34018 (or -25300 before iOS 13) |
|
|
353
|
+
|
|
354
|
+
## Threat model
|
|
355
|
+
|
|
356
|
+
- Synced items are end-to-end encrypted.
|
|
357
|
+
- Any device the user adds to their iCloud account can read, and could tamper with,
|
|
358
|
+
synced items. Sync as little as possible and validate what you read back.
|
|
359
|
+
- Every app in a group can read every item in it. Use the narrowest group.
|
|
360
|
+
- Orphaned items persist until the device is erased; weigh that for very sensitive
|
|
361
|
+
data.
|
|
362
|
+
|
|
363
|
+
## 2024 to 2026
|
|
364
|
+
|
|
365
|
+
- The `SecItem` API is unchanged. iOS 17/18 and macOS 14/15 added no sharing APIs, and
|
|
366
|
+
there is still no Apple Swift-native Keychain wrapper.
|
|
367
|
+
- The Passwords app in iOS 18 and macOS Sequoia is a front end to iCloud Keychain; it
|
|
368
|
+
changes no API or group behaviour.
|
|
369
|
+
- Passkey work (automatic upgrades, `ASCredentialExportManager` import and export)
|
|
370
|
+
happens at the credential-manager level and adds no sharing mechanism.
|
|
371
|
+
- The `Always` accessibility constants have been deprecated since iOS 12. Use
|
|
372
|
+
`AfterFirstUnlock` or `WhenPasscodeSetThisDeviceOnly`.
|
|
373
|
+
|
|
374
|
+
## Related
|
|
375
|
+
|
|
376
|
+
[keychain-fundamentals.md](keychain-fundamentals.md) for the SecItem contract,
|
|
377
|
+
[keychain-access-control.md](keychain-access-control.md) for accessibility classes,
|
|
378
|
+
[keychain-item-classes.md](keychain-item-classes.md) for primary keys,
|
|
379
|
+
[common-anti-patterns.md](common-anti-patterns.md) (#5, wrong data protection class),
|
|
380
|
+
and [credential-storage-patterns.md](credential-storage-patterns.md) for sharing tokens
|
|
381
|
+
with extensions.
|
|
382
|
+
|
|
383
|
+
## Checklist
|
|
384
|
+
|
|
385
|
+
- [ ] Code uses the full `TEAMID.` prefixed group string.
|
|
386
|
+
- [ ] Keychain Sharing is on every executable target.
|
|
387
|
+
- [ ] Keychain Sharing and App Groups formats are not mixed up; App Groups are not
|
|
388
|
+
used for Keychain on macOS.
|
|
389
|
+
- [ ] Default group understood: keychain groups, then app id, then app groups; an app
|
|
390
|
+
group is never the default.
|
|
391
|
+
- [ ] `kSecAttrAccessGroup` set explicitly on every add and query.
|
|
392
|
+
- [ ] Sync is opt-in, never with ThisDeviceOnly, and queries include
|
|
393
|
+
`kSecAttrSynchronizable: true` or `kSecAttrSynchronizableAny`.
|
|
394
|
+
- [ ] macOS calls set `kSecUseDataProtectionKeychain: true`.
|
|
395
|
+
- [ ] Leftover items after reinstall handled with a UserDefaults flag and a scoped
|
|
396
|
+
delete.
|
|
397
|
+
- [ ] Group changes done as read, add, then delete.
|
|
398
|
+
- [ ] Built entitlements checked with `codesign`; tested on a device.
|
|
399
|
+
- [ ] watchOS treated as a separate keychain; shared through sync or WatchConnectivity.
|