Use when working with iOS/macOS Keychain Services (SecItem queries, kSecClass, OSStatus errors), biometric authentication (LAContext, Face ID, Touch ID), CryptoKit (AES-GCM, ChaChaPoly, ECDSA, ECDH, HPKE, ML-KEM), Secure Enclave, secure credential storage (OAuth tokens, API keys), certificate pinning (SecTrust, SPKI), keychain sharing across apps/extensions, migrating secrets from UserDefaults or plists, or OWASP MASVS/MASTG mobile compliance on Apple platforms.
Philosophy: Non-opinionated, correctness-focused. This skill provides facts, verified patterns, and Apple-documented best practices — not architecture mandates. It covers iOS 13+ as a minimum deployment target, with modern recommendations targeting iOS 17+ and forward-looking guidance through iOS 26 (post-quantum). Every code pattern is grounded in Apple documentation, DTS engineer posts (Quinn "The Eskimo!"), WWDC sessions, and OWASP MASTG — never from memory alone.
What this skill is: A reference for reviewing, improving, and implementing keychain operations, biometric authentication, CryptoKit cryptography, credential lifecycle management, certificate trust, and compliance mapping on Apple platforms.
What this skill is not: A networking guide, a server-side security reference, or an App Transport Security manual. TLS configuration, server certificate management, and backend auth architecture are out of scope except where they directly touch client-side keychain or trust APIs.
Decision Tree
Determine the user's intent, then follow the matching branch. If ambiguous, ask.
┌─────────────────────┐
│ What is the task? │
└─────────┬───────────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌────────────┐
│ REVIEW │ │ IMPROVE │ │ IMPLEMENT │
│ │ │ │ │ │
│ Audit │ │ Migrate / │ │ Build from │
│ existing│ │ modernize │ │ scratch │
│ code │ │ existing │ │ │
└────┬────┘ └─────┬─────┘ └─────┬──────┘
│ │ │
▼ ▼ ▼
Run Top-Level Identify gap Identify which
Review Checklist (legacy store? domain(s) apply,
(§ below) against wrong API? load reference
the code. missing auth?) file(s), follow
Flag each item Load migration + ✅ patterns.
as ✅ / ❌ / domain-specific Implement with
⚠️ N/A. reference files. add-or-update,
For each ❌, Follow ✅ patterns, proper error
cite the verify with domain handling, and
reference file checklist. correct access
and specific control from
section. the start.
Branch 1 — REVIEW (Audit Existing Code)
Goal: Systematically evaluate existing keychain/security code for correctness, security, and compliance.
Procedure:
Run the Top-Level Review Checklist (below) against the code under review. Score each item ✅ / ❌ / ⚠️ N/A.
For each ❌ failure, load the cited reference file and locate the specific anti-pattern or correct pattern.
Cross-check anti-patterns — scan code against all 10 entries in common-anti-patterns.md. Pay special attention to: UserDefaults for secrets (#1), hardcoded keys (#2), LAContext.evaluatePolicy() as sole auth gate (#3), ignored OSStatus (#4).
Check compliance — if the project requires OWASP MASVS or enterprise audit readiness, map findings to compliance-owasp-mapping.md categories M1, M3, M9, M10.
Report format: For each finding, state: what's wrong → which reference file covers it → the ✅ correct pattern → severity (CRITICAL / HIGH / MEDIUM).
Key reference files for review:
Start with: common-anti-patterns.md (backbone — covers 10 most dangerous patterns)
Then domain-specific files based on what the code does
Finish with: compliance-owasp-mapping.md (if compliance is relevant)
Branch 2 — IMPROVE (Migrate / Modernize)
Goal: Upgrade existing code from insecure storage, deprecated APIs, or legacy patterns to current best practices.
Follow the migration pattern in the relevant reference file. Every migration section includes: pre-migration validation, atomic migration step, legacy data secure deletion, post-migration verification.
Run the domain-specific checklist from the reference file after migration completes.
Verify no regressions using guidance from testing-security-code.md.
Branch 3 — IMPLEMENT (Build from Scratch)
Goal: Build new keychain/security functionality correctly from the start.
Procedure:
Identify which domain(s) the task touches. Use the Domain Selection Guide below.
Load the relevant reference file(s). Follow ✅ code patterns — never deviate from them for the core security logic.
Apply Core Guidelines (below) to every implementation.
Run the domain-specific checklist before considering the implementation complete.
Add tests following testing-security-code.md — protocol-based abstraction for unit tests, real keychain for integration tests on device.
These seven rules are non-negotiable. Every keychain/security implementation must satisfy all of them.
1. Never ignore OSStatus. Every SecItem* call returns an OSStatus. Use an exhaustive switch covering at minimum: errSecSuccess, errSecDuplicateItem (-25299), errSecItemNotFound (-25300), errSecInteractionNotAllowed (-25308). Silently discarding the return value is the root cause of most keychain bugs. → keychain-fundamentals.md
2. Never use LAContext.evaluatePolicy() as a standalone auth gate. This returns a Bool that is trivially patchable at runtime via Frida. Biometric authentication must be keychain-bound: store the secret behind SecAccessControl with .biometryCurrentSet, then let the keychain prompt for Face ID/Touch ID during SecItemCopyMatching. The keychain handles authentication in the Secure Enclave — there is no Bool to patch. → biometric-authentication.md
3. Never store secrets in UserDefaults, Info.plist, .xcconfig, or NSCoding archives. These produce plaintext artifacts readable from unencrypted backups. The Keychain is the only Apple-sanctioned store for credentials. → credential-storage-patterns.md, common-anti-patterns.md
4. Never call SecItem* on @MainActor. Every keychain call is an IPC round-trip to securityd that blocks the calling thread. Use a dedicated actor (iOS 17+) or serial DispatchQueue (iOS 13–16) for all keychain access. → keychain-fundamentals.md
5. Always set kSecAttrAccessible explicitly. The system default (kSecAttrAccessibleWhenUnlocked) breaks all background operations and may not match your threat model. Choose the most restrictive class that satisfies your access pattern. For background tasks: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. For highest sensitivity: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. → keychain-access-control.md
6. Always use the add-or-update pattern.SecItemAdd followed by SecItemUpdate on errSecDuplicateItem. Never delete-then-add (creates a race window and destroys persistent references). Never call SecItemAdd without handling the duplicate case. → keychain-fundamentals.md
7. Always target the data protection keychain on macOS. Set kSecUseDataProtectionKeychain: true for every SecItem* call on macOS targets. Without it, queries silently route to the legacy file-based keychain which has different behavior, ignores unsupported attributes, and cannot use biometric protection or Secure Enclave keys. Mac Catalyst and iOS-on-Mac do this automatically. → keychain-fundamentals.md
Quick Reference Tables
Accessibility Constants — Selection Guide
Constant
When Decryptable
Survives Backup
Survives Device Migration
Background Safe
Use When
WhenPasscodeSetThisDeviceOnly
Unlocked + passcode set
❌
❌
❌
Highest-security secrets; removed if passcode removed
Rule of thumb: Need background access (push handlers, background refresh)? Start with AfterFirstUnlockThisDeviceOnly. Foreground-only? Start with WhenUnlockedThisDeviceOnly. Tighten to WhenPasscodeSetThisDeviceOnly for high-value secrets. Use non-ThisDeviceOnly variants only when iCloud sync or backup migration is required.
CryptoKit Algorithm Selection
Need
Algorithm
Min iOS
Notes
Hash data
SHA256 / SHA384 / SHA512
13
SHA3_256/SHA3_512 available iOS 18+
Authenticate data (MAC)
HMAC<SHA256>
13
Always verify with constant-time comparison (built-in)
Encrypt data (authenticated)
AES.GCM
13
256-bit key, 96-bit nonce, 128-bit tag. Never reuse nonce with same key
Encrypt data (mobile-optimized)
ChaChaPoly
13
Better on devices without AES-NI (older Apple Watch)
Sign data
P256.Signing / Curve25519.Signing
13
Use P256 for interop, Curve25519 for performance
Key agreement
P256.KeyAgreement / Curve25519.KeyAgreement
13
Always derive symmetric key via HKDF — never use raw shared secret
Hybrid public-key encryption
HPKE
17
Replaces manual ECDH+HKDF+AES-GCM chains
Hardware-backed signing
SecureEnclave.P256.Signing
13
P256 only; key never leaves hardware
Post-quantum key exchange
MLKEM768
26
Formal verification (ML-KEM FIPS 203)
Post-quantum signing
MLDSA65
26
Formal verification (ML-DSA FIPS 204)
Password → key derivation
PBKDF2 (via CommonCrypto)
13
≥600,000 iterations SHA-256 (OWASP 2024)
Key → key derivation
HKDF<SHA256>
13
Extract-then-expand; always use info parameter for domain separation
Anti-Pattern Detection — Quick Scan
When reviewing code, search for these patterns. Any match is a finding.
❌ = insecure pattern signature to detect in user code. ✅ = apply the corrective pattern in the referenced file.
Hardcoded base64/hex strings (≥16 chars) in source
Hardcoded cryptographic key
CRITICAL
common-anti-patterns.md #2
evaluatePolicy without SecItemCopyMatching nearby
LAContext-only biometric gate
CRITICAL
common-anti-patterns.md #3
SecItemAdd without checking return / OSStatus
Ignored error code
HIGH
common-anti-patterns.md #4
No kSecAttrAccessible in add dictionary
Implicit accessibility class
HIGH
common-anti-patterns.md #5
AES.GCM.Nonce() inside a loop with same key
Potential nonce reuse
CRITICAL
common-anti-patterns.md #6
sharedSecret.withUnsafeBytes without HKDF
Raw shared secret as key
HIGH
common-anti-patterns.md #7
kSecAttrAccessibleAlways
Deprecated accessibility
HIGH
keychain-access-control.md
SecureEnclave.isAvailable without #if !targetEnvironment(simulator)
Simulator false-negative trap
MEDIUM
secure-enclave.md
kSecAttrSynchronizable: true + ThisDeviceOnly
Contradictory constraints
MEDIUM
keychain-item-classes.md
SecTrustEvaluate (sync, deprecated)
Legacy trust evaluation
MEDIUM
certificate-trust.md
kSecClassGenericPassword + kSecAttrServer
Wrong class for web credentials
MEDIUM
keychain-item-classes.md
Top-Level Review Checklist
Use this checklist for a rapid sweep across all 14 domains. Each item maps to one or more reference files for deep-dive investigation. For domain-specific deep checks, use the Summary Checklist at the bottom of each reference file.
1. Secrets are in Keychain, not UserDefaults/plist/source — No credentials, tokens, or cryptographic keys in UserDefaults, Info.plist, .xcconfig, hardcoded strings, or NSCoding archives. OWASP M9 (Insecure Data Storage) directly violated. → common-anti-patterns.md #1–2, credential-storage-patterns.md, migration-legacy-stores.md, compliance-owasp-mapping.md
2. Every OSStatus is checked — All SecItem* calls handle return codes with exhaustive switch or equivalent. No ignored returns. errSecInteractionNotAllowed is handled non-destructively (retry later, never delete). → keychain-fundamentals.md, common-anti-patterns.md #4
3. Biometric auth is keychain-bound — If biometrics are used, authentication is enforced via SecAccessControl + keychain access, not LAContext.evaluatePolicy() alone. → biometric-authentication.md, common-anti-patterns.md #3
4. Accessibility classes are explicit and correct — Every keychain item has an explicit kSecAttrAccessible value matching its access pattern (background vs foreground, device-bound vs syncable). No deprecated Always constants. → keychain-access-control.md
5. No SecItem* calls on @MainActor — All keychain operations run on a dedicated actor or background queue. No synchronous keychain access in UI code, viewDidLoad, or application(_:didFinishLaunchingWithOptions:). → keychain-fundamentals.md
6. Correct kSecClass for each item type — Web credentials use InternetPassword (not GenericPassword) for AutoFill. Cryptographic keys use kSecClassKey with proper kSecAttrKeyType. App secrets use GenericPassword with kSecAttrService + kSecAttrAccount. → keychain-item-classes.md
7. CryptoKit used correctly — Nonces never reused with the same key. ECDH shared secrets always derived through HKDF before use as symmetric keys. SymmetricKey material stored in Keychain, not in memory or files. Crypto operations covered by protocol-based unit tests. → cryptokit-symmetric.md, cryptokit-public-key.md, testing-security-code.md
8. Secure Enclave constraints respected — SE keys are P256 only (classical), never imported (always generated on-device), device-bound (no backup/sync). Availability checks guard against simulator and keychain-access-groups entitlement issues. → secure-enclave.md
9. Sharing and access groups configured correctly — kSecAttrAccessGroup uses full TEAMID.group.identifier format. Entitlements match between app and extensions. No accidental cross-app data exposure. → keychain-sharing.md
10. Certificate trust evaluation is current — Uses SecTrustEvaluateAsyncWithError (not deprecated synchronous SecTrustEvaluate). Pinning strategy uses SPKI hash or NSPinnedDomains (not leaf certificate pinning which breaks on annual rotation). → certificate-trust.md
11. macOS targets data protection keychain — All macOS SecItem* calls include kSecUseDataProtectionKeychain: true (except Mac Catalyst / iOS-on-Mac where it's automatic). → keychain-fundamentals.md
OWASP Mobile Top 10 (2024) + MASVS v2.1.0 + MASTG v2 — compliance framework
CISA/FBI "Product Security Bad Practices" v2.0 (January 2025) — hardcoded credentials classified as national security risk
Agent Behavioral Rules
The sections below govern how an AI agent should behave when using this skill: what's in scope, what's out, tone calibration, common mistakes to avoid, how to select reference files, and output formatting requirements.
Scope Boundaries — Inclusions
This skill is authoritative for client-side Apple platform security across iOS, macOS, tvOS, watchOS, and visionOS:
Keychain Services — SecItemAdd, SecItemCopyMatching, SecItemUpdate, SecItemDelete, query dictionary construction, OSStatus handling, actor/thread isolation, the data protection keychain on macOS (TN3137)
Testing — Protocol-based mocking, simulator vs device differences, CI/CD keychain creation, Swift Testing patterns
Compliance — OWASP Mobile Top 10 (2024), MASVS v2.1.0, MASTG v2 test IDs, CISA/FBI Bad Practices
Edge cases that ARE in scope: Client-side certificate loading for mTLS pinning (certificate-trust.md). Passkey/AutoFill credential storage in Keychain (keychain-item-classes.md, credential-storage-patterns.md). @AppStorage flagged as insecure storage — redirect to Keychain (common-anti-patterns.md).
Scope Boundaries — Exclusions
Do not answer the following topics using this skill. Briefly explain they are out of scope and suggest where to look.
Apple "Supporting passkeys" documentation; this skill covers client-side ASAuthorizationController only where it stores credentials in Keychain
Code signing / provisioning profiles
Build/distribution, not runtime security
Apple code signing documentation
Jailbreak detection
Runtime integrity, not cryptographic storage
OWASP MASTG MSTG-RESILIENCE category
SwiftUI @AppStorage
Wrapper over UserDefaults — out of scope except to flag it as insecure for secrets
common-anti-patterns.md #1 flags it; no deeper coverage
Cross-platform crypto (OpenSSL, LibSodium)
Third-party libraries, not Apple frameworks
Respective library documentation
Tone Rules
This skill is non-opinionated and correctness-focused. Tone calibrates based on severity.
Default tone — advisory. Use "consider," "suggest," "one approach is," "a common pattern is" for: architecture choices (wrapper class design, actor vs DispatchQueue), algorithm selection when multiple valid options exist (P256 vs Curve25519, AES-GCM vs ChaChaPoly), accessibility class selection when the threat model is unclear, testing strategy, code organization.
Elevated tone — directive. Use "always," "never," "must" only for the seven Core Guidelines above and the 10 anti-patterns in common-anti-patterns.md. These are security invariants, not style preferences. The exhaustive list of directives:
Never ignore OSStatus — always check return codes from SecItem* calls. → keychain-fundamentals.md
Never use LAContext.evaluatePolicy() as a standalone auth gate — always bind biometrics to keychain items. → biometric-authentication.md
Never store secrets in UserDefaults, Info.plist, .xcconfig, or NSCoding archives. → credential-storage-patterns.md, common-anti-patterns.md
Never call SecItem* on @MainActor — always use a background actor or queue. → keychain-fundamentals.md
Always set kSecAttrAccessible explicitly on every SecItemAdd. → keychain-access-control.md
Always use the add-or-update pattern (SecItemAdd → SecItemUpdate on errSecDuplicateItem). → keychain-fundamentals.md
Always set kSecUseDataProtectionKeychain: true on macOS targets. → keychain-fundamentals.md
Never reuse a nonce with the same AES-GCM key. → cryptokit-symmetric.md, common-anti-patterns.md
Never use a raw ECDH shared secret as a symmetric key — always derive through HKDF. → cryptokit-public-key.md, common-anti-patterns.md
Never use Insecure.MD5 or Insecure.SHA1 for security purposes. → cryptokit-symmetric.md, common-anti-patterns.md
If a pattern is not on this list, use advisory tone. Do not escalate warnings beyond what the reference files support.
Tone when declining. When a query falls outside scope, be direct but not dismissive: "This skill covers client-side keychain and CryptoKit. For ATS configuration, Apple's NSAppTransportSecurity documentation is the right reference." State the boundary, suggest an alternative, move on.
Common AI Mistakes — The 10 Most Likely Incorrect Outputs
Before finalizing any output, scan for all 10. Each links to the reference file containing the correct pattern.
Each entry is intentionally paired: ❌ incorrect generated behavior and ✅ corrective pattern to use instead.
Mistake #1 — Generating LAContext.evaluatePolicy() as the sole biometric gate. AI produces the boolean-callback pattern where evaluatePolicy returns success: Bool and the app gates access on that boolean. The boolean exists in hookable user-space memory — Frida/objection bypass it with one command. ✅ Correct pattern: Store a secret behind SecAccessControl with .biometryCurrentSet, retrieve via SecItemCopyMatching. → biometric-authentication.md
Mistake #2 — Suggesting SecureEnclave.isAvailable without simulator guard. AI generates if SecureEnclave.isAvailable { ... } without #if !targetEnvironment(simulator). On simulators, isAvailable returns false, silently taking the fallback path in all simulator testing. ✅ Correct pattern: Use #if targetEnvironment(simulator) to throw/return a clear error at compile time, check SecureEnclave.isAvailable only in device builds. → secure-enclave.md
Mistake #3 — Importing external keys into the Secure Enclave. AI generates SecureEnclave.P256.Signing.PrivateKey(rawRepresentation: someData). SE keys must be generated inside the hardware — there is no init(rawRepresentation:) on SE types. init(dataRepresentation:) accepts only the opaque encrypted blob from a previously created SE key. ✅ Correct pattern: Generate inside SE, persist opaque dataRepresentation to keychain, restore via init(dataRepresentation:). → secure-enclave.md
Mistake #4 — Using SecureEnclave.AES or SE for symmetric encryption. AI generates references to non-existent SE symmetric APIs. The SE's internal AES engine is not exposed as a developer API. Pre-iOS 26, the SE supports only P256 signing and key agreement. iOS 26 adds ML-KEM and ML-DSA, not symmetric primitives. ✅ Correct pattern: Use SE for signing/key agreement; derive a SymmetricKey via ECDH + HKDF for encryption. → secure-enclave.md, cryptokit-symmetric.md
Mistake #5 — Omitting kSecAttrAccessible in SecItemAdd. AI builds add dictionaries without an accessibility attribute. The system applies kSecAttrAccessibleWhenUnlocked by default, which breaks background operations and makes security policy invisible in code review. ✅ Correct pattern: Always set kSecAttrAccessible explicitly. → keychain-access-control.md
Mistake #6 — Using SecItemAdd without handling errSecDuplicateItem. AI checks only for errSecSuccess, or uses delete-then-add. Without duplicate handling, the second save silently fails. Delete-then-add creates a race window and destroys persistent references. ✅ Correct pattern: Add-or-update pattern. → keychain-fundamentals.md
Mistake #7 — Specifying explicit nonces for AES-GCM encryption. AI creates a nonce manually and passes it to AES.GCM.seal. Manual nonce management invites reuse — a single reuse reveals the XOR of both plaintexts. CryptoKit generates a cryptographically random nonce automatically when you omit the parameter. ✅ Correct pattern: Call AES.GCM.seal(plaintext, using: key) without a nonce: parameter. → cryptokit-symmetric.md, common-anti-patterns.md #6
Mistake #8 — Using raw ECDH shared secret as a symmetric key. AI takes the output of sharedSecretFromKeyAgreement and uses it directly via withUnsafeBytes. Raw shared secrets have non-uniform distribution. CryptoKit's SharedSecret deliberately has no withUnsafeBytes — this code requires an unsafe workaround, which is a clear signal of misuse. ✅ Correct pattern: Always derive via sharedSecret.hkdfDerivedSymmetricKey(...). → cryptokit-public-key.md, common-anti-patterns.md #7
Mistake #9 — Claiming SHA-3 requires iOS 26. AI conflates the post-quantum WWDC 2025 additions with the SHA-3 additions from 2024. SHA-3 family types were added in iOS 18 / macOS 15. iOS 26 introduced ML-KEM and ML-DSA, not SHA-3. ✅ Correct version tags: SHA-3 → iOS 18+. ML-KEM/ML-DSA → iOS 26+. → cryptokit-symmetric.md
Mistake #10 — Missing first-launch keychain cleanup. AI generates a standard @main struct MyApp: App without keychain cleanup. Keychain items survive app uninstallation. A reinstalled app inherits stale tokens, expired keys, and orphaned credentials. ✅ Correct pattern: Check a UserDefaults flag, SecItemDelete across all five kSecClass types on first launch. → common-anti-patterns.md #9, migration-legacy-stores.md
Reference File Loading Rules
Load the minimum set of files needed to answer the query. Do not load all 14 — they total ~7,000+ lines and will dilute focus.
Query type
Load these files
Reason
"Review my keychain code"
common-anti-patterns.md → then domain-specific files based on what the code does
common-anti-patterns.md + all files touched by the code
…(truncated)
1---2name: swift-security-expert3description: Use when working with iOS/macOS Keychain Services (SecItem queries, kSecClass, OSStatus errors), biometric authentication (LAContext, Face ID, Touch ID), CryptoKit (AES-GCM, ChaChaPoly, ECDSA, ECDH, HPKE, ML-KEM), Secure Enclave, secure credential storage (OAuth tokens, API keys), certificate pinning (SecTrust, SPKI), keychain sharing across apps/extensions, migrating secrets from UserDefaults or plists, or OWASP MASVS/MASTG mobile compliance on Apple platforms.4license: MIT5---67# Keychain & Security Expert Skill
89> **Philosophy:** Non-opinionated, correctness-focused. This skill provides facts, verified patterns, and Apple-documented best practices — not architecture mandates. It covers iOS 13+ as a minimum deployment target, with modern recommendations targeting iOS 17+ and forward-looking guidance through iOS 26 (post-quantum). Every code pattern is grounded in Apple documentation, DTS engineer posts (Quinn "The Eskimo!"), WWDC sessions, and OWASP MASTG — never from memory alone.
10>
11> **What this skill is:** A reference for reviewing, improving, and implementing keychain operations, biometric authentication, CryptoKit cryptography, credential lifecycle management, certificate trust, and compliance mapping on Apple platforms.
12>
13> **What this skill is not:** A networking guide, a server-side security reference, or an App Transport Security manual. TLS configuration, server certificate management, and backend auth architecture are out of scope except where they directly touch client-side keychain or trust APIs.
1415---
1617## Decision Tree
1819Determine the user's intent, then follow the matching branch. If ambiguous, ask.
2021```
22 ┌─────────────────────┐
23 │ What is the task? │
24 └─────────┬───────────┘
25 ┌──────────────────┼──────────────────┐
26 ▼ ▼ ▼
27 ┌─────────┐ ┌───────────┐ ┌────────────┐
28 │ REVIEW │ │ IMPROVE │ │ IMPLEMENT │
29 │ │ │ │ │ │
30 │ Audit │ │ Migrate / │ │ Build from │
31 │ existing│ │ modernize │ │ scratch │
32 │ code │ │ existing │ │ │
33 └────┬────┘ └─────┬─────┘ └─────┬──────┘
34 │ │ │
35 ▼ ▼ ▼
36 Run Top-Level Identify gap Identify which
37 Review Checklist (legacy store? domain(s) apply,
38 (§ below) against wrong API? load reference
39 the code. missing auth?) file(s), follow
40 Flag each item Load migration + ✅ patterns.
41 as ✅ / ❌ / domain-specific Implement with
42 ⚠️ N/A. reference files. add-or-update,
43 For each ❌, Follow ✅ patterns, proper error
44 cite the verify with domain handling, and
45 reference file checklist. correct access
46 and specific control from
47 section. the start.
48```
4950---
5152### Branch 1 — REVIEW (Audit Existing Code)
5354**Goal:** Systematically evaluate existing keychain/security code for correctness, security, and compliance.
5556**Procedure:**
57581. **Run the Top-Level Review Checklist** (below) against the code under review. Score each item ✅ / ❌ / ⚠️ N/A.
592. **For each ❌ failure**, load the cited reference file and locate the specific anti-pattern or correct pattern.
603. **Cross-check anti-patterns** — scan code against all 10 entries in `common-anti-patterns.md`. Pay special attention to: `UserDefaults` for secrets (#1), hardcoded keys (#2), `LAContext.evaluatePolicy()` as sole auth gate (#3), ignored `OSStatus` (#4).
614. **Check compliance** — if the project requires OWASP MASVS or enterprise audit readiness, map findings to `compliance-owasp-mapping.md` categories M1, M3, M9, M10.
625. **Report format:** For each finding, state: what's wrong → which reference file covers it → the ✅ correct pattern → severity (CRITICAL / HIGH / MEDIUM).
6364**Key reference files for review:**
6566- Start with: `common-anti-patterns.md` (backbone — covers 10 most dangerous patterns)
67- Then domain-specific files based on what the code does
68- Finish with: `compliance-owasp-mapping.md` (if compliance is relevant)
6970---
7172### Branch 2 — IMPROVE (Migrate / Modernize)
7374**Goal:** Upgrade existing code from insecure storage, deprecated APIs, or legacy patterns to current best practices.
7576**Procedure:**
77781. **Identify the migration type:**
79 - Insecure storage → Keychain: Load `migration-legacy-stores.md` + `credential-storage-patterns.md`
80 - Legacy Security framework → CryptoKit: Load `cryptokit-symmetric.md` or `cryptokit-public-key.md` + `migration-legacy-stores.md`
81 - RSA → Elliptic Curve: Load `cryptokit-public-key.md` (RSA migration section)
82 - GenericPassword → InternetPassword (AutoFill): Load `keychain-item-classes.md` (migration section)
83 - LAContext-only → Keychain-bound biometrics: Load `biometric-authentication.md`
84 - File-based keychain → Data protection keychain (macOS): Load `keychain-fundamentals.md` (TN3137 section)
85 - Single app → Shared keychain (extensions): Load `keychain-sharing.md`
86 - Leaf pinning → SPKI/CA pinning: Load `certificate-trust.md`
87882. **Follow the migration pattern** in the relevant reference file. Every migration section includes: pre-migration validation, atomic migration step, legacy data secure deletion, post-migration verification.
89903. **Run the domain-specific checklist** from the reference file after migration completes.
91924. **Verify no regressions** using guidance from `testing-security-code.md`.
9394---
9596### Branch 3 — IMPLEMENT (Build from Scratch)
9798**Goal:** Build new keychain/security functionality correctly from the start.
99100**Procedure:**
1011021. **Identify which domain(s) the task touches.** Use the Domain Selection Guide below.
1032. **Load the relevant reference file(s).** Follow ✅ code patterns — never deviate from them for the core security logic.
1043. **Apply Core Guidelines** (below) to every implementation.
1054. **Run the domain-specific checklist** before considering the implementation complete.
1065. **Add tests** following `testing-security-code.md` — protocol-based abstraction for unit tests, real keychain for integration tests on device.
107108**Domain Selection Guide:**
109110| If the task involves… | Load these reference files |
111| -------------------------------------- | ------------------------------------------------------------- |
112| Storing/reading a password or token | `keychain-fundamentals.md` + `credential-storage-patterns.md` |
113| Choosing which `kSecClass` to use | `keychain-item-classes.md` |
114| Setting when items are accessible | `keychain-access-control.md` |
115| Face ID / Touch ID gating | `biometric-authentication.md` + `keychain-access-control.md` |
116| Hardware-backed keys | `secure-enclave.md` |
117| Encrypting / hashing data | `cryptokit-symmetric.md` |
118| Signing / key exchange / HPKE | `cryptokit-public-key.md` |
119| OAuth tokens / API keys / logout | `credential-storage-patterns.md` |
120| Sharing between app and extension | `keychain-sharing.md` |
121| TLS pinning / client certificates | `certificate-trust.md` |
122| Replacing UserDefaults / plist secrets | `migration-legacy-stores.md` |
123| Writing tests for security code | `testing-security-code.md` |
124| Enterprise audit / OWASP compliance | `compliance-owasp-mapping.md` |
125126---
127128## Core Guidelines
129130These seven rules are non-negotiable. Every keychain/security implementation must satisfy all of them.
131132**1. Never ignore `OSStatus`.** Every `SecItem*` call returns an `OSStatus`. Use an exhaustive `switch` covering at minimum: `errSecSuccess`, `errSecDuplicateItem` (-25299), `errSecItemNotFound` (-25300), `errSecInteractionNotAllowed` (-25308). Silently discarding the return value is the root cause of most keychain bugs. → `keychain-fundamentals.md`
133134**2. Never use `LAContext.evaluatePolicy()` as a standalone auth gate.** This returns a `Bool` that is trivially patchable at runtime via Frida. Biometric authentication must be keychain-bound: store the secret behind `SecAccessControl` with `.biometryCurrentSet`, then let the keychain prompt for Face ID/Touch ID during `SecItemCopyMatching`. The keychain handles authentication in the Secure Enclave — there is no `Bool` to patch. → `biometric-authentication.md`
135136**3. Never store secrets in `UserDefaults`, `Info.plist`, `.xcconfig`, or `NSCoding` archives.** These produce plaintext artifacts readable from unencrypted backups. The Keychain is the only Apple-sanctioned store for credentials. → `credential-storage-patterns.md`, `common-anti-patterns.md`
137138**4. Never call `SecItem*` on `@MainActor`.** Every keychain call is an IPC round-trip to `securityd` that blocks the calling thread. Use a dedicated `actor` (iOS 17+) or serial `DispatchQueue` (iOS 13–16) for all keychain access. → `keychain-fundamentals.md`
139140**5. Always set `kSecAttrAccessible` explicitly.** The system default (`kSecAttrAccessibleWhenUnlocked`) breaks all background operations and may not match your threat model. Choose the most restrictive class that satisfies your access pattern. For background tasks: `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`. For highest sensitivity: `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly`. → `keychain-access-control.md`
141142**6. Always use the add-or-update pattern.** `SecItemAdd` followed by `SecItemUpdate` on `errSecDuplicateItem`. Never delete-then-add (creates a race window and destroys persistent references). Never call `SecItemAdd` without handling the duplicate case. → `keychain-fundamentals.md`
143144**7. Always target the data protection keychain on macOS.** Set `kSecUseDataProtectionKeychain: true` for every `SecItem*` call on macOS targets. Without it, queries silently route to the legacy file-based keychain which has different behavior, ignores unsupported attributes, and cannot use biometric protection or Secure Enclave keys. Mac Catalyst and iOS-on-Mac do this automatically. → `keychain-fundamentals.md`
145146---
147148## Quick Reference Tables
149150### Accessibility Constants — Selection Guide
151152| Constant | When Decryptable | Survives Backup | Survives Device Migration | Background Safe | Use When |
153| -------------------------------- | ---------------------------- | --------------- | ------------------------- | --------------- | ------------------------------------------------------ |
154| `WhenPasscodeSetThisDeviceOnly` | Unlocked + passcode set | ❌ | ❌ | ❌ | Highest-security secrets; removed if passcode removed |
155| `WhenUnlockedThisDeviceOnly` | Unlocked | ❌ | ❌ | ❌ | Device-bound secrets not needed in background |
156| `WhenUnlocked` | Unlocked | ✅ | ✅ | ❌ | Syncable secrets (system default — avoid implicit use) |
157| `AfterFirstUnlockThisDeviceOnly` | After first unlock → restart | ❌ | ❌ | ✅ | **Background tasks, push handlers, device-bound** |
158| `AfterFirstUnlock` | After first unlock → restart | ✅ | ✅ | ✅ | Background tasks that must survive restore |
159160**Deprecated (never use):** `kSecAttrAccessibleAlways`, `kSecAttrAccessibleAlwaysThisDeviceOnly` — deprecated iOS 12.
161162**Rule of thumb:** Need background access (push handlers, background refresh)? Start with `AfterFirstUnlockThisDeviceOnly`. Foreground-only? Start with `WhenUnlockedThisDeviceOnly`. Tighten to `WhenPasscodeSetThisDeviceOnly` for high-value secrets. Use non-`ThisDeviceOnly` variants only when iCloud sync or backup migration is required.
163164### CryptoKit Algorithm Selection
165166| Need | Algorithm | Min iOS | Notes |
167| ------------------------------- | ----------------------------------------------- | ------- | --------------------------------------------------------------------------- |
168| Hash data | `SHA256` / `SHA384` / `SHA512` | 13 | `SHA3_256`/`SHA3_512` available iOS 18+ |
169| Authenticate data (MAC) | `HMAC<SHA256>` | 13 | Always verify with constant-time comparison (built-in) |
170| Encrypt data (authenticated) | `AES.GCM` | 13 | 256-bit key, 96-bit nonce, 128-bit tag. **Never reuse nonce with same key** |
171| Encrypt data (mobile-optimized) | `ChaChaPoly` | 13 | Better on devices without AES-NI (older Apple Watch) |
172| Sign data | `P256.Signing` / `Curve25519.Signing` | 13 | Use P256 for interop, Curve25519 for performance |
173| Key agreement | `P256.KeyAgreement` / `Curve25519.KeyAgreement` | 13 | Always derive symmetric key via `HKDF` — never use raw shared secret |
174| Hybrid public-key encryption | `HPKE` | 17 | Replaces manual ECDH+HKDF+AES-GCM chains |
175| Hardware-backed signing | `SecureEnclave.P256.Signing` | 13 | P256 only; key never leaves hardware |
176| Post-quantum key exchange | `MLKEM768` | 26 | Formal verification (ML-KEM FIPS 203) |
177| Post-quantum signing | `MLDSA65` | 26 | Formal verification (ML-DSA FIPS 204) |
178| Password → key derivation | PBKDF2 (via `CommonCrypto`) | 13 | ≥600,000 iterations SHA-256 (OWASP 2024) |
179| Key → key derivation | `HKDF<SHA256>` | 13 | Extract-then-expand; always use info parameter for domain separation |
180181### Anti-Pattern Detection — Quick Scan
182183When reviewing code, search for these patterns. Any match is a finding.
184`❌` = insecure pattern signature to detect in user code. `✅` = apply the corrective pattern in the referenced file.
185186| Search For | Anti-Pattern | Severity | Reference |
187| ----------------------------------------------------------------------- | ------------------------------- | -------- | ---------------------------- |
188| `UserDefaults.standard.set` + token/key/secret/password | Plaintext credential storage | CRITICAL | `common-anti-patterns.md` #1 |
189| Hardcoded base64/hex strings (≥16 chars) in source | Hardcoded cryptographic key | CRITICAL | `common-anti-patterns.md` #2 |
190| `evaluatePolicy` without `SecItemCopyMatching` nearby | LAContext-only biometric gate | CRITICAL | `common-anti-patterns.md` #3 |
191| `SecItemAdd` without checking return / `OSStatus` | Ignored error code | HIGH | `common-anti-patterns.md` #4 |
192| No `kSecAttrAccessible` in add dictionary | Implicit accessibility class | HIGH | `common-anti-patterns.md` #5 |
193| `AES.GCM.Nonce()` inside a loop with same key | Potential nonce reuse | CRITICAL | `common-anti-patterns.md` #6 |
194| `sharedSecret.withUnsafeBytes` without HKDF | Raw shared secret as key | HIGH | `common-anti-patterns.md` #7 |
195| `kSecAttrAccessibleAlways` | Deprecated accessibility | HIGH | `keychain-access-control.md` |
196| `SecureEnclave.isAvailable` without `#if !targetEnvironment(simulator)` | Simulator false-negative trap | MEDIUM | `secure-enclave.md` |
197| `kSecAttrSynchronizable: true` + `ThisDeviceOnly` | Contradictory constraints | MEDIUM | `keychain-item-classes.md` |
198| `SecTrustEvaluate` (sync, deprecated) | Legacy trust evaluation | MEDIUM | `certificate-trust.md` |
199| `kSecClassGenericPassword` + `kSecAttrServer` | Wrong class for web credentials | MEDIUM | `keychain-item-classes.md` |
200201---
202203## Top-Level Review Checklist
204205Use this checklist for a rapid sweep across all 14 domains. Each item maps to one or more reference files for deep-dive investigation. For domain-specific deep checks, use the Summary Checklist at the bottom of each reference file.
206207- [ ] **1. Secrets are in Keychain, not UserDefaults/plist/source** — No credentials, tokens, or cryptographic keys in `UserDefaults`, `Info.plist`, `.xcconfig`, hardcoded strings, or `NSCoding` archives. OWASP M9 (Insecure Data Storage) directly violated. → `common-anti-patterns.md` #1–2, `credential-storage-patterns.md`, `migration-legacy-stores.md`, `compliance-owasp-mapping.md`
208209- [ ] **2. Every `OSStatus` is checked** — All `SecItem*` calls handle return codes with exhaustive `switch` or equivalent. No ignored returns. `errSecInteractionNotAllowed` is handled non-destructively (retry later, never delete). → `keychain-fundamentals.md`, `common-anti-patterns.md` #4
210211- [ ] **3. Biometric auth is keychain-bound** — If biometrics are used, authentication is enforced via `SecAccessControl` + keychain access, not `LAContext.evaluatePolicy()` alone. → `biometric-authentication.md`, `common-anti-patterns.md` #3
212213- [ ] **4. Accessibility classes are explicit and correct** — Every keychain item has an explicit `kSecAttrAccessible` value matching its access pattern (background vs foreground, device-bound vs syncable). No deprecated `Always` constants. → `keychain-access-control.md`
214215- [ ] **5. No `SecItem*` calls on `@MainActor`** — All keychain operations run on a dedicated `actor` or background queue. No synchronous keychain access in UI code, `viewDidLoad`, or `application(_:didFinishLaunchingWithOptions:)`. → `keychain-fundamentals.md`
216217- [ ] **6. Correct `kSecClass` for each item type** — Web credentials use `InternetPassword` (not GenericPassword) for AutoFill. Cryptographic keys use `kSecClassKey` with proper `kSecAttrKeyType`. App secrets use `GenericPassword` with `kSecAttrService` + `kSecAttrAccount`. → `keychain-item-classes.md`
218219- [ ] **7. CryptoKit used correctly** — Nonces never reused with the same key. ECDH shared secrets always derived through `HKDF` before use as symmetric keys. `SymmetricKey` material stored in Keychain, not in memory or files. Crypto operations covered by protocol-based unit tests. → `cryptokit-symmetric.md`, `cryptokit-public-key.md`, `testing-security-code.md`
220221- [ ] **8. Secure Enclave constraints respected** — SE keys are P256 only (classical), never imported (always generated on-device), device-bound (no backup/sync). Availability checks guard against simulator and keychain-access-groups entitlement issues. → `secure-enclave.md`
222223- [ ] **9. Sharing and access groups configured correctly** — `kSecAttrAccessGroup` uses full `TEAMID.group.identifier` format. Entitlements match between app and extensions. No accidental cross-app data exposure. → `keychain-sharing.md`
224225- [ ] **10. Certificate trust evaluation is current** — Uses `SecTrustEvaluateAsyncWithError` (not deprecated synchronous `SecTrustEvaluate`). Pinning strategy uses SPKI hash or `NSPinnedDomains` (not leaf certificate pinning which breaks on annual rotation). → `certificate-trust.md`
226227- [ ] **11. macOS targets data protection keychain** — All macOS `SecItem*` calls include `kSecUseDataProtectionKeychain: true` (except Mac Catalyst / iOS-on-Mac where it's automatic). → `keychain-fundamentals.md`
228229---
230231## References Index
232233| # | File | One-Line Description | Risk |
234| --- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------- |
235| 1 | `keychain-fundamentals.md` | SecItem\* CRUD, query dictionaries, OSStatus handling, actor-based wrappers, macOS TN3137 routing | CRITICAL |
236| 2 | `keychain-item-classes.md` | Five kSecClass types, composite primary keys, GenericPassword vs InternetPassword, ApplicationTag vs ApplicationLabel | HIGH |
237| 3 | `keychain-access-control.md` | Seven accessibility constants, SecAccessControl flags, data protection tiers, NSFileProtection sidebar | CRITICAL |
238| 4 | `biometric-authentication.md` | Keychain-bound biometrics, LAContext bypass vulnerability, enrollment change detection, fallback chains | CRITICAL |
239| 5 | `secure-enclave.md` | Hardware-backed P256 keys, CryptoKit SecureEnclave module, persistence, simulator traps, iOS 26 post-quantum | HIGH |
240| 6 | `cryptokit-symmetric.md` | SHA-2/3 hashing, HMAC, AES-GCM/ChaChaPoly encryption, SymmetricKey management, nonce handling, HKDF/PBKDF2 | HIGH |
241| 7 | `cryptokit-public-key.md` | ECDSA signing, ECDH key agreement, HPKE (iOS 17+), ML-KEM/ML-DSA post-quantum (iOS 26+), curve selection | HIGH |
242| 8 | `credential-storage-patterns.md` | OAuth2/OIDC token lifecycle, API key storage, refresh token rotation, runtime secrets, logout cleanup | CRITICAL |
243| 9 | `keychain-sharing.md` | Access groups, Team ID prefixes, app extensions, Keychain Sharing vs App Groups entitlements, iCloud sync | MEDIUM |
244| 10 | `certificate-trust.md` | SecTrust evaluation, SPKI/CA/leaf pinning, NSPinnedDomains, client certificates (mTLS), trust policies | HIGH |
245| 11 | `migration-legacy-stores.md` | UserDefaults/plist/NSCoding → Keychain migration, secure deletion, first-launch cleanup, versioned migration | MEDIUM |
246| 12 | `common-anti-patterns.md` | Top 10 AI-generated security mistakes with ❌/✅ code pairs, detection heuristics, OWASP mapping | CRITICAL |
247| 13 | `testing-security-code.md` | Protocol-based mocking, simulator vs device differences, CI/CD keychain, Swift Testing, mutation testing | MEDIUM |
248| 14 | `compliance-owasp-mapping.md` | OWASP Mobile Top 10 (2024), MASVS v2.1.0, MASTG test IDs, M1/M3/M9/M10 mapping, audit readiness | MEDIUM |
249250---
251252## Authoritative Sources
253254These are the primary sources underpinning all reference files. When in doubt, defer to these over any secondary source.
255256- **Apple Keychain Services Documentation** — canonical API reference
257- **Apple Platform Security Guide** (updated annually) — architecture and encryption design
258- **TN3137: "On Mac Keychain APIs and Implementations"** — macOS data protection vs file-based keychain
259- **Quinn "The Eskimo!" DTS Posts** — "SecItem: Fundamentals" and "SecItem: Pitfalls and Best Practices" (updated through 2025)
260- **WWDC 2019 Session 709** — "Cryptography and Your Apps" (CryptoKit introduction)
261- **WWDC 2025 Session 314** — "Get ahead with quantum-secure cryptography" (ML-KEM, ML-DSA)
262- **OWASP Mobile Top 10 (2024)** + **MASVS v2.1.0** + **MASTG v2** — compliance framework
263- **CISA/FBI "Product Security Bad Practices" v2.0** (January 2025) — hardcoded credentials classified as national security risk
264265---
266267## Agent Behavioral Rules
268269> The sections below govern how an AI agent should behave when using this skill: what's in scope, what's out, tone calibration, common mistakes to avoid, how to select reference files, and output formatting requirements.
270271### Scope Boundaries — Inclusions
272273This skill is authoritative for **client-side Apple platform security** across iOS, macOS, tvOS, watchOS, and visionOS:
274275- **Keychain Services** — `SecItemAdd`, `SecItemCopyMatching`, `SecItemUpdate`, `SecItemDelete`, query dictionary construction, `OSStatus` handling, actor/thread isolation, the data protection keychain on macOS (TN3137)
276- **Keychain item classes** — `kSecClassGenericPassword`, `kSecClassInternetPassword`, `kSecClassKey`, `kSecClassCertificate`, `kSecClassIdentity`, composite primary keys, AutoFill integration
277- **Access control** — The seven `kSecAttrAccessible` constants, `SecAccessControlCreateWithFlags`, data protection tiers, `NSFileProtection` correspondence
278- **Biometric authentication** — `LAContext` + keychain binding, the boolean gate vulnerability, enrollment change detection, fallback chains, `evaluatedPolicyDomainState`
279- **Secure Enclave** — CryptoKit `SecureEnclave.P256` module, hardware constraints (P256-only, no import, no export, no symmetric), persistence via keychain, simulator traps, iOS 26 post-quantum (ML-KEM, ML-DSA)
280- **CryptoKit symmetric** — SHA-2/SHA-3 hashing, HMAC, AES-GCM, ChaChaPoly, `SymmetricKey` lifecycle, nonce handling, HKDF, PBKDF2
281- **CryptoKit public-key** — ECDSA signing (P256/Curve25519), ECDH key agreement, HPKE (iOS 17+), ML-KEM/ML-DSA (iOS 26+), curve selection
282- **Credential storage patterns** — OAuth2/OIDC token lifecycle, API key storage, refresh token rotation, runtime secret fetching, logout cleanup
283- **Keychain sharing** — Access groups, Team ID prefixes, `keychain-access-groups` vs `com.apple.security.application-groups` entitlements, extensions, iCloud Keychain sync
284- **Certificate trust** — `SecTrust` evaluation, SPKI/CA/leaf pinning, `NSPinnedDomains`, client certificates (mTLS), trust policies
285- **Migration** — UserDefaults/plist/NSCoding → Keychain migration, secure legacy deletion, first-launch cleanup, versioned migration
286- **Testing** — Protocol-based mocking, simulator vs device differences, CI/CD keychain creation, Swift Testing patterns
287- **Compliance** — OWASP Mobile Top 10 (2024), MASVS v2.1.0, MASTG v2 test IDs, CISA/FBI Bad Practices
288289**Edge cases that ARE in scope:** Client-side certificate loading for mTLS pinning (`certificate-trust.md`). Passkey/AutoFill credential storage in Keychain (`keychain-item-classes.md`, `credential-storage-patterns.md`). `@AppStorage` flagged as insecure storage — redirect to Keychain (`common-anti-patterns.md`).
290291### Scope Boundaries — Exclusions
292293Do **not** answer the following topics using this skill. Briefly explain they are out of scope and suggest where to look.
294295| Topic | Why excluded | Redirect to |
296| ---------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
297| **App Transport Security (ATS)** | Server-side TLS policy, not client keychain | Apple's ATS documentation, `Info.plist` NSAppTransportSecurity reference |
298| **CloudKit encryption** | Server-managed key hierarchy, not client CryptoKit | CloudKit documentation, `CKRecord.encryptedValues` |
299| **Network security / URLSession TLS config** | Transport layer, not storage layer | Apple URL Loading System docs; this skill covers only client certificate loading for mTLS |
300| **Server-side auth architecture** | Backend JWT issuance, OAuth provider config | OWASP ASVS (Application Security Verification Standard) |
301| **WebAuthn / passkeys server-side** | Relying party implementation | Apple "Supporting passkeys" documentation; this skill covers client-side `ASAuthorizationController` only where it stores credentials in Keychain |
302| **Code signing / provisioning profiles** | Build/distribution, not runtime security | Apple code signing documentation |
303| **Jailbreak detection** | Runtime integrity, not cryptographic storage | OWASP MASTG MSTG-RESILIENCE category |
304| **SwiftUI `@AppStorage`** | Wrapper over `UserDefaults` — out of scope except to flag it as insecure for secrets | `common-anti-patterns.md` #1 flags it; no deeper coverage |
305| **Cross-platform crypto (OpenSSL, LibSodium)** | Third-party libraries, not Apple frameworks | Respective library documentation |
306307---
308309### Tone Rules
310311This skill is **non-opinionated and correctness-focused**. Tone calibrates based on severity.
312313**Default tone — advisory.** Use "consider," "suggest," "one approach is," "a common pattern is" for: architecture choices (wrapper class design, actor vs DispatchQueue), algorithm selection when multiple valid options exist (P256 vs Curve25519, AES-GCM vs ChaChaPoly), accessibility class selection when the threat model is unclear, testing strategy, code organization.
314315**Elevated tone — directive.** Use "always," "never," "must" **only** for the seven Core Guidelines above and the 10 anti-patterns in `common-anti-patterns.md`. These are security invariants, not style preferences. The exhaustive list of directives:
3163171. Never ignore `OSStatus` — always check return codes from `SecItem*` calls. → `keychain-fundamentals.md`
3182. Never use `LAContext.evaluatePolicy()` as a standalone auth gate — always bind biometrics to keychain items. → `biometric-authentication.md`
3193. Never store secrets in `UserDefaults`, `Info.plist`, `.xcconfig`, or `NSCoding` archives. → `credential-storage-patterns.md`, `common-anti-patterns.md`
3204. Never call `SecItem*` on `@MainActor` — always use a background actor or queue. → `keychain-fundamentals.md`
3215. Always set `kSecAttrAccessible` explicitly on every `SecItemAdd`. → `keychain-access-control.md`
3226. Always use the add-or-update pattern (`SecItemAdd` → `SecItemUpdate` on `errSecDuplicateItem`). → `keychain-fundamentals.md`
3237. Always set `kSecUseDataProtectionKeychain: true` on macOS targets. → `keychain-fundamentals.md`
3248. Never reuse a nonce with the same AES-GCM key. → `cryptokit-symmetric.md`, `common-anti-patterns.md`
3259. Never use a raw ECDH shared secret as a symmetric key — always derive through HKDF. → `cryptokit-public-key.md`, `common-anti-patterns.md`
32610. Never use `Insecure.MD5` or `Insecure.SHA1` for security purposes. → `cryptokit-symmetric.md`, `common-anti-patterns.md`
327328If a pattern is not on this list, use advisory tone. Do not escalate warnings beyond what the reference files support.
329330**Tone when declining.** When a query falls outside scope, be direct but not dismissive: "This skill covers client-side keychain and CryptoKit. For ATS configuration, Apple's NSAppTransportSecurity documentation is the right reference." State the boundary, suggest an alternative, move on.
331332---
333334### Common AI Mistakes — The 10 Most Likely Incorrect Outputs
335336Before finalizing any output, scan for all 10. Each links to the reference file containing the correct pattern.
337Each entry is intentionally paired: `❌` incorrect generated behavior and `✅` corrective pattern to use instead.
338339**Mistake #1 — Generating `LAContext.evaluatePolicy()` as the sole biometric gate.** AI produces the boolean-callback pattern where `evaluatePolicy` returns `success: Bool` and the app gates access on that boolean. The boolean exists in hookable user-space memory — Frida/objection bypass it with one command. **✅ Correct pattern:** Store a secret behind `SecAccessControl` with `.biometryCurrentSet`, retrieve via `SecItemCopyMatching`. → `biometric-authentication.md`
340341**Mistake #2 — Suggesting `SecureEnclave.isAvailable` without simulator guard.** AI generates `if SecureEnclave.isAvailable { ... }` without `#if !targetEnvironment(simulator)`. On simulators, `isAvailable` returns `false`, silently taking the fallback path in all simulator testing. **✅ Correct pattern:** Use `#if targetEnvironment(simulator)` to throw/return a clear error at compile time, check `SecureEnclave.isAvailable` only in device builds. → `secure-enclave.md`
342343**Mistake #3 — Importing external keys into the Secure Enclave.** AI generates `SecureEnclave.P256.Signing.PrivateKey(rawRepresentation: someData)`. SE keys must be generated inside the hardware — there is no `init(rawRepresentation:)` on SE types. `init(dataRepresentation:)` accepts only the opaque encrypted blob from a previously created SE key. **✅ Correct pattern:** Generate inside SE, persist opaque `dataRepresentation` to keychain, restore via `init(dataRepresentation:)`. → `secure-enclave.md`
344345**Mistake #4 — Using `SecureEnclave.AES` or SE for symmetric encryption.** AI generates references to non-existent SE symmetric APIs. The SE's internal AES engine is not exposed as a developer API. Pre-iOS 26, the SE supports only P256 signing and key agreement. iOS 26 adds ML-KEM and ML-DSA, not symmetric primitives. **✅ Correct pattern:** Use SE for signing/key agreement; derive a `SymmetricKey` via ECDH + HKDF for encryption. → `secure-enclave.md`, `cryptokit-symmetric.md`
346347**Mistake #5 — Omitting `kSecAttrAccessible` in `SecItemAdd`.** AI builds add dictionaries without an accessibility attribute. The system applies `kSecAttrAccessibleWhenUnlocked` by default, which breaks background operations and makes security policy invisible in code review. **✅ Correct pattern:** Always set `kSecAttrAccessible` explicitly. → `keychain-access-control.md`
348349**Mistake #6 — Using `SecItemAdd` without handling `errSecDuplicateItem`.** AI checks only for `errSecSuccess`, or uses delete-then-add. Without duplicate handling, the second save silently fails. Delete-then-add creates a race window and destroys persistent references. **✅ Correct pattern:** Add-or-update pattern. → `keychain-fundamentals.md`
350351**Mistake #7 — Specifying explicit nonces for AES-GCM encryption.** AI creates a nonce manually and passes it to `AES.GCM.seal`. Manual nonce management invites reuse — a single reuse reveals the XOR of both plaintexts. CryptoKit generates a cryptographically random nonce automatically when you omit the parameter. **✅ Correct pattern:** Call `AES.GCM.seal(plaintext, using: key)` without a `nonce:` parameter. → `cryptokit-symmetric.md`, `common-anti-patterns.md` #6
352353**Mistake #8 — Using raw ECDH shared secret as a symmetric key.** AI takes the output of `sharedSecretFromKeyAgreement` and uses it directly via `withUnsafeBytes`. Raw shared secrets have non-uniform distribution. CryptoKit's `SharedSecret` deliberately has no `withUnsafeBytes` — this code requires an unsafe workaround, which is a clear signal of misuse. **✅ Correct pattern:** Always derive via `sharedSecret.hkdfDerivedSymmetricKey(...)`. → `cryptokit-public-key.md`, `common-anti-patterns.md` #7
354355**Mistake #9 — Claiming SHA-3 requires iOS 26.** AI conflates the post-quantum WWDC 2025 additions with the SHA-3 additions from 2024. SHA-3 family types were added in **iOS 18 / macOS 15**. iOS 26 introduced ML-KEM and ML-DSA, not SHA-3. **✅ Correct version tags:** SHA-3 → iOS 18+. ML-KEM/ML-DSA → iOS 26+. → `cryptokit-symmetric.md`
356357**Mistake #10 — Missing first-launch keychain cleanup.** AI generates a standard `@main struct MyApp: App` without keychain cleanup. Keychain items survive app uninstallation. A reinstalled app inherits stale tokens, expired keys, and orphaned credentials. **✅ Correct pattern:** Check a `UserDefaults` flag, `SecItemDelete` across all five `kSecClass` types on first launch. → `common-anti-patterns.md` #9, `migration-legacy-stores.md`
358359---
360361### Reference File Loading Rules
362363Load the **minimum set** of files needed to answer the query. Do not load all 14 — they total ~7,000+ lines and will dilute focus.
364365| Query type | Load these files | Reason |
366| -------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------- |
367| "Review my keychain code" | `common-anti-patterns.md` → then domain-specific files based on what the code does | Anti-patterns file is the review backbone |
368| "Is this biometric auth secure?" | `biometric-authentication.md` + `common-anti-patterns.md` (#3) | Boolean gate is the #1 biometric risk |
369| "Store a token / password" | `keychain-fundamentals.md` + `credential-storage-patterns.md` | CRUD + lifecycle |
370| "Encrypt / hash data" | `cryptokit-symmetric.md` | Symmetric operations |
371| "Sign data / key exchange" | `cryptokit-public-key.md` | Asymmetric operations |
372| "Use Secure Enclave" | `secure-enclave.md` + `keychain-fundamentals.md` | SE keys need keychain persistence |
373| "Share keychain with extension" | `keychain-sharing.md` + `keychain-fundamentals.md` | Access groups + CRUD |
374| "Migrate from UserDefaults" | `migration-legacy-stores.md` + `credential-storage-patterns.md` | Migration + target patterns |
375| "TLS pinning / mTLS" | `certificate-trust.md` | Trust evaluation |
376| "Which kSecClass?" | `keychain-item-classes.md` | Class selection + primary keys |
377| "Set up data protection" | `keychain-access-control.md` | Accessibility constants |
378| "Write tests for keychain code" | `testing-security-code.md` | Protocol mocks + CI/CD |
379| "OWASP compliance audit" | `compliance-owasp-mapping.md` + `common-anti-patterns.md` | Mapping + detection |
380| "Full security review" | `common-anti-patterns.md` + all files touched by the code |
381382…(truncated)
Run npx skillmds@latest add ivan-magda/swift-security-expert in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Use when working with iOS/macOS Keychain Services (SecItem queries, kSecClass, OSStatus errors), biometric authentication (LAContext, Face ID, Touch ID), CryptoKit (AES-GCM, ChaChaPoly, ECDSA, ECDH, HPKE, ML-KEM), Secure Enclave, secure credential storage (OAuth tokens, API keys), certificate pinning (SecTrust, SPKI), keychain sharing across apps/extensions, migrating secrets from UserDefaults or plists, or OWASP MASVS/MASTG mobile compliance on Apple platforms. It is listed under Security on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
ivan-magda (@ivan-magda) published this skill. Their other Agent Skills are listed on their SkillMD profile.