@mmerterden/multi-agent-pipeline 20.6.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.
Files changed (326) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +328 -329
  5. package/package.json +2 -2
  6. package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
  7. package/pipeline/scripts/_notices.mjs +12 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  9. package/pipeline/skills/.skill-manifest.json +106 -106
  10. package/pipeline/skills/shared/README.md +71 -71
  11. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
  16. package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
  17. package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
  18. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  19. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  20. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  21. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  22. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  23. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  24. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  25. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  26. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  27. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  28. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  29. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  30. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  31. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  32. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  33. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  34. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  35. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  37. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  38. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  39. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  40. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  41. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  42. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  43. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  44. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  45. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  46. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  47. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  48. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  49. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  50. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  51. package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
  52. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  53. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  54. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  55. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
  56. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  57. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  58. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  59. package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
  60. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
  61. package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
  62. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  63. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  64. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  65. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  66. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  67. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  68. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  69. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  70. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  71. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  72. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  73. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  74. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  75. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  76. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  77. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  78. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  79. package/pipeline/skills/shared/external/council/SKILL.md +1 -0
  80. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  81. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  82. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  83. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
  84. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
  85. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  86. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  87. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  88. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  89. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  90. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  91. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  92. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  93. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  94. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  95. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  96. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  97. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  98. package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
  99. package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
  100. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
  101. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  102. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  103. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  104. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  105. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  106. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  107. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
  108. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
  109. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  110. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  111. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  112. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  113. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  114. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
  115. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  116. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  117. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  118. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  119. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
  120. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  121. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  122. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  123. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  124. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  125. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  126. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  127. package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
  128. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  129. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  130. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  131. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  132. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  133. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  134. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
  135. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
  136. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
  137. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
  138. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
  139. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
  140. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
  141. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
  142. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
  143. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
  144. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
  145. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
  146. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
  147. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
  148. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
  149. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
  150. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  151. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  152. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  153. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  154. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  155. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  156. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  157. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  158. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  159. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  160. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  161. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  162. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  163. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
  164. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  165. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  166. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  167. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  168. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  169. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  170. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  171. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  172. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  173. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  174. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  175. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  176. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  177. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  178. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  179. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  180. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  181. package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  183. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  184. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  185. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  186. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
  187. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
  188. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  189. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  190. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  191. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
  192. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
  193. package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
  194. package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
  195. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  196. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  197. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  198. package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
  199. package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
  200. package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
  201. package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
  202. package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
  203. package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
  204. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
  205. package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
  206. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  207. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  208. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  209. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  210. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  211. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  212. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  213. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  214. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  215. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  216. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  217. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  218. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  219. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  220. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  221. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  222. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  223. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  224. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  225. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  226. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  227. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  228. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  229. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  230. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  231. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  232. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  233. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  234. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  235. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  236. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  237. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  238. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  239. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  240. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  241. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  242. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  243. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  244. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  245. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  246. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  247. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  248. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  249. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  250. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  251. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  252. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  253. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  254. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  255. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  256. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  257. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  258. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  259. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  260. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  261. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  262. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  263. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  264. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  265. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  266. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  267. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  268. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  269. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  270. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  271. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  272. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  273. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  274. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  275. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  276. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  277. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  278. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  279. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  280. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  281. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  282. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  283. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  284. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  285. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  286. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  287. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  288. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  289. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  290. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  291. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  292. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  293. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  294. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  295. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  296. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  297. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  298. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  299. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  300. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  301. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  302. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  303. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  304. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  305. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  306. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  307. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  308. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  309. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  310. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
  311. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
  312. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  313. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  314. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
  315. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  316. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  317. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  318. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
  319. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  320. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  321. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
  322. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
  323. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
  324. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  325. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  326. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,708 +1,438 @@
1
- # Common Anti-Patterns
1
+ # Insecure Patterns in Generated iOS Code
2
2
 
