---
name: swift-security
description: "Apple client security: Keychain (SecItem, kSecClass, OSStatus, iOS, macOS), Face ID/Touch ID keychain items, CryptoKit (AES-GCM, ChaChaPoly, ECDSA, ECDH, HPKE, ML-KEM), Secure Enclave, OAuth token and API key storage, SecTrust/SPKI pinning, keychain sharing, moving secrets out of UserDefaults or plists, OWASP MASVS/MASTG. Use when writing, reviewing or migrating code that stores secrets, holds keys or evaluates trust. Not for passkeys, App Attest or App Review."
metadata:
  source: multi-agent-pipeline
---

# Swift Security

This skill covers the parts of an Apple-platform app that hold secrets on the
device: the keychain, biometric gates, CryptoKit primitives, Secure Enclave keys,
server trust, and the audit vocabulary around them.

If nobody states a deployment target, write examples for iOS 17 and later with
Swift concurrency (actors, `async`/`await`). If the user names an older target,
keep an iOS 13 compatible path alongside (serial queue, completion handlers).
Everything CryptoKit shipped for post-quantum work in iOS 26 (SHA-3, ML-KEM,
ML-DSA, X-Wing, the Secure Enclave lattice types) sits behind an availability
check; never present it as usable on earlier systems.

## Workflow

Decide which of three jobs the request is before opening any reference file.

1. **Review of existing code.** Walk the Review Checklist below. For every row
   that fails, open [Anti-pattern catalog](references/common-anti-patterns.md)
   and the domain file for that row. Report each finding with a severity, the
   offending code as evidence, and the corrected pattern.
2. **Improvement or migration.** Name the kind of migration (plaintext to
   keychain, class change, accessibility change, sharing, macOS backend). Open
   [Legacy migration](references/migration-legacy-stores.md) and the
   file for the target domain. Keep the user's existing data readable, read the
   new item back to prove the write, and remove the old copy only after that
   check passes.
3. **New implementation.** Open the fewest domain files that answer the
   question and follow what they show. Handle every OSStatus, add tests, and
   finish by running the matching part of the checklist.

Load references on demand. Opening all of them for every question wastes
context and blurs the answer.

## Which Reference to Open

| The task involves | Open |
| --- | --- |
| Keychain CRUD, return types, OSStatus codes | [Keychain basics](references/keychain-fundamentals.md) |
| Picking a `kSecClass`, primary keys, item identity | [Item classes](references/keychain-item-classes.md) |
| `kSecAttrAccessible` values, `SecAccessControl` flags | [Access control](references/keychain-access-control.md) |
| Face ID, Touch ID, secrets released by biometrics | [Biometrics](references/biometric-authentication.md) |
| Keys generated in the Secure Enclave | [Enclave keys](references/secure-enclave.md) |
| SHA-2/SHA-3, HMAC, AES-GCM, ChaChaPoly, HKDF, PBKDF2 | [Symmetric crypto](references/cryptokit-symmetric.md) |
| Signatures, ECDH, HPKE, ML-KEM, ML-DSA | [Public-key crypto](references/cryptokit-public-key.md) |
| OAuth tokens, API keys, refresh rotation, logout | [Credential lifecycles](references/credential-storage-patterns.md) |
| Sharing items between an app and its extensions or sibling apps | [Sharing](references/keychain-sharing.md) |
| `SecTrust`, SPKI pinning, client certificates (mTLS) | [Trust and pinning](references/certificate-trust.md) |
| Moving data out of UserDefaults, plists, NSCoding archives | [Legacy migration](references/migration-legacy-stores.md) |
| Unit, integration, Simulator, device and CI testing | [Testing](references/testing-security-code.md) |
| OWASP MASVS, MASTG, audit evidence | [OWASP mapping](references/compliance-owasp-mapping.md) |
| A full security review | [Anti-pattern catalog](references/common-anti-patterns.md) first, then each domain the code touches |

## Security Invariants

Only these rules, and the anti-patterns that break them, are phrased as "must"
or "never". Everything else in this skill is a recommendation with trade-offs
and should be worded that way.

1. Never place refresh tokens, signing keys, API keys, passwords or tokens in
   `UserDefaults`, `Info.plist`, `.xcconfig` values, source literals, log
   output, plain files or `NSCoding` archives. Use the keychain, or fetch the
   value from a server at runtime.
2. Never discard an `OSStatus`. Every path through `SecItemAdd`,
   `SecItemCopyMatching`, `SecItemUpdate` and `SecItemDelete` handles success
   and the expected failures `errSecDuplicateItem`, `errSecItemNotFound` and
   `errSecInteractionNotAllowed`.