3
- > **Scope:** The 10 most dangerous security anti-patterns that AI coding assistants generate for iOS apps. Each entry includes the vulnerability explanation, realistic ❌ insecure code, ✅ correct replacement, detection heuristic, and OWASP risk mapping. This is the skill's backbone - the single most important file for correcting AI-generated security code.
4
- >
5
- > **Cross-references:** `biometric-authentication.md` (anti-pattern #3 deep dive), `keychain-fundamentals.md` (anti-pattern #4 CRUD patterns), `keychain-access-control.md` (anti-pattern #5 protection classes), `cryptokit-symmetric.md` (anti-patterns #6-7), `credential-storage-patterns.md` (anti-patterns #1-2 token lifecycle), `migration-legacy-stores.md` (anti-pattern #9 first-launch cleanup), `compliance-owasp-mapping.md` (full OWASP/MASVS mapping).
3
+ This is the review backbone of the skill. It lists the ten security mistakes that code generators produce most often on Apple platforms. Every entry gives the reason it is dangerous, an insecure sample, a corrected sample, a way to detect it, and its OWASP mapping.
6
4
 
7
- ---
5
+ Deeper material per entry: [credential-storage-patterns.md](credential-storage-patterns.md) (1 and 2), [biometric-authentication.md](biometric-authentication.md) (3), [keychain-fundamentals.md](keychain-fundamentals.md) (4), [keychain-access-control.md](keychain-access-control.md) (5), [cryptokit-symmetric.md](cryptokit-symmetric.md) (6 and 7), [migration-legacy-stores.md](migration-legacy-stores.md) (9), and [compliance-owasp-mapping.md](compliance-owasp-mapping.md) for the complete standards mapping.
8
6
 
9
7
  ## Contents
10
8
 
11
- - [Why AI Generates Insecure iOS Code](#why-ai-generates-insecure-ios-code)
12
- - [Anti-Pattern #1 - Storing Secrets in UserDefaults](#anti-pattern-1-storing-secrets-in-userdefaults)
13
- - [Anti-Pattern #2 - Hardcoded API Keys](#anti-pattern-2-hardcoded-api-keys)
14
- - [Anti-Pattern #3 - LAContext-Only Biometric Authentication](#anti-pattern-3-lacontext-only-biometric-authentication)
15
- - [Anti-Pattern #4 - Ignoring SecItem Error Codes](#anti-pattern-4-ignoring-secitem-error-codes)
16
- - [Anti-Pattern #5 - Wrong or Missing Data Protection Class](#anti-pattern-5-wrong-or-missing-data-protection-class)
17
- - [Anti-Pattern #6 - Nonce Reuse in AES-GCM](#anti-pattern-6-nonce-reuse-in-aes-gcm)
18
- - [Anti-Pattern #7 - MD5/SHA-1 for Security Purposes](#anti-pattern-7-md5sha-1-for-security-purposes)
19
- - [Anti-Pattern #8 - Logging Sensitive Data](#anti-pattern-8-logging-sensitive-data)
20
- - [Anti-Pattern #9 - Not Clearing Keychain on First Launch](#anti-pattern-9-not-clearing-keychain-on-first-launch)
21
- - [Anti-Pattern #10 - Non-Cryptographic RNG for Security Operations](#anti-pattern-10-non-cryptographic-rng-for-security-operations)
22
- - [Quick Reference Matrix](#quick-reference-matrix)
23
- - [CI/CD Detection Strategy](#cicd-detection-strategy)
24
- - [iOS 26 / WWDC 2025 Implications](#ios-26-wwdc-2025-implications)
25
- - [Summary Checklist](#summary-checklist)
9
+ - [Why generated code drifts toward insecure defaults](#why-generated-code-drifts-toward-insecure-defaults)
10
+ - [1. Secrets in UserDefaults](#1-secrets-in-userdefaults)
11
+ - [2. Hardcoded API keys](#2-hardcoded-api-keys)
12
+ - [3. Biometric check that only returns a Boolean](#3-biometric-check-that-only-returns-a-boolean)
13
+ - [4. Ignoring SecItem status codes](#4-ignoring-secitem-status-codes)
14
+ - [5. Missing or weak data protection class](#5-missing-or-weak-data-protection-class)
15
+ - [6. Reusing an AES-GCM nonce](#6-reusing-an-aes-gcm-nonce)
16
+ - [7. MD5 or SHA-1 where security matters](#7-md5-or-sha-1-where-security-matters)
17
+ - [8. Secrets in logs](#8-secrets-in-logs)
18
+ - [9. No keychain cleanup after reinstall](#9-no-keychain-cleanup-after-reinstall)
19
+ - [10. Weak randomness for security values](#10-weak-randomness-for-security-values)
20
+ - [Quick reference](#quick-reference)
21
+ - [Automating detection in CI](#automating-detection-in-ci)
22
+ - [What iOS 26 changes](#what-ios-26-changes)
23
+ - [Checklist](#checklist)
26
24
 
27
- ## Why AI Generates Insecure iOS Code
25
+ ## Why generated code drifts toward insecure defaults
28
26
 
29
- AI assistants optimize for functional correctness, not security - reproducing the most common patterns from training data, which are overwhelmingly insecure-by-default. Veracode's 2025 analysis: 45% of AI-generated code fails security tests. Cybernews: 815,000+ hardcoded secrets across 156,000 iOS apps (71% leaking ≥1 credential). Stanford: developers using AI write less secure code yet feel more confident.
27
+ Generators are rewarded for code that compiles and works, and their training data is full of tutorials that store tokens in `UserDefaults` or gate screens on a biometric Boolean. The insecure version is usually the shortest one, so it wins.
30
28
 
31
- Apple's security primitives (Keychain, CryptoKit, Secure Enclave) are excellent but AI consistently bypasses them. CISA/FBI classified hardcoded credentials as elevating "risk to national security" in their January 2025 Bad Practices v2.0 (CWE-798).
29
+ Published numbers point the same way. A 2025 industry study found that 45% of AI-produced code failed security tests. A 2025 scan of about 156,000 iOS apps found more than 815,000 hardcoded secrets, with 71% of apps leaking at least one. An academic user study found that developers with an AI assistant wrote less secure code while feeling more confident in it. The CISA and FBI "Product Security Bad Practices" guidance (version 2.0, January 2025) names hardcoded credentials (CWE-798) as a practice that raises risk to national security.
32
30
 
33
- **OWASP standard:** Mobile Top 10 (2024) with MASTG v2 test IDs. Legacy MSTG-\* identifiers noted where commonly referenced.
31
+ Mappings below use the OWASP Mobile Top 10 (2024) and MASTG v2 test identifiers, with legacy MSTG identifiers in parentheses where auditors still quote them.
34
32
 
35
- ---
33
+ The samples share two small error types: `KeychainFailure(status)` wraps an `OSStatus`, and `VaultError` carries app-level cases such as `deviceLocked` and `authenticationFailed`.
36
34
 
37
- ## Anti-Pattern #1 - Storing Secrets in UserDefaults
35
+ ## 1. Secrets in UserDefaults
38
36
 
39
- **Severity:** CRITICAL | **OWASP:** M9 (Insecure Data Storage) | **Fix effort:** Medium
37
+ Severity: Critical. OWASP: M9. Fix effort: Medium.
40
38
 
41
- UserDefaults writes to an unencrypted XML plist at `~/Library/Preferences/{BUNDLE_ID}.plist`. Apple's documentation: "Don't store personal or sensitive information as settings." Readable from unencrypted backups, jailbroken devices (Objection `ios nsuserdefaults get`), and third-party SDKs. **SwiftUI's `@AppStorage` is a wrapper over `UserDefaults`** - it has identical security properties and must never be used for tokens, keys, or credentials.
39
+ `UserDefaults` writes an unencrypted property list to `Library/Preferences/<bundle id>.plist` inside the container, and Apple's documentation says not to keep sensitive data there. The file shows up in unencrypted backups, on jailbroken devices (`objection` reads it with `ios nsuserdefaults get`), and to any third-party SDK in the process. SwiftUI's `@AppStorage` is the same store with a property wrapper around it, so it is equally unsuitable for tokens, keys or credentials.
42
40
 
43
- **❌ Insecure - AI-generated pattern:**
41
+ Insecure:
44
42
 
45
43
  ```swift
46
- // Plaintext on disk, readable from backups
47
- func saveAuthToken(_ token: String) {
48
- UserDefaults.standard.set(token, forKey: "userAuthToken")
49
- UserDefaults.standard.set(refreshToken, forKey: "refreshToken")
50
- UserDefaults.standard.synchronize()
51
- }
52
-
53
- let token = UserDefaults.standard.string(forKey: "userAuthToken")
44
+ let prefs = UserDefaults.standard
45
+ prefs.set(session.accessToken, forKey: "access_token")
46
+ prefs.set(session.refreshToken, forKey: "refresh_token")
47
+ prefs.synchronize()
54
48
  ```
55
49
 
56
- **✅ Secure - Keychain with add-or-update:**
50
+ Secure:
57
51
 
58
52
  ```swift
59
- func saveTokenToKeychain(_ token: Data, account: String) throws {
60
- let query: [String: Any] = [
61
- kSecClass as String: kSecClassGenericPassword,
62
- kSecAttrService as String: "com.myapp.auth",
63
- kSecAttrAccount as String: account,
64
- kSecValueData as String: token,
65
- kSecAttrAccessible as String:
66
- kSecAttrAccessibleWhenUnlockedThisDeviceOnly
67
- ]
68
- let status = SecItemAdd(query as CFDictionary, nil)
53
+ func storeAccessToken(_ token: String) throws {
54
+ let identity: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
55
+ kSecAttrService: "com.example.courier",
56
+ kSecAttrAccount: "access_token"]
57
+ var insert = identity
58
+ insert[kSecValueData] = Data(token.utf8)
59
+ insert[kSecAttrAccessible] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
60
+ var status = SecItemAdd(insert as CFDictionary, nil)
69
61
  if status == errSecDuplicateItem {
70
- // Full add-or-update pattern → see anti-pattern #4
71
- let search: [String: Any] = [
72
- kSecClass as String: kSecClassGenericPassword,
73
- kSecAttrService as String: "com.myapp.auth",
74
- kSecAttrAccount as String: account
75
- ]
76
- let updateStatus = SecItemUpdate(
77
- search as CFDictionary,
78
- [kSecValueData as String: token] as CFDictionary)
79
- guard updateStatus == errSecSuccess else {
80
- throw KeychainError.unexpectedStatus(updateStatus)
81
- }
82
- } else if status != errSecSuccess {
83
- throw KeychainError.unexpectedStatus(status)
62
+ let changes: [CFString: Any] = [kSecValueData: Data(token.utf8)]
63
+ status = SecItemUpdate(identity as CFDictionary, changes as CFDictionary)
84
64
  }
65
+ guard status == errSecSuccess else { throw KeychainFailure(status) }
85
66
  }
86
67
  ```
87
68
 
88
- **MASTG tests:** MASTG-TEST-0300, MASTG-TEST-0302. **MASWE:** MASWE-0006. **Legacy:** MSTG-STORAGE-1.
69
+ Standards: MASTG-TEST-0300, MASTG-TEST-0302, MASWE-0006 (legacy MSTG-STORAGE-1).
89
70
 
90
- **Detection heuristic:**
71
+ Detection: case-insensitive search of `.swift` files for lines containing `UserDefaults` together with token, password, secret, credential, auth, session, api key, jwt or bearer.
91
72
 
92
- ```bash
93
- grep -rn "UserDefaults" --include="*.swift" | \
94
- grep -iE "token|password|secret|credential|auth|session|api.?key|jwt|bearer"
73
+ ```sh
74
+ grep -rniE 'UserDefaults.*(token|password|secret|credential|auth|session|api_?key|jwt|bearer)' --include='*.swift' .
95
75
  ```
96
76
 
97
- ---
98
-
99
- ## Anti-Pattern #2 - Hardcoded API Keys
77
+ ## 2. Hardcoded API keys
100
78
 
101
- **Severity:** CRITICAL | **OWASP:** M1 (Improper Credential Usage) | **Fix effort:** High
79
+ Severity: Critical. OWASP: M1. Fix effort: High.
102
80
 
103
- API keys compiled into Swift appear in the binary's `__TEXT.__cstring` segment - `strings MyApp.app/MyApp` extracts them instantly. Even `.xcconfig` or `Info.plist` values ship inside the IPA. Cybernews found 78,800 Google API keys across 156,000 iOS apps.
81
+ A Swift string literal ends up in the binary's `__TEXT.__cstring` section, where `strings` prints it in seconds. Values in `.xcconfig` files or `Info.plist` ship inside the IPA just the same. The 2025 scan mentioned above found about 78,800 keys for a single cloud provider's APIs alone.
104
82
 
105
- **❌ Insecure - AI-generated pattern:**
83
+ Insecure:
106
84
 
107
85
  ```swift
108
- class PaymentService {
109
- private let stripeKey = "sk_live_51H7bK2E..." // In binary
110
- private let firebaseKey = "AIzaSyB..." // In binary
111
-
112
- func charge(amount: Int) async throws {
113
- var request = URLRequest(
114
- url: URL(string: "https://api.stripe.com/v1/charges")!)
115
- request.setValue("Bearer \(stripeKey)",
116
- forHTTPHeaderField: "Authorization")
117
- let (data, _) = try await URLSession.shared.data(for: request)
118
- }
86
+ enum Secrets {
87
+ static let paymentsKey = "sk_live_EXAMPLEONLY000000000000"
88
+ static let backendKey = "AIzaEXAMPLEONLY00000000000000000"
119
89
  }
120
-
121
- // Also dangerous: key in Info.plist or .xcconfig bundled in app
122
- let key = Bundle.main.infoDictionary?["API_KEY"] as? String
90
+ request.setValue("Bearer \(Secrets.paymentsKey)", forHTTPHeaderField: "Authorization")
91
+ let plistKey = Bundle.main.infoDictionary?["API_KEY"] as? String
123
92
  ```
124
93
 
125
- **✅ Secure - server proxy + Keychain cache:**
94
+ Secure, in order of preference:
126
95
 
127
- ```swift
128
- class SecureAPIKeyManager {
129
- static let shared = SecureAPIKeyManager()
130
-
131
- /// Best: proxy through your server (key never on device)
132
- func secureRequest(endpoint: String, params: [String: Any]) async throws -> Data {
133
- var request = URLRequest(
134
- url: URL(string: "https://api.myserver.com/proxy/\(endpoint)")!)
135
- request.httpMethod = "POST"
136
- request.httpBody = try JSONSerialization.data(withJSONObject: params)
137
- let (data, _) = try await URLSession.shared.data(for: request)
138
- return data
139
- }
96
+ 1. A server-side proxy holds the key and the app calls the proxy, so the key never reaches the device.
97
+ 2. If the device genuinely needs a key, fetch it at runtime from your server after the user or device is authenticated and cache it in the keychain.
140
98
 
141
- /// If client must hold key: fetch at runtime, cache in Keychain
142
- func getAPIKey() async throws -> String {
143
- if let cached = try? readFromKeychain(service: "api-keys", account: "primary") {
144
- return String(data: cached, encoding: .utf8)!
145
- }
146
- let (data, _) = try await URLSession.shared.data(
147
- from: URL(string: "https://api.myserver.com/config/key")!)
148
- try saveToKeychain(data, service: "api-keys", account: "primary")
149
- return String(data: data, encoding: .utf8)!
150
- }
99
+ ```swift
100
+ func quoteShipment(_ order: ShipmentRequest) async throws -> Quote {
101
+ var request = URLRequest(url: quotesEndpoint)
102
+ request.httpMethod = "POST"
103
+ request.httpBody = try JSONEncoder().encode(order)
104
+ request.setValue("Bearer \(try await session.userAccessToken())", forHTTPHeaderField: "Authorization")
105
+ let (body, _) = try await URLSession.shared.data(for: request)
106
+ return try JSONDecoder().decode(Quote.self, from: body)
151
107
  }
152
108
  ```
153
109
 
154
- Apple's DeviceCheck and App Attest frameworks provide server-side device verification without embedding secrets. WWDC 2019-709 advises storing credentials in Keychain, not in code.
110
+ DeviceCheck and App Attest let the server verify a genuine app instance without shipping any secret (details in the `device-integrity` skill). WWDC 2019 session 709 states the rule plainly: credentials belong in the keychain.
155
111
 
156
- **MASTG tests:** MASTG-TEST-0213, MASTG-TEST-0214. **MASWE:** MASWE-0005. **Legacy:** MSTG-STORAGE-12. **CISA/FBI:** CWE-798 - Product Security Bad Practices v2.0 (January 2025).
112
+ Standards: MASTG-TEST-0213, MASTG-TEST-0214, MASWE-0005 (legacy MSTG-STORAGE-12); CWE-798 per the CISA/FBI guidance.
157
113
 
158
- **Detection heuristic:**
114
+ Detection: in `.swift`, `.plist` and `.xcconfig` files, look for `let` constants whose name contains "key" assigned a literal of 20 or more alphanumeric characters, and for the prefixes `"sk_live_`, `"pk_live_`, `"AIza` and `"AKIA`.
159
115
 
160
- ```bash
161
- grep -rn 'let.*[Kk]ey.*=.*"[A-Za-z0-9_\-]\{20,\}"' --include="*.swift"
162
- grep -rn '"sk_live_\|"pk_live_\|"AIza[A-Za-z0-9]\|"AKIA[A-Z0-9]' \
163
- --include="*.swift" --include="*.plist" --include="*.xcconfig"
116
+ ```sh
117
+ grep -rnE 'let [A-Za-z]*[Kk]ey[A-Za-z]* *= *"[A-Za-z0-9_-]{20,}"' --include='*.swift' .
118
+ grep -rnE '"(sk_live_|pk_live_|AIza|AKIA)' --include='*.swift' --include='*.plist' --include='*.xcconfig' .
164
119
  ```
165
120
 
166
- ---
121
+ ## 3. Biometric check that only returns a Boolean
167
122
 
168
- ## Anti-Pattern #3 - LAContext-Only Biometric Authentication
123
+ Severity: Critical. OWASP: M3. Fix effort: Medium.
169
124
 
170
- **Severity:** CRITICAL | **OWASP:** M3 (Insecure Authentication) | **Fix effort:** Medium
125
+ This is the insecure pattern generators reproduce most. `evaluatePolicy` hands back a `Bool` in the app's own process, and one Frida command or `objection`'s `ios ui biometrics_bypass` flips it. MASTG's position is that biometric authentication must unlock something in the keychain, not just a screen.
171
126
 
172
- Using `LAContext.evaluatePolicy()` alone is the single most reproduced insecure pattern across iOS tutorials. The method returns a simple boolean callback in user-space - no cryptographic binding. Frida forces `success = true` in one command; Objection packages this as `ios ui biometrics_bypass`. OWASP MASTG: "Biometric authentication must be based on unlocking the keychain." Full deep dive: see `biometric-authentication.md`.
173
-
174
- **❌ Insecure - AI-generated pattern:**
127
+ Insecure:
175
128
 
176
129
  ```swift
177
- func authenticateUser(completion: @escaping (Bool) -> Void) {
178
- let context = LAContext()
179
- context.evaluatePolicy(
180
- .deviceOwnerAuthenticationWithBiometrics,
181
- localizedReason: "Authenticate to access your account"
182
- ) { success, authError in
183
- DispatchQueue.main.async {
184
- if success {
185
- self.showSensitiveData() // Gated on a hookable boolean
186
- }
187
- completion(success)
188
- }
189
- }
130
+ LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
131
+ localizedReason: "Show your balance") { granted, _ in
132
+ if granted { DispatchQueue.main.async { self.revealBalance() } }
190
133
  }
191
134
  ```
192
135
 
193
- **✅ Secure - Keychain + SecAccessControl hardware binding:**
136
+ Secure: bind the secret to a keychain item whose access control demands biometrics, and read it with an `LAContext` attached.
194
137
 
195
138
  ```swift
196
- // STORE: biometric-protected via Secure Enclave
197
- func storeWithBiometric(secret: Data, account: String) throws {
198
- let access = SecAccessControlCreateWithFlags(
199
- nil,
200
- kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
201
- .biometryCurrentSet, nil)!
202
-
203
- let query: [String: Any] = [
204
- kSecClass as String: kSecClassGenericPassword,
205
- kSecAttrService as String: "com.myapp.biometric",
206
- kSecAttrAccount as String: account,
207
- kSecAttrAccessControl as String: access,
208
- kSecValueData as String: secret
209
- ]
210
- let status = SecItemAdd(query as CFDictionary, nil)
211
- guard status == errSecSuccess || status == errSecDuplicateItem else {
212
- throw KeychainError.unexpectedStatus(status)
139
+ func sealBalanceKey(_ secret: Data) throws {
140
+ guard let gate = SecAccessControlCreateWithFlags(nil,
141
+ kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .biometryCurrentSet, nil) else {
142
+ throw KeychainFailure(errSecParam)
143
+ }
144
+ let identity: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
145
+ kSecAttrService: "com.example.courier",
146
+ kSecAttrAccount: "balance-key"]
147
+ var insert = identity
148
+ insert[kSecValueData] = secret
149
+ insert[kSecAttrAccessControl] = gate
150
+ var status = SecItemAdd(insert as CFDictionary, nil)
151
+ if status == errSecDuplicateItem {
152
+ status = SecItemUpdate(identity as CFDictionary, [kSecValueData: secret] as CFDictionary)
213
153
  }
154
+ guard status == errSecSuccess else { throw KeychainFailure(status) }
214
155
  }
215
156
 
216
- // READ: Secure Enclave enforces biometric before releasing data
217
- func readWithBiometric(account: String) throws -> Data {
157
+ func openBalanceKey() throws -> Data {
218
158
  let context = LAContext()
219
- context.localizedReason = "Access your secure data"
220
- let query: [String: Any] = [
221
- kSecClass as String: kSecClassGenericPassword,
222
- kSecAttrService as String: "com.myapp.biometric",
223
- kSecAttrAccount as String: account,
224
- kSecReturnData as String: true,
225
- kSecMatchLimit as String: kSecMatchLimitOne,
226
- kSecUseAuthenticationContext as String: context
227
- ]
228
- var result: AnyObject?
229
- let status = SecItemCopyMatching(query as CFDictionary, &result)
230
- guard status == errSecSuccess, let data = result as? Data else {
231
- throw KeychainError.unexpectedStatus(status)
232
- }
233
- return data // Only returned after hardware biometric validation
159
+ context.localizedReason = "Show your balance"
160
+ let lookup: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
161
+ kSecAttrService: "com.example.courier",
162
+ kSecAttrAccount: "balance-key",
163
+ kSecReturnData: true,
164
+ kSecUseAuthenticationContext: context]
165
+ var out: CFTypeRef?
166
+ let status = SecItemCopyMatching(lookup as CFDictionary, &out)
167
+ guard status == errSecSuccess, let secret = out as? Data else { throw KeychainFailure(status) }
168
+ return secret
234
169
  }
235
170
  ```
236
171
 
237
- The `.biometryCurrentSet` flag invalidates the item if biometrics change, preventing an attacker with physical access from enrolling their own biometric. Objection's documentation confirms this bypass "will NOT work" with keychain-bound biometric items.
238
-
239
- **MASTG tests:** MASTG-TEST-0266, MASTG-TEST-0267. **MASWE:** MASWE-0044. **Legacy:** MSTG-AUTH-8. **WWDC:** 2014-711 introduced `SecAccessControlCreateWithFlags`.
172
+ A duplicate is updated, never silently accepted. If the item needs a different access control than the one it was created with, delete it and add it again, because that is the one case where add-or-update does not apply.
240
173
 
241
- **Detection heuristic:**
174
+ `.biometryCurrentSet` invalidates the item when the enrolled biometrics change, so an attacker who adds their own face or finger to a stolen, unlocked device gets nothing. `objection`'s own documentation notes that its bypass does not work against keychain-bound items.
242
175
 
243
- ```bash
244
- # evaluatePolicy without SecAccessControl → insecure
245
- grep -rn "evaluatePolicy" --include="*.swift" -l | \
246
- xargs grep -L "SecAccessControlCreateWithFlags"
247
- # Verify secure pattern exists
248
- grep -rn "\.biometryCurrentSet\|\.biometryAny" --include="*.swift"
249
- ```
176
+ Standards: MASTG-TEST-0266, MASTG-TEST-0267, MASWE-0044 (legacy MSTG-AUTH-8). `SecAccessControlCreateWithFlags` arrived with WWDC 2014 session 711.
250
177
 
251
- ---
178
+ Detection: files that call `evaluatePolicy` but never `SecAccessControlCreateWithFlags`; also confirm whether `.biometryCurrentSet` or `.biometryAny` appears at all.
252
179
 
253
- ## Anti-Pattern #4 - Ignoring SecItem Error Codes
180
+ ```sh
181
+ grep -rl 'evaluatePolicy' --include='*.swift' . | xargs grep -L 'SecAccessControlCreateWithFlags'
182
+ ```
254
183
 
255
- **Severity:** HIGH | **OWASP:** M8 (Security Misconfiguration) | **Fix effort:** Low
184
+ ## 4. Ignoring SecItem status codes
256
185
 
257
- `errSecDuplicateItem` (OSStatus -25299) is the most common Keychain failure. When `SecItemAdd` hits a duplicate, it silently discards the new value. Password updates never persist, refreshed tokens are lost, and auth breaks in hard-to-debug ways. Other critical codes: `errSecItemNotFound` (-25300), `errSecAuthFailed` (-25293), `errSecInteractionNotAllowed` (-25308).
186
+ Severity: High. OWASP: M8 (Security Misconfiguration). Fix effort: Low.
258
187
 
259
- Full CRUD patterns: see `keychain-fundamentals.md`.
188
+ The usual casualty is `errSecDuplicateItem` (-25299): the add fails, nobody looks, and the new value is dropped while the old one stays. Other statuses that must be handled: `errSecItemNotFound` (-25300), `errSecAuthFailed` (-25293), `errSecInteractionNotAllowed` (-25308). A cancelled authentication prompt surfaces as `errSecUserCanceled` (-128).
260
189
 
261
- **❌ Insecure - AI-generated pattern:**
190
+ Insecure:
262
191
 
263
192
  ```swift
264
- func saveToken(_ token: Data) {
265
- let query: [String: Any] = [
266
- kSecClass as String: kSecClassGenericPassword,
267
- kSecAttrService as String: "com.app.auth",
268
- kSecAttrAccount as String: "accessToken",
269
- kSecValueData as String: token
270
- ]
271
- SecItemAdd(query as CFDictionary, nil) // Return value ignored!
272
- }
193
+ SecItemAdd(insert as CFDictionary, nil)
273
194
  ```
274
195
 
275
- **✅ Secure - OSStatus switch with add-or-update:**
196
+ Secure:
276
197
 
277
198
  ```swift
278
- func saveToKeychain(value: Data, service: String, account: String) throws {
279
- let query: [String: Any] = [
280
- kSecClass as String: kSecClassGenericPassword,
281
- kSecAttrService as String: service,
282
- kSecAttrAccount as String: account,
283
- kSecValueData as String: value,
284
- kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
285
- ]
286
- let status = SecItemAdd(query as CFDictionary, nil)
287
- switch status {
288
- case errSecSuccess: return
289
- case errSecDuplicateItem:
290
- let search: [String: Any] = [
291
- kSecClass as String: kSecClassGenericPassword,
292
- kSecAttrService as String: service,
293
- kSecAttrAccount as String: account
294
- ]
295
- let updateStatus = SecItemUpdate(
296
- search as CFDictionary, [kSecValueData as String: value] as CFDictionary)
297
- guard updateStatus == errSecSuccess else { throw KeychainError.updateFailed(updateStatus) }
298
- case errSecInteractionNotAllowed: throw KeychainError.deviceLocked
299
- case errSecAuthFailed: throw KeychainError.authenticationFailed
300
- default: throw KeychainError.unexpectedStatus(status)
301
- }
199
+ switch SecItemAdd(insert as CFDictionary, nil) {
200
+ case errSecSuccess:
201
+ break
202
+ case errSecDuplicateItem:
203
+ let status = SecItemUpdate(identity as CFDictionary, [kSecValueData: payload] as CFDictionary)
204
+ guard status == errSecSuccess else { throw KeychainFailure(status) }
205
+ case errSecInteractionNotAllowed:
206
+ throw VaultError.deviceLocked
207
+ case errSecAuthFailed, errSecUserCanceled:
208
+ throw VaultError.authenticationFailed
209
+ case let other:
210
+ throw KeychainFailure(other)
302
211
  }
303
212
  ```
304
213
 
305
- Critical detail: `SecItemUpdate` takes two dictionaries - search query (without `kSecValueData`) and attributes to update. Passing the full query as the search parameter is a common mistake.
214
+ `SecItemUpdate` takes two dictionaries: a search query with no `kSecValueData`, and the attributes to change. Passing the full add dictionary as the search query is a frequent mistake that makes the update match nothing.
306
215
 
307
- **MASTG tests:** MASTG-TEST-0300, MASTG-TEST-0301. **Legacy:** MASVS-STORAGE-2.
216
+ Standards: MASTG-TEST-0300, MASTG-TEST-0301 (legacy MASVS-STORAGE-2).
308
217
 
309
- **Detection heuristic:**
218
+ Detection: files containing `SecItemAdd` with no mention of `errSecDuplicateItem` or -25299, and `SecItemAdd(` calls whose result is neither assigned nor compared.
310
219
 
311
- ```bash
312
- grep -rn "SecItemAdd" --include="*.swift" -l | \
313
- xargs grep -L "errSecDuplicateItem\|DuplicateItem\|-25299"
314
- grep -rn "SecItemAdd(" --include="*.swift" | \
315
- grep -v "let\|var\|status\|=\|switch\|if\|guard"
316
- ```
220
+ ## 5. Missing or weak data protection class
317
221
 
318
- ---
222
+ Severity: High. OWASP: M9. Fix effort: Low.
319
223
 
320
- ## Anti-Pattern #5 - Wrong or Missing Data Protection Class
224
+ Leaving `kSecAttrAccessible` out means inheriting a default that may not fit. `kSecAttrAccessibleAlways` (deprecated in iOS 12) keeps the item decryptable while the device is locked. A class without the `ThisDeviceOnly` suffix is included in backups.
321
225
 
322
- **Severity:** HIGH | **OWASP:** M9 (Insecure Data Storage) | **Fix effort:** Low
226
+ Pick deliberately:
323
227
 
324
- Omitting `kSecAttrAccessible` inherits a default that may be insufficient. Using deprecated `kSecAttrAccessibleAlways` (deprecated iOS 12) leaves data decryptable on a locked device. Missing `ThisDeviceOnly` suffix means items are included in backups. Full protection class guide: see `keychain-access-control.md`.
228
+ | Item | Class |
229
+ | --- | --- |
230
+ | Passwords, access tokens used in the foreground | `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` |
231
+ | Highest-sensitivity secrets | `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly` |
232
+ | Values background code must read (push tokens, refresh tokens) | `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` |
325
233
 
326
- **❌ Insecure - AI-generated patterns:**
234
+ WWDC 2014 session 711's advice still holds: choose the most restrictive option that works for the feature.
327
235
 
328
- ```swift
329
- // Missing kSecAttrAccessible entirely
330
- let query: [String: Any] = [
331
- kSecClass as String: kSecClassGenericPassword,
332
- kSecAttrAccount as String: "user_password",
333
- kSecValueData as String: passwordData
334
- ]
335
- SecItemAdd(query as CFDictionary, nil)
336
-
337
- // Deprecated - accessible when device is locked
338
- kSecAttrAccessible as String: kSecAttrAccessibleAlways
339
- ```
340
-
341
- **✅ Secure - selection by use case:**
342
-
343
- ```swift
344
- // Passwords, auth tokens (foreground-only)
345
- kSecAttrAccessible as String:
346
- kSecAttrAccessibleWhenUnlockedThisDeviceOnly
347
-
348
- // Highest sensitivity - requires passcode to exist
349
- kSecAttrAccessible as String:
350
- kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
236
+ Standards: MASTG-TEST-0299 (legacy MASTG-STORAGE-3).
351
237
 
352
- // Background-access items (push tokens, refresh tokens)
353
- kSecAttrAccessible as String:
354
- kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
355
- ```
356
-
357
- WWDC 2014-711: "Always use the most restrictive option that makes sense for your app."
358
-
359
- **MASTG test:** MASTG-TEST-0299. **Legacy:** MASTG-STORAGE-3.
360
-
361
- **Detection heuristic:**
362
-
363
- ```bash
364
- grep -rn "kSecAttrAccessibleAlways\b" --include="*.swift"
365
- grep -rn "SecItemAdd" --include="*.swift" -l | \
366
- xargs grep -L "kSecAttrAccessible\|kSecAttrAccessControl"
367
- grep -rn "kSecAttrAccessibleWhenUnlocked\b" --include="*.swift" | \
368
- grep -v "ThisDeviceOnly"
369
- ```
238
+ Detection: `kSecAttrAccessibleAlways\b`; files with `SecItemAdd` but neither `kSecAttrAccessible` nor `kSecAttrAccessControl`; `kSecAttrAccessibleWhenUnlocked\b` without `ThisDeviceOnly`.
370
239
 
371
- ---
240
+ ## 6. Reusing an AES-GCM nonce
372
241
 
373
- ## Anti-Pattern #6 - Nonce Reuse in AES-GCM
242
+ Severity: Critical. OWASP: M10. Fix effort: Medium.
374
243
 
375
- **Severity:** CRITICAL | **OWASP:** M10 (Insufficient Cryptography) | **Fix effort:** Medium
244
+ Two messages sealed with the same key and nonce share a keystream, so XOR of the ciphertexts equals XOR of the plaintexts, and the GHASH authentication key can be recovered, which lets an attacker forge messages (Joux, 2006). CryptoKit picks a fresh random nonce for you; the break only happens when code builds nonces by hand.
376
245
 
377
- Reusing a nonce with the same key in AES-GCM is a complete cryptographic break. Identical nonces produce identical keystreams, enabling plaintext recovery via `C1 ⊕ C2 = P1 ⊕ P2` and authentication key recovery via polynomial factorization ("forbidden attack," Joux 2006). CryptoKit's `AES.GCM.seal` has a safe default: omitting the `nonce` parameter auto-generates a random 12-byte nonce. Danger occurs when AI explicitly constructs nonces. Full patterns: see `cryptokit-symmetric.md`.
378
-
379
- **❌ Insecure - AI-generated patterns:**
246
+ Insecure:
380
247
 
381
248
  ```swift
382
- import CryptoKit
383
-
384
- // Hardcoded nonce - identical keystream every encryption
385
- let fixedNonce = try! AES.GCM.Nonce(data: Data(repeating: 0x00, count: 12))
386
-
387
- func encrypt(_ plaintext: Data, using key: SymmetricKey) throws -> Data {
388
- let sealedBox = try AES.GCM.seal(
389
- plaintext, using: key, nonce: fixedNonce) // CATASTROPHIC
390
- return sealedBox.combined!
391
- }
392
- // Also dangerous: counter-based nonce that resets on app restart → collision
249
+ let fixedNonce = try AES.GCM.Nonce(data: Data(repeating: 0, count: 12))
250
+ let box = try AES.GCM.seal(record, using: key, nonce: fixedNonce)
393
251
  ```
394
252
 
395
- **✅ Secure - let CryptoKit handle nonces:**
253
+ A counter nonce that restarts from zero after each launch fails the same way.
254
+
255
+ Secure:
396
256
 
397
257
  ```swift
398
- import CryptoKit
258
+ let key = SymmetricKey(size: .bits256)
399
259
 
400
- func encrypt(_ plaintext: Data, using key: SymmetricKey) throws -> Data {
401
- // Nonce omitted → CryptoKit generates random 12-byte nonce
402
- let sealedBox = try AES.GCM.seal(plaintext, using: key)
403
- return sealedBox.combined! // Contains: nonce ‖ ciphertext ‖ tag
260
+ func encrypt(_ record: Data) throws -> Data {
261
+ guard let wire = try AES.GCM.seal(record, using: key).combined else {
262
+ throw CryptoKitError.incorrectParameterSize
263
+ }
264
+ return wire
404
265
  }
405
266
 
406
- func decrypt(_ combined: Data, using key: SymmetricKey) throws -> Data {
407
- let sealedBox = try AES.GCM.SealedBox(combined: combined)
408
- return try AES.GCM.open(sealedBox, using: key)
267
+ func decrypt(_ wire: Data) throws -> Data {
268
+ try AES.GCM.open(try AES.GCM.SealedBox(combined: wire), using: key)
409
269
  }
410
-
411
- let key = SymmetricKey(size: .bits256) // AES-256 per WWDC 2025 guidance
412
270
  ```
413
271
 
414
- WWDC 2019-709 introduced CryptoKit with the design philosophy: "easy to use, hard to misuse."
415
-
416
- **MASTG test:** MASTG-TEST-0317. **MASWE:** MASWE-0022. **Legacy:** MASTG-CRYPTO-4.
272
+ `combined` packs nonce, ciphertext and tag together, so nothing needs managing separately. This is the design goal WWDC 2019 session 709 set for CryptoKit: easy to use correctly, hard to use wrongly.
417
273
 
418
- **Detection heuristic:**
419
-
420
- ```bash
421
- grep -rn "AES\.GCM\.Nonce(data:" --include="*.swift"
422
- grep -rn "let.*nonce.*=.*AES\.GCM\.Nonce" --include="*.swift"
423
- grep -rn "Data(repeating:.*count:\s*12)" --include="*.swift"
424
- grep -rn "\.seal(.*nonce:" --include="*.swift"
425
- ```
274
+ Standards: MASTG-TEST-0317, MASWE-0022 (legacy MASTG-CRYPTO-4).
426
275
 
427
- ---
276
+ Detection: `AES.GCM.Nonce(data:`, variables named like `nonce`, `Data(repeating:` with `count: 12`, and `.seal(` calls that pass `nonce:`.
428
277
 
429
- ## Anti-Pattern #7 - MD5/SHA-1 for Security Purposes
278
+ ## 7. MD5 or SHA-1 where security matters
430
279
 
431
- **Severity:** HIGH | **OWASP:** M10 (Insufficient Cryptography) | **Fix effort:** Low
280
+ Severity: High. OWASP: M10. Fix effort: Low.
432
281
 
433
- MD5 broken since Wang & Yu (2005); SHA-1 broken by SHAttered (2017). CISA January 2025 lists both as insecure. Apple signals this via CryptoKit's `Insecure.MD5` and `Insecure.SHA1` namespacing.
282
+ MD5 collisions have been practical since 2005 (Wang and Yu); SHA-1 fell publicly with SHAttered in 2017. The January 2025 CISA guidance lists both as insecure, and CryptoKit parks them in the `Insecure` namespace for a reason.
434
283
 
435
- **❌ Insecure - AI-generated pattern:**
284
+ Insecure:
436
285
 
437
286
  ```swift
438
- import CryptoKit
439
- func hashPassword(_ password: String) -> String {
440
- let hash = Insecure.MD5.hash(data: password.data(using: .utf8)!)
441
- return hash.map { String(format: "%02x", $0) }.joined()
442
- }
443
- // Also: CC_MD5, CC_SHA1 from CommonCrypto
287
+ let stored = Insecure.MD5.hash(data: Data(password.utf8))
444
288
  ```
445
289
 
446
- **✅ Secure - SHA-256 minimum, KDF for passwords:**
447
-
448
- ```swift
449
- import CryptoKit
450
-
451
- // Integrity verification
452
- func hashData(_ data: Data) -> String {
453
- let hash = SHA256.hash(data: data)
454
- return hash.map { String(format: "%02x", $0) }.joined()
455
- }
290
+ Also `CC_MD5` and `CC_SHA1` from CommonCrypto.
456
291
 
457
- // HMAC for message authentication
458
- func authenticate(_ data: Data, key: SymmetricKey) -> Data {
459
- Data(HMAC<SHA256>.authenticationCode(for: data, using: key))
460
- }
292
+ Secure: use the hash that fits the job.
461
293
 
462
- // Password storage - NEVER raw hashes. Use a KDF:
463
- // Server-side: Argon2id, bcrypt, or scrypt
464
- // On-device: PBKDF2 with ≥600,000 iterations (OWASP 2023 minimum for HMAC-SHA256)
465
- // See cryptokit-symmetric.md for full PBKDF2 implementation
294
+ ```swift
295
+ let fingerprint = SHA256.hash(data: download)
296
+ let tag = HMAC<SHA256>.authenticationCode(for: message, using: macKey)
466
297
  ```
467
298
 
468
- iOS 26 adds SHA-3 family (`SHA3_256`, `SHA3_384`, `SHA3_512`) in CryptoKit. WWDC 2025-314 covers post-quantum additions (ML-KEM, ML-DSA), not SHA-3.
299
+ Passwords are never hashed with a plain digest. Hash them on the server with Argon2id, bcrypt or scrypt. When a key must be derived on the device, use PBKDF2 with at least 600,000 iterations.
469
300
 
470
- **MASTG test:** MASTG-TEST-0211. **MASTG demos:** MASTG-DEMO-0015, MASTG-DEMO-0016. **Legacy:** MSTG-CRYPTO-1.
301
+ iOS 26 adds `SHA3_256`, `SHA3_384` and `SHA3_512`. WWDC 2025 session 314 is about post-quantum cryptography and does not cover SHA-3.
471
302
 
472
- **Detection heuristic:**
303
+ Standards: MASTG-TEST-0211, MASTG-DEMO-0015, MASTG-DEMO-0016 (legacy MSTG-CRYPTO-1).
473
304
 
474
- ```bash
475
- grep -rn "Insecure\.\(MD5\|SHA1\)" --include="*.swift"
476
- grep -rn "CC_MD5\|CC_SHA1\|CC_MD5_DIGEST_LENGTH\|CC_SHA1_DIGEST_LENGTH" \
477
- --include="*.swift" --include="*.m"
478
- ```
305
+ Detection: `Insecure.MD5` or `Insecure.SHA1`, plus `CC_MD5`, `CC_SHA1` and the matching digest-length constants in `.swift` and `.m` files.
479
306
 
480
- ---
307
+ ## 8. Secrets in logs
481
308
 
482
- ## Anti-Pattern #8 - Logging Sensitive Data
309
+ Severity: High. OWASP: M9. Fix effort: Low.
483
310
 
484
- **Severity:** HIGH | **OWASP:** M9 (Insecure Data Storage) | **Fix effort:** Low
311
+ Whatever `print()`, `NSLog()` or `os_log()` emits can persist in the device log, where Xcode's console, `idevicesyslog` or `log collect --device` retrieve it; on a jailbroken device any process can read it.
485
312
 
486
- `print()`, `NSLog()`, and `os_log()` with sensitive values persist in device logs - accessible via Xcode Console, `idevicesyslog`, and `log collect --device`. On jailbroken devices, any process reads log storage. Apple's `OSLogPrivacy` (iOS 14+): `.private` redacts in production; `.sensitive` (iOS 15+) always redacted.
313
+ `OSLogPrivacy` (iOS 14 and later) controls redaction: `.private` values are hidden in production logs, and `.sensitive` (iOS 15 and later) is always hidden.
487
314
 
488
- **❌ Insecure - AI-generated pattern:**
315
+ Insecure:
489
316
 
490
317
  ```swift
491
- func login(username: String, password: String) async throws {
492
- print("Logging in with password: \(password)") // In device logs!
493
- let token = try await authService.authenticate(username, password)
494
- print("Got auth token: \(token)") // In device logs!
495
- os_log("API key loaded: %{public}@", apiKey) // Explicitly public!
496
- }
318
+ print("login ok, token=\(token)")
319
+ os_log("using key %{public}@", apiKey)
497
320
  ```
498
321
 
499
- **✅ Secure - OSLogPrivacy with redaction:**
322
+ Secure:
500
323
 
501
324
  ```swift
502
- import os
503
-
504
- let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "auth")
505
-
506
- func login(username: String, password: String) async throws {
507
- // Log events, not values - .private(mask: .hash) enables correlation
508
- logger.info("Login attempt: \(username, privacy: .private(mask: .hash))")
509
- let token = try await authService.authenticate(username, password)
510
- logger.info("Authentication succeeded") // No token value
511
- }
512
-
513
- // Legacy os_log
514
- os_log("Account: %{private}@", log: .default, type: .info, accountNumber)
515
-
516
- // Strip debug logging in release builds
517
- #if DEBUG
518
- print("Debug: \(sensitiveValue)")
519
- #endif
325
+ let authLog = Logger(subsystem: "com.example.courier", category: "auth")
326
+ authLog.info("sign-in succeeded")
327
+ authLog.debug("session \(sessionID, privacy: .private(mask: .hash))")
520
328
  ```
521
329
 
522
- **MASTG tests:** MASTG-TEST-0296, MASTG-TEST-0297. **MASWE:** MASWE-0001. **Legacy:** MSTG-STORAGE-3.
330
+ Log the event, not the value. `.private(mask: .hash)` still lets you correlate entries for the same session. With the older API, use `%{private}@`. Keep any diagnostic `print` inside `#if DEBUG`.
523
331
 
524
- **Detection heuristic:**
332
+ Standards: MASTG-TEST-0296, MASTG-TEST-0297, MASWE-0001 (legacy MSTG-STORAGE-3).
525
333
 
526
- ```bash
527
- grep -rn "print(.*\\\(" --include="*.swift" | \
528
- grep -iE "password|token|secret|key|credential|ssn|credit"
529
- grep -rn "NSLog(.*%@" --include="*.swift" --include="*.m" | \
530
- grep -iE "password|token|secret|key"
531
- grep -rn 'os_log.*%{public}' --include="*.swift" | \
532
- grep -iE "password|token|secret|key"
533
- ```
534
-
535
- ---
334
+ Detection: `print(` with string interpolation, `NSLog(` with `%@`, and `os_log` with `%{public}` near words like password, token, secret or key.
536
335
 
537
- ## Anti-Pattern #9 - Not Clearing Keychain on First Launch
336
+ ## 9. No keychain cleanup after reinstall
538
337
 
539
- **Severity:** MEDIUM | **OWASP:** M9 (Insecure Data Storage) | **Fix effort:** Low
338
+ Severity: Medium. OWASP: M9. Fix effort: Low.
540
339
 
541
- Keychain items persist in a system-wide encrypted database managed by `securityd`, outside the app sandbox. App deletion removes the sandbox but keychain items survive. Apple DTS engineer Quinn "The Eskimo!" confirmed this as "currently expected behaviour despite being an obvious privacy concern." Consequences: stale credentials on reinstall, cross-user data leakage on device resale, and Firebase SDK authentication errors on reinstall. Full migration patterns: see `migration-legacy-stores.md`.
340
+ The keychain database lives in `securityd`, outside the app container, so items survive deleting the app. Apple DTS has described this as expected behaviour even though it has privacy consequences. The effects: stale credentials after reinstall, one user's session leaking to the next owner of a resold device, and backend SDKs failing authentication with leftover state.
542
341
 
543
- **❌ The missing pattern - AI never generates this:**
342
+ Secure: before any SDK initialises, wait until protected data is readable, then check a marker in `UserDefaults`; if it is unset, delete every item class with `kSecAttrSynchronizable: kSecAttrSynchronizableAny` and set the marker.
544
343
 
545
344
  ```swift
546
- @main
547
- struct MyApp: App {
548
- var body: some Scene {
549
- WindowGroup { ContentView() }
345
+ @MainActor
346
+ func purgeKeychainOnFreshInstall() async {
347
+ await ReinstallSweeper.waitUntilProtectedDataIsReadable()
348
+ guard !UserDefaults.standard.bool(forKey: "vault.installMarker") else { return }
349
+ for itemClass in [kSecClassGenericPassword, kSecClassInternetPassword,
350
+ kSecClassCertificate, kSecClassKey, kSecClassIdentity] {
351
+ let scope: [CFString: Any] = [kSecClass: itemClass,
352
+ kSecAttrSynchronizable: kSecAttrSynchronizableAny]
353
+ SecItemDelete(scope as CFDictionary)
550
354
  }
551
- // Stale keychain items from previous install persist silently
355
+ UserDefaults.standard.set(true, forKey: "vault.installMarker")
552
356
  }
553
357
  ```
554
358
 
555
- **✅ Secure - first-launch keychain cleanup:**
359
+ The protected-data wait is not optional. A pre-warmed launch before first unlock reads the marker as unset and would erase a signed-in user. The full version, including retry on failed deletes, is in [migration-legacy-stores.md](migration-legacy-stores.md).
556
360
 
557
- ```swift
558
- @main
559
- struct MyApp: App {
560
- init() { clearKeychainIfFirstLaunch() }
361
+ Standards: MASTG-TEST-0300, MASTG-TEST-0301 (legacy MSTG-STORAGE-11).
561
362
 
562
- var body: some Scene {
563
- WindowGroup { ContentView() }
564
- }
363
+ Detection: files using `SecItemAdd` or `SecItemCopyMatching` with no first-launch marker anywhere in the project; files with `SecItemDelete` that reference such a marker are the cleanup to review.
565
364
 
566
- private func clearKeychainIfFirstLaunch() {
567
- let defaults = UserDefaults.standard
568
- guard !defaults.bool(forKey: "hasLaunchedBefore") else { return }
569
-
570
- // UserDefaults was cleared on uninstall → this is first launch
571
- for secClass in [kSecClassGenericPassword, kSecClassInternetPassword,
572
- kSecClassCertificate, kSecClassKey, kSecClassIdentity] {
573
- SecItemDelete([
574
- kSecClass: secClass,
575
- kSecAttrSynchronizable: kSecAttrSynchronizableAny
576
- ] as NSDictionary)
577
- }
578
- defaults.set(true, forKey: "hasLaunchedBefore")
579
- }
580
- }
581
- ```
365
+ ## 10. Weak randomness for security values
582
366
 
583
- Place this before initializing any SDKs (Firebase, analytics) that read from Keychain. Including `kSecAttrSynchronizableAny` ensures iCloud Keychain items are also cleared.
367
+ Severity: High. OWASP: M10. Fix effort: Low.
584
368
 
585
- **MASTG tests:** MASTG-TEST-0300, MASTG-TEST-0301. **Legacy:** MSTG-STORAGE-11.
369
+ The generator and the amount of entropy both matter.
586
370
 
587
- **Detection heuristic:**
371
+ - `rand()`, `random()`, `drand48()` and `srand(time(nil))` are predictable and never acceptable. GameplayKit sources such as `GKMersenneTwisterRandomSource` and `GKARC4RandomSource` are built for games and are not for security either.
372
+ - `arc4random`, `arc4random_buf` and `arc4random_uniform` are backed by the system cryptographic generator on Apple platforms, as their manual page states, and so is Swift's `SystemRandomNumberGenerator`, which `randomElement()` and `Int.random(in:)` use by default. The generator is fine; the usual defect is how little of it the code uses. `arc4random()` returns a single 32-bit `UInt32`, `arc4random() % n` adds modulo bias, and `arc4random_uniform(999_999)` produces a token with about 20 bits of entropy. A few `randomElement()` picks from a character set make an equally guessable session identifier. These functions also cannot report failure.
373
+ - For keys, tokens, salts and nonces, prefer `SecRandomCopyBytes`, which draws from the same system generator (corecrypto, seeded from hardware entropy), returns a status you can check, and makes the security intent obvious in review. For key material, use `SymmetricKey(size: .bits256)` directly.
588
374
 
589
- ```bash
590
- grep -rn "SecItemAdd\|SecItemCopyMatching" --include="*.swift" -l | \
591
- xargs grep -L "hasLaunchedBefore\|isFirstLaunch\|firstRun"
592
- grep -rn "SecItemDelete" --include="*.swift" -l | \
593
- xargs grep "hasLaunchedBefore\|isFirstLaunch"
594
- ```
595
-
596
- ---
597
-
598
- ## Anti-Pattern #10 - Non-Cryptographic RNG for Security Operations
599
-
600
- **Severity:** HIGH | **OWASP:** M10 (Insufficient Cryptography) | **Fix effort:** Low
601
-
602
- `arc4random()` returns only 32-bit `UInt32` - insufficient for cryptographic purposes requiring 128-256 bits. Character-by-character token construction introduces bias. Truly non-cryptographic alternatives (`rand()`, `drand48()`, GameplayKit RNG) must never be used for security operations.
603
-
604
- **❌ Insecure - AI-generated patterns:**
375
+ Insecure:
605
376
 
606
377
  ```swift
607
- func generateToken() -> String {
608
- return String(arc4random_uniform(999_999)) // ~20 bits of entropy
609
- }
610
-
611
- func generateSessionId(length: Int = 16) -> String {
612
- let chars = "abcdefghijklmnopqrstuvwxyz0123456789"
613
- return String((0..<length).map { _ in chars.randomElement()! }) // Bias
614
- }
615
- // Also dangerous: srand48/drand48, rand(), GameplayKit RNG
378
+ let resetCode = String(arc4random_uniform(999_999))
379
+ let sessionID = String((0..<6).compactMap { _ in "abcdef0123456789".randomElement() })
616
380
  ```
617
381
 
618
- **✅ Secure - SecRandomCopyBytes / CryptoKit:**
382
+ Secure:
619
383
 
620
384
  ```swift
621
- import Security
622
- import CryptoKit
623
-
624
- // SecRandomCopyBytes - canonical iOS crypto RNG
625
- func generateSecureToken(byteCount: Int = 32) throws -> String {
626
- var bytes = [UInt8](repeating: 0, count: byteCount)
385
+ func makeSessionToken() throws -> String {
386
+ var bytes = [UInt8](repeating: 0, count: 32)
627
387
  let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
628
- guard status == errSecSuccess else {
629
- throw CryptoError.randomGenerationFailed(status)
630
- }
388
+ guard status == errSecSuccess else { throw KeychainFailure(status) }
631
389
  return bytes.map { String(format: "%02x", $0) }.joined()
632
390
  }
633
391
 
634
- // CryptoKit key generation (secure RNG internally)
635
- let encryptionKey = SymmetricKey(size: .bits256)
392
+ let wrappingKey = SymmetricKey(size: .bits256)
636
393
  ```
637
394
 
638
- `SecRandomCopyBytes` sources entropy from the Secure Enclave's hardware TRNG via corecrypto's `ccrng_generate`. It reports errors via return status - unlike `arc4random`, which silently cannot fail.
639
-
640
- **MASTG test:** MASTG-TEST-0311. **MASTG demos:** MASTG-DEMO-0073, MASTG-DEMO-0074. **Legacy:** MSTG-CRYPTO-6.
641
-
642
- **Detection heuristic:**
643
-
644
- ```bash
645
- grep -rn "arc4random\|arc4random_uniform\|arc4random_buf" --include="*.swift" | \
646
- grep -iE "token|nonce|salt|key|secret|session|iv"
647
- grep -rn "\bsrand\b\|\brand()\|\brandom()\|\bdrand48\b" --include="*.swift"
648
- grep -rn "GKARC4RandomSource\|GKMersenneTwisterRandomSource" --include="*.swift"
649
- ```
650
-
651
- ---
652
-
653
- ## Quick Reference Matrix
654
-
655
- | # | Anti-Pattern | OWASP 2024 | MASTG Test | Dangerous API | Secure API | Fix Effort |
656
- | --- | ------------------------ | ---------- | --------------- | -------------------------- | ----------------------------------- | ---------- |
657
- | 1 | UserDefaults secrets | M9 | MASTG-TEST-0302 | `UserDefaults.set` | `SecItemAdd` + Keychain | Medium |
658
- | 2 | Hardcoded API keys | M1 | MASTG-TEST-0213 | String literals | Server proxy + Keychain cache | High |
659
- | 3 | LAContext-only biometric | M3 | MASTG-TEST-0266 | `evaluatePolicy` | `SecAccessControlCreateWithFlags` | Medium |
660
- | 4 | Ignored SecItem errors | M8 | MASTG-TEST-0300 | Unchecked `SecItemAdd` | OSStatus switch + `SecItemUpdate` | Low |
661
- | 5 | Wrong data protection | M9 | MASTG-TEST-0299 | `kSecAttrAccessibleAlways` | `WhenUnlockedThisDeviceOnly` | Low |
662
- | 6 | Nonce reuse AES-GCM | M10 | MASTG-TEST-0317 | `AES.GCM.Nonce(data:)` | Omit nonce (auto-random) | Medium |
663
- | 7 | MD5/SHA-1 for security | M10 | MASTG-TEST-0211 | `Insecure.MD5/.SHA1` | `SHA256`+ / KDF for passwords | Low |
664
- | 8 | Logging sensitive data | M9 | MASTG-TEST-0297 | `print(token)` | `Logger` + `.private` | Low |
665
- | 9 | No keychain cleanup | M9 | MASTG-TEST-0300 | Missing cleanup | UserDefaults flag + `SecItemDelete` | Low |
666
- | 10 | Non-crypto RNG | M10 | MASTG-TEST-0311 | `arc4random()` | `SecRandomCopyBytes` | Low |
667
-
668
- ---
669
-
670
- ## CI/CD Detection Strategy
671
-
672
- **Semgrep** (pre-commit/PR gate): Fast structural pattern matching for `UserDefaults` misuse, missing `errSecDuplicateItem`, `LAContext` booleans. Limited data-flow analysis.
673
-
674
- **CodeQL** (nightly/PR gate): Deep semantic taint tracking - catches tokens assigned to variables then logged. Slower execution.
395
+ Standards: MASTG-TEST-0311, MASTG-DEMO-0073, MASTG-DEMO-0074 (legacy MSTG-CRYPTO-6).
675
396
 
676
- **Binary scanning** (post-build): `strings`/`class-dump` on compiled binary catches hardcoded keys surviving source-level obfuscation.
397
+ Detection: `srand`, `rand()`, `random()`, `drand48`, `GKARC4RandomSource`, `GKMersenneTwisterRandomSource` are findings. `arc4random` calls near token, nonce, salt, key, secret, session or iv are review prompts: check how many bits the value carries.
677
398
 
678
- Recommended: Semgrep on every PR + post-build binary scanning. CodeQL nightly for deep analysis.
399
+ ## Quick reference
679
400
 
680
- ---
401
+ | # | OWASP | Main MASTG test | Look for | Replace with | Effort |
402
+ | --- | --- | --- | --- | --- | --- |
403
+ | 1 | M9 | 0302 | `UserDefaults.set` of a secret | `SecItemAdd` with update on duplicate | Medium |
404
+ | 2 | M1 | 0213 | Key literals | Server proxy, runtime fetch cached in keychain | High |
405
+ | 3 | M3 | 0266 | `evaluatePolicy` as the gate | `SecAccessControlCreateWithFlags` | Medium |
406
+ | 4 | M8 | 0300 | Unchecked `SecItemAdd` | Status `switch` with update on duplicate | Low |
407
+ | 5 | M9 | 0299 | `Always`, missing class | `WhenUnlockedThisDeviceOnly` or stricter | Low |
408
+ | 6 | M10 | 0317 | `AES.GCM.Nonce(data:)` | Let CryptoKit choose the nonce | Medium |
409
+ | 7 | M10 | 0211 | `Insecure.*` | SHA-256 or stronger, a password KDF | Low |
410
+ | 8 | M9 | 0297 | `print(token)` | `Logger` with `.private` | Low |
411
+ | 9 | M9 | 0300 | No reinstall cleanup | Marker plus `SecItemDelete` after protected data | Low |
412
+ | 10 | M10 | 0311 | Low-entropy or predictable RNG | `SecRandomCopyBytes`, `SymmetricKey` | Low |
681
413
 
682
- ## iOS 26 / WWDC 2025 Implications
414
+ ## Automating detection in CI
683
415
 
684
- WWDC 2025-314 introduced the most significant CryptoKit expansion since 2019:
416
+ - **Semgrep**: fast structural rules, ideal as a pre-commit hook or pull-request gate for `UserDefaults` secrets, add calls without duplicate handling and Boolean biometric gates. Its data-flow tracking is limited.
417
+ - **CodeQL**: semantic taint analysis, for example a token flowing into a logging call. Slower; run nightly or on pull requests.
418
+ - **Binary scan after the build**: `strings` and `class-dump` over the built product catch keys that survived obfuscation or came in through configuration files.
685
419
 
686
- - **Symmetric keys:** `.bits256` recommended over `.bits128` for quantum resistance (anti-patterns #6, #10)
687
- - **Hashing:** SHA-3 family (`SHA3_256/384/512`) in CryptoKit on iOS 26+ (anti-pattern #7)
688
- - **Post-quantum:** ML-KEM 768/1024, ML-DSA 65/87, X-Wing - all with Secure Enclave support
689
- - **TLS:** `X25519MLKEM768` enabled by default for `URLSession` in iOS 26
690
- - **Secure Enclave:** Hardware post-quantum key creation strengthens anti-patterns #3 and #5 fixes
420
+ A practical setup runs Semgrep on every pull request, a binary scan after each build, and CodeQL nightly.
691
421
 
692
- ---
422
+ ## What iOS 26 changes
693
423
 
694
- ## Summary Checklist
424
+ Prefer `.bits256` symmetric keys over `.bits128` for headroom against quantum attacks. The SHA-3 family is available from iOS 26. CryptoKit adds ML-KEM-768 and ML-KEM-1024, ML-DSA-65 and ML-DSA-87, and the X-Wing hybrid KEM, with Secure Enclave support, and `URLSession` negotiates the hybrid `X25519MLKEM768` TLS key exchange by default. Creating post-quantum keys in the Secure Enclave strengthens the fixes for entries 3 and 5.
695
425
 
696
- When reviewing iOS code for security anti-patterns, verify each item:
426
+ ## Checklist
697
427
 
698
- 1. **No secrets in UserDefaults** - tokens, passwords, API keys, JWTs use Keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` or stricter
699
- 1. **No hardcoded keys in source** - API keys fetched at runtime via server proxy or authenticated endpoint; no high-entropy string literals, no secrets in `.xcconfig` or `Info.plist`
700
- 1. **Biometrics bound to Keychain** - `evaluatePolicy` is never used alone to gate sensitive actions; `SecAccessControlCreateWithFlags` with `.biometryCurrentSet` protects keychain items
701
- 1. **All SecItem calls checked** - `SecItemAdd` handles `errSecDuplicateItem` with `SecItemUpdate` fallback; `SecItemCopyMatching` handles `errSecItemNotFound`; no discarded `OSStatus` return values
702
- 1. **Explicit data protection class** - every `SecItemAdd` includes `kSecAttrAccessible` or `kSecAttrAccessControl`; no `kSecAttrAccessibleAlways`; `ThisDeviceOnly` variants used for non-syncing items
703
- 1. **No nonce reuse** - `AES.GCM.seal` called without explicit `nonce:` parameter (auto-random); no stored/global/counter-based nonce variables
704
- 1. **No broken hashes** - no `Insecure.MD5`, `Insecure.SHA1`, `CC_MD5`, `CC_SHA1` for security purposes; passwords use a password KDF such as Argon2id, bcrypt, or PBKDF2-HMAC-SHA256 with at least 600,000 iterations
705
- 1. **No sensitive data in logs** - `print()` and `NSLog()` never contain tokens, keys, or credentials; `os_log` uses `%{private}@`; `Logger` uses `.private` or `.private(mask: .hash)`
706
- 1. **First-launch keychain cleanup** - `UserDefaults` flag + `SecItemDelete` for all classes runs before SDK initialization at app startup
707
- 1. **Cryptographic RNG only** - `SecRandomCopyBytes` or CryptoKit APIs for tokens, nonces, salts, keys; no `arc4random` / `rand()` / `drand48()` / GameplayKit RNG in security contexts
708
- 1. **iOS 26 readiness** - symmetric keys use `.bits256`; no deprecated algorithms; aware of post-quantum CryptoKit APIs for forward-looking implementations
428
+ - [ ] No secrets in `UserDefaults` or `@AppStorage`; keychain with `WhenUnlockedThisDeviceOnly` or stricter.
429
+ - [ ] No hardcoded keys or high-entropy literals; nothing secret in `.xcconfig` or `Info.plist`.
430
+ - [ ] Biometrics unlock a keychain item protected with `.biometryCurrentSet`.
431
+ - [ ] Every `SecItem` call checks its status; duplicates are updated, not-found is handled.
432
+ - [ ] Every add sets accessibility or access control; no `Always`; `ThisDeviceOnly` for items that should not sync.
433
+ - [ ] No explicit, global or counter nonces.
434
+ - [ ] No MD5 or SHA-1; passwords go through Argon2id, bcrypt, or PBKDF2-HMAC-SHA256 with at least 600,000 iterations.
435
+ - [ ] No secrets in logs; `%{private}@`, or `Logger` with `.private` or `.private(mask: .hash)`.
436
+ - [ ] Reinstall cleanup runs before SDK setup and after protected data is available.
437
+ - [ ] Security values come from a cryptographic generator with enough bits.
438
+ - [ ] Ready for iOS 26: `.bits256`, no deprecated algorithms, post-quantum APIs on the radar.