3. Never let the `Bool` from `LAContext.evaluatePolicy()` be the only thing
   standing between a user and a secret. Put the secret in a keychain item
   protected by `SecAccessControl`; reading the item makes the system run the
   biometric check.
4. Set `kSecAttrAccessible` or `kSecAttrAccessControl` explicitly on every add.
5. Save with add-or-update: call `SecItemAdd`, and when it returns
   `errSecDuplicateItem`, call `SecItemUpdate`. Deleting and re-adding is
   reserved for changing an item's access control, which `SecItemUpdate`
   cannot do.
6. `SecItem*` calls must not run on the main actor. Isolate them in an actor
   (available from iOS 13) or, in completion-handler code, a private serial
   queue.
7. On macOS, pass `kSecUseDataProtectionKeychain: true` unless the code is
   deliberately working with the legacy file-based keychain.
8. Never encrypt twice with the same AES-GCM nonce under one key.
9. Never use the bytes of an ECDH `SharedSecret` as a key. Derive one with
   HKDF or X9.63 first.
10. Never let `Insecure.SHA1` or `Insecure.MD5` protect anything; they exist
    for legacy interoperability only.

## Settled Facts

These answers apply across every reference file in this skill.

- `errSecUserCanceled` is `-128` (the user dismissed the prompt).
  `errSecAuthFailed` is `-25293` (authentication failed or the protected item is
  no longer usable).
- An item cannot be both synchronizable and `...ThisDeviceOnly`. Apple's
  `kSecAttrSynchronizable` documentation forbids pairing it with an
  accessibility value that ends in `ThisDeviceOnly`; the add is rejected with
  `errSecParam` (-50) rather than quietly staying local.
- The Simulator has no Secure Enclave. Branch on
  `#if targetEnvironment(simulator)` at compile time and use
  `SecureEnclave.isAvailable` as the runtime check on real hardware; do not base
  Simulator behaviour on what the flag reports there.
- Secure Enclave private keys that carry an access control always include
  `.privateKeyUsage` in its flags, for example
  `[.privateKeyUsage, .biometryCurrentSet]`.
- Any code path that shows Face ID needs `NSFaceIDUsageDescription` in
  `Info.plist`. The `LAContext` header asks apps to supply it, shows its string
  the first time Face ID is used, and fails evaluations with
  `LAError.biometryNotAvailable` once the user denies Face ID for the app. That
  applies to keychain reads that trigger Face ID as much as to `evaluatePolicy`.
- `arc4random` on Apple platforms is backed by a cryptographically secure
  generator, not RC4. Its limits are a 32-bit result and the bias of `% n`. For
  keys, nonces, salts and tokens, use `SecRandomCopyBytes`,
  `SymmetricKey(size:)` or `SystemRandomNumberGenerator` so the size and the
  failure path are explicit.
- After migrating a secret, remove the plaintext copy as soon as the keychain
  read-back succeeds. A rollback window that keeps plaintext on disk defeats the
  migration; keep any rollback state in the keychain instead.

## Sibling Boundaries

This skill owns on-device secret storage, cryptographic primitives, keys held
in hardware, and checks of server trust. Adjacent topics appear here only to
point at the right skill:

- `authentication`: passkeys, Sign in with Apple, OAuth sign-in flows,
  `ASAuthorizationController`, credential state, sign-in UX, and `LAContext`
  prompts and policy that do not guard a keychain item.
- `cryptokit`: CryptoKit questions with no storage, keychain, Secure Enclave,
  trust or compliance angle.
- `device-integrity`: DeviceCheck and App Attest attestations and assertions.
- `ios-networking`: URLSession, request pipelines, ATS settings, retries,
  caching, reachability and transport design.
- `app-store-review`: privacy manifests, ATT, App Review guidelines and
  submission readiness.

## Review Checklist

Mark each row pass, fail or n/a. A fail names the reference file and a severity.

- [ ] No secret sits in source code, `UserDefaults`, a plist, a log line, a plain file or an archive.
- [ ] Each `SecItem*` call inspects its `OSStatus` and handles the recoverable cases.
- [ ] Biometric access to a secret goes through a `SecAccessControl` keychain item, not an `evaluatePolicy()` Bool.
- [ ] Add dictionaries carry an explicit accessibility value.
- [ ] Saves are add-or-update; delete-then-add appears only for access-control changes.
- [ ] Keychain calls run away from the UI and the main actor.
- [ ] The `kSecClass` fits the item and its primary-key attributes are all set.
- [ ] CryptoKit use avoids nonce reuse, raw shared secrets, weak hashes and embedded keys.
- [ ] Secure Enclave code checks availability, separates Simulator from device, stores only `dataRepresentation`, and assumes keys never leave the device.
- [ ] App and extension sharing uses Team ID prefixed access groups with matching entitlements on every target.
- [ ] Trust evaluation uses current `SecTrust` APIs, checks the host name and policy, and pins SPKI or a CA when pinning is required.
- [ ] On macOS, the choice between the file-based keychain and the data protection keychain is deliberate.
- [ ] Tests exercise the happy path, a duplicate add, an absent item, a locked device, Simulator against hardware, and migration where it applies.
- [ ] A MASVS/MASTG mapping is included when compliance was asked for.

## Common Mistakes

- Keychain snippets that stop at `SecItemAdd` with no duplicate branch or no
  `errSecItemNotFound` branch.
- A Face ID prompt bolted onto the UI while the secret itself remains readable
  without any keychain access control.
- Leaving out `kSecAttrAccessible`, which silently means
  `kSecAttrAccessibleWhenUnlocked`.
- Using `kSecAttrAccessibleAlways` or `kSecAttrAccessibleAlwaysThisDeviceOnly`;
  both are deprecated.
- One add dictionary that carries both `kSecAttrAccessControl` and
  `kSecAttrAccessible`.
- Expecting to import, export or sync an enclave key, or asking the enclave to
  perform symmetric encryption.
- Offering the CryptoKit ML-KEM, ML-DSA, X-Wing or SHA-3 types on systems older
  than iOS 26.
- Offering HPKE on systems older than iOS 17.
- Computing a pin over bare public-key bytes instead of the DER-encoded
  SubjectPublicKeyInfo.
- Wandering into sign-in flows, networking stacks, App Attest or App Review
  instead of handing those parts to the sibling skill.

## Output Rules

- Severity: **CRITICAL** is an exploitable secret exposure or broken crypto;
  **HIGH** is a silent break in a security boundary or a data-loss path;
  **MEDIUM** is hardening that is brittle or incomplete.
- When reviewing code with a concrete anti-pattern, show the wrong code and the
  corrected code.
- State the minimum iOS and macOS version whenever you recommend an API that is
  versioned.
- Name the reference file that backs each substantive security pattern.
- Keychain samples always show `OSStatus` handling and an explicit
  accessibility value.
- Close implementation and migration answers with a `## Reference Files`
  section: each reference you loaded plus one line on why.
- Do not make up WWDC session numbers or citations. When a claim has no source
  at hand, say it needs verification.

## References

- [Keychain basics](references/keychain-fundamentals.md): the four SecItem calls, return types, OSStatus handling, actor wrapper, macOS backends.
- [Item classes](references/keychain-item-classes.md): the five item classes, their primary keys, keys, certificates and identities.
- [Access control](references/keychain-access-control.md): accessibility constants, `SecAccessControl` flags, background access, file protection.
- [Biometrics](references/biometric-authentication.md): keychain-bound biometrics, `LAContext`, enrollment changes.
- [Enclave keys](references/secure-enclave.md): enclave limits, CryptoKit `SecureEnclave`, key persistence, iOS 26 post-quantum types.
- [Symmetric crypto](references/cryptokit-symmetric.md): hashing, HMAC, AES-GCM, ChaChaPoly, HKDF, PBKDF2.
- [Public-key crypto](references/cryptokit-public-key.md): ECDSA, ECDH, HPKE, ML-KEM, ML-DSA, key formats, moving off RSA.
- [Credential lifecycles](references/credential-storage-patterns.md): OAuth tokens, API keys, refresh rotation, logout cleanup.
- [Sharing](references/keychain-sharing.md): access groups, entitlements, extensions, iCloud Keychain, macOS groups.
- [Trust and pinning](references/certificate-trust.md): `SecTrust`, SPKI and CA pinning, `NSPinnedDomains`, client certificates.
- [Legacy migration](references/migration-legacy-stores.md): moving secrets out of UserDefaults, plists and archives, then cleaning up.
- [Anti-pattern catalog](references/common-anti-patterns.md): the catalog of insecure patterns that drives a review.
- [Testing](references/testing-security-code.md): protocol mocks, real keychain tests, the CI and device split.
- [OWASP mapping](references/compliance-owasp-mapping.md): OWASP Mobile Top 10, MASVS and MASTG evidence.
