{"id":"4fd0a945-f55d-450a-9ce2-6f44d792c7c1","shortId":"fZwx8V","kind":"skill","title":"Ios Security","tagline":"Swift Ios Skills skill by Dpearson2699","description":"# iOS Security\n\nGuidance for handling sensitive data, authenticating users, encrypting\ncorrectly, and following Apple's security best practices on iOS.\n\n## Contents\n\n- [Keychain Services](#keychain-services)\n- [Data Protection](#data-protection)\n- [CryptoKit](#cryptokit)\n- [Secure Enclave](#secure-enclave)\n- [Biometric Authentication](#biometric-authentication)\n- [App Transport Security (ATS)](#app-transport-security-ats)\n- [Certificate Pinning](#certificate-pinning)\n- [Secure Coding Patterns](#secure-coding-patterns)\n- [Privacy Manifests](#privacy-manifests)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n## Keychain Services\n\nThe Keychain is the ONLY correct place to store sensitive data. Never store\npasswords, tokens, API keys, or secrets in UserDefaults, files, or Core Data.\n\n### Storing Credentials\n\n```swift\nfunc saveToKeychain(account: String, data: Data, service: String) throws {\n    let query: [String: Any] = [\n        kSecClass as String: kSecClassGenericPassword,\n        kSecAttrAccount as String: account,\n        kSecAttrService as String: service,\n        kSecValueData as String: data,\n        kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly\n    ]\n\n    let status = SecItemAdd(query as CFDictionary, nil)\n\n    if status == errSecDuplicateItem {\n        let updateQuery: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrAccount as String: account,\n            kSecAttrService as String: service\n        ]\n        let updates: [String: Any] = [kSecValueData as String: data]\n        let updateStatus = SecItemUpdate(updateQuery as CFDictionary, updates as CFDictionary)\n        guard updateStatus == errSecSuccess else {\n            throw KeychainError.updateFailed(updateStatus)\n        }\n    } else if status != errSecSuccess {\n        throw KeychainError.saveFailed(status)\n    }\n}\n```\n\n### Reading Credentials\n\n```swift\nfunc readFromKeychain(account: String, service: String) throws -> Data {\n    let query: [String: Any] = [\n        kSecClass as String: kSecClassGenericPassword,\n        kSecAttrAccount as String: account,\n        kSecAttrService as String: service,\n        kSecReturnData as String: true,\n        kSecMatchLimit as String: kSecMatchLimitOne\n    ]\n\n    var result: AnyObject?\n    let status = SecItemCopyMatching(query as CFDictionary, &result)\n\n    guard status == errSecSuccess, let data = result as? Data else {\n        throw KeychainError.readFailed(status)\n    }\n    return data\n}\n```\n\n### Deleting Credentials\n\n```swift\nfunc deleteFromKeychain(account: String, service: String) throws {\n    let query: [String: Any] = [\n        kSecClass as String: kSecClassGenericPassword,\n        kSecAttrAccount as String: account,\n        kSecAttrService as String: service\n    ]\n\n    let status = SecItemDelete(query as CFDictionary)\n    guard status == errSecSuccess || status == errSecItemNotFound else {\n        throw KeychainError.deleteFailed(status)\n    }\n}\n```\n\n### kSecAttrAccessible Values\n\n| Value | When Available | Device-Only | Use For |\n|---|---|---|---|\n| `kSecAttrAccessibleWhenUnlocked` | Device unlocked | No | General credentials |\n| `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` | Device unlocked | Yes | Sensitive credentials |\n| `kSecAttrAccessibleAfterFirstUnlock` | After first unlock | No | Background-accessible tokens |\n| `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` | After first unlock | Yes | Background tokens, no backup |\n| `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly` | Passcode set + unlocked | Yes | Highest security |\n\nRules:\n- Use `ThisDeviceOnly` variants for sensitive data. Prevents backup/restore to other devices.\n- Use `AfterFirstUnlock` for tokens needed by background operations.\n- Use `WhenPasscodeSetThisDeviceOnly` for most sensitive data. Item is deleted if passcode is removed.\n- NEVER use `kSecAttrAccessibleAlways` (deprecated and insecure).\n\n### Keychain Access Groups\n\nShare keychain items across apps from the same team:\n\n```swift\nlet query: [String: Any] = [\n    kSecClass as String: kSecClassGenericPassword,\n    kSecAttrAccount as String: \"shared-token\",\n    kSecAttrAccessGroup as String: \"TEAMID.com.company.shared\"\n]\n```\n\n### @AppStorage vs Keychain\n\n| Storage | Use For | Security |\n|---------|---------|----------|\n| `@AppStorage` / `UserDefaults` | Non-sensitive preferences (theme, onboarding state, feature flags) | Not encrypted at rest |\n| Keychain | Passwords, tokens, API keys, secrets | Hardware-encrypted, access-controlled |\n\n**Rule:** If the data would be embarrassing or dangerous if exposed, it goes in Keychain. Everything else can use `@AppStorage`.\n\n```swift\n// Non-sensitive preference -- @AppStorage is fine\n@AppStorage(\"hasCompletedOnboarding\") private var hasOnboarded = false\n\n// Sensitive credential -- MUST use Keychain\n// WRONG: @AppStorage(\"authToken\") private var token = \"\"\n// CORRECT: Use saveToKeychain(account:data:service:)\n```\n\n## Data Protection\n\niOS encrypts files based on their protection class:\n\n| Class | When Available | Use For |\n|---|---|---|\n| `.complete` | Only when unlocked | Sensitive user data |\n| `.completeUnlessOpen` | Open handles survive lock | Active downloads, recordings |\n| `.completeUntilFirstUserAuthentication` | After first unlock (default) | Most app data |\n| `.none` | Always | Non-sensitive, system-needed data |\n\n```swift\n// Set file protection\ntry data.write(to: url, options: .completeFileProtection)\n\n// Check protection level\nlet attributes = try FileManager.default.attributesOfItem(atPath: path)\nlet protection = attributes[.protectionKey] as? FileProtectionType\n```\n\nUse `.complete` for any file containing user-sensitive data. The default\n`.completeUntilFirstUserAuthentication` is acceptable for general app data.\n\n## CryptoKit\n\nUse CryptoKit for all cryptographic operations. Do not use CommonCrypto or the\nraw Security framework for new code.\n\n### Symmetric Encryption (AES-GCM)\n\n```swift\nimport CryptoKit\n\nlet key = SymmetricKey(size: .bits256)\n\nfunc encrypt(_ data: Data, using key: SymmetricKey) throws -> Data {\n    let sealed = try AES.GCM.seal(data, using: key)\n    guard let combined = sealed.combined else {\n        throw CryptoError.sealFailed\n    }\n    return combined\n}\n\nfunc decrypt(_ data: Data, using key: SymmetricKey) throws -> Data {\n    let box = try AES.GCM.SealedBox(combined: data)\n    return try AES.GCM.open(box, using: key)\n}\n```\n\n### Hashing\n\n```swift\nlet hash = SHA256.hash(data: data)\nlet hashString = hash.compactMap { String(format: \"%02x\", $0) }.joined()\n\n// Also available: SHA384, SHA512\n```\n\n### HMAC (Message Authentication)\n\n```swift\nlet key = SymmetricKey(size: .bits256)\n\n// Sign\nlet signature = HMAC<SHA256>.authenticationCode(for: data, using: key)\n\n// Verify\nlet isValid = HMAC<SHA256>.isValidAuthenticationCode(signature, authenticating: data, using: key)\n```\n\nFor digital signatures (P256/ECDSA), key agreement (Curve25519), ChaChaPoly,\nand HKDF key derivation, see [references/cryptokit-advanced.md](references/cryptokit-advanced.md).\n\n## Secure Enclave\n\nFor the highest security, store keys in the Secure Enclave. Keys never leave\nthe hardware. Only P256 is supported.\n\n```swift\nguard SecureEnclave.isAvailable else { return }\n\nlet accessControl = SecAccessControlCreateWithFlags(\n    nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,\n    [.privateKeyUsage, .biometryCurrentSet], nil\n)!\nlet privateKey = try SecureEnclave.P256.Signing.PrivateKey(accessControl: accessControl)\n\nlet signature = try privateKey.signature(for: data)  // May trigger biometric prompt\nlet isValid = privateKey.publicKey.isValidSignature(signature, for: data)\n\n// Persist: store dataRepresentation in Keychain, restore with:\nlet restored = try SecureEnclave.P256.Signing.PrivateKey(\n    dataRepresentation: privateKey.dataRepresentation\n)\n```\n\n## Biometric Authentication\n\nThis section covers biometric protection for Keychain items and data\naccess. For user-facing biometric sign-in flows (`LAContext.evaluatePolicy`\nas a login mechanism), see the `authentication` skill.\n\n### LocalAuthentication (Face ID / Touch ID)\n\n```swift\nimport LocalAuthentication\n\nfunc authenticateWithBiometrics() async throws -> Bool {\n    let context = LAContext()\n    var error: NSError?\n\n    guard context.canEvaluatePolicy(\n        .deviceOwnerAuthenticationWithBiometrics, error: &error\n    ) else {\n        // Biometrics not available -- fall back to passcode\n        if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {\n            return try await context.evaluatePolicy(\n                .deviceOwnerAuthentication,\n                localizedReason: \"Authenticate to access your account\"\n            )\n        }\n        throw AuthError.biometricsUnavailable\n    }\n\n    return try await context.evaluatePolicy(\n        .deviceOwnerAuthenticationWithBiometrics,\n        localizedReason: \"Authenticate to access your account\"\n    )\n}\n```\n\n### Info.plist Requirement\n\nYou MUST include `NSFaceIDUsageDescription` in Info.plist:\n\n```xml\n<key>NSFaceIDUsageDescription</key>\n<string>Authenticate to access your secure data</string>\n```\n\nMissing this key causes a crash on Face ID devices.\n\n### LAContext Configuration\n\n```swift\nlet context = LAContext()\ncontext.localizedFallbackTitle = \"Use Passcode\"\ncontext.touchIDAuthenticationAllowableReuseDuration = 30\nlet currentState = context.evaluatedPolicyDomainState // Compare to detect enrollment changes\n```\n\n### Biometric + Keychain\n\nProtect keychain items with biometric access:\n\n```swift\nlet access = SecAccessControlCreateWithFlags(\n    nil,\n    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,\n    .biometryCurrentSet,\n    nil\n)!\n\nlet query: [String: Any] = [\n    kSecClass as String: kSecClassGenericPassword,\n    kSecAttrAccount as String: \"auth-token\",\n    kSecValueData as String: tokenData,\n    kSecAttrAccessControl as String: access,\n    kSecUseAuthenticationContext as String: LAContext()\n]\n```\n\nSecAccessControl flags:\n- `.biometryCurrentSet` -- Requires biometry, invalidated if enrollment changes. Most secure.\n- `.biometryAny` -- Requires biometry, survives enrollment changes.\n- `.userPresence` -- Biometry or passcode. Most flexible.\n\n## App Transport Security (ATS)\n\nATS enforces HTTPS by default. Do NOT disable it.\n\n### What ATS Requires\n\n- TLS 1.2 or later\n- Forward secrecy cipher suites\n- SHA-256 or better certificates\n- 2048-bit or greater RSA keys (or 256-bit ECC)\n\n### Exception Domains (Last Resort)\n\n```xml\n<!-- Only for legacy servers you cannot upgrade -->\n<key>NSAppTransportSecurity</key>\n<dict>\n    <key>NSExceptionDomains</key>\n    <dict>\n        <key>legacy-api.example.com</key>\n        <dict>\n            <key>NSExceptionAllowsInsecureHTTPLoads</key>\n            <true/>\n            <key>NSExceptionMinimumTLSVersion</key>\n            <string>TLSv1.2</string>\n        </dict>\n    </dict>\n</dict>\n```\n\nRules:\n- NEVER set `NSAllowsArbitraryLoads` to true. Apple will reject the app.\n- Exception domains require justification in App Review notes.\n- Use exception domains only for third-party servers you cannot control.\n\n## Certificate Pinning\n\nPin certificates for sensitive API connections to prevent MITM attacks.\n\n### URLSession Delegate Pinning\n\n```swift\nimport CryptoKit\n\nclass PinnedSessionDelegate: NSObject, URLSessionDelegate {\n    // SHA-256 hash of the certificate's Subject Public Key Info\n    private let pinnedHashes: Set<String> = [\n        \"base64EncodedSHA256HashOfSPKI==\"\n    ]\n\n    func urlSession(\n        _ session: URLSession,\n        didReceive challenge: URLAuthenticationChallenge\n    ) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n        guard let trust = challenge.protectionSpace.serverTrust,\n              let chain = SecTrustCopyCertificateChain(trust) as? [SecCertificate],\n              let certificate = chain.first else {\n            return (.cancelAuthenticationChallenge, nil)\n        }\n\n        guard let publicKey = SecCertificateCopyKey(certificate),\n              let publicKeyData = SecKeyCopyExternalRepresentation(\n                  publicKey, nil\n              ) as Data? else {\n            return (.cancelAuthenticationChallenge, nil)\n        }\n\n        let hash = SHA256.hash(data: publicKeyData)\n        let hashString = Data(hash).base64EncodedString()\n\n        if pinnedHashes.contains(hashString) {\n            return (.useCredential, URLCredential(trust: trust))\n        }\n\n        return (.cancelAuthenticationChallenge, nil)\n    }\n}\n```\n\nRules:\n- Pin the public key hash, not the certificate. Certificates rotate; public keys are more stable.\n- Always include at least one backup pin.\n- Have a rotation plan. If all pinned keys expire, the app cannot connect.\n- Consider a kill switch (remote config to disable pinning in emergency).\n\n## Secure Coding Patterns\n\n### Never Log Sensitive Data\n\n```swift\n// WRONG\nlogger.debug(\"User logged in with token: \\(token)\")\n\n// CORRECT\nlogger.debug(\"User logged in successfully\")\n```\n\n### Clear Sensitive Data From Memory\n\n```swift\nvar sensitiveData = Data(/* ... */)\ndefer {\n    sensitiveData.resetBytes(in: 0..<sensitiveData.count)\n}\n```\n\n### Validate All Input\n\n```swift\nguard let url = URL(string: input),\n      [\"https\"].contains(url.scheme?.lowercased()) else {\n    throw SecurityError.invalidURL\n}\nlet resolved = url.standardized.path\nguard resolved.hasPrefix(allowedDirectory.path) else {\n    throw SecurityError.pathTraversal\n}\n```\n\n### API Key Placeholder Pattern\n\nUse `#error` to prevent accidental commits of placeholder API keys:\n\n```swift\n// Forces a build error until the real key is configured\n#error(\"Add your API key to Secrets.plist -- see README for setup\")\nprivate let apiKey = Secrets.value(for: \"API_KEY\")\n```\n\n### Jailbreak Detection\n\nCheck for known jailbreak file paths (`/Applications/Cydia.app`, `/usr/sbin/sshd`, etc.) and sandbox escape. Jailbreak detection is not foolproof -- use it as one layer, not the only layer. See [references/cryptokit-advanced.md](references/cryptokit-advanced.md) for full implementation.\n\n## Privacy Manifests\n\nApps and SDKs must declare data access in `PrivacyInfo.xcprivacy`. See\n[references/privacy-manifest.md](references/privacy-manifest.md) for required-reason API declarations and\nsecurity-related data collection details. For submission requirements and\ncompliance checklists, see [references/app-review-guidelines.md](references/app-review-guidelines.md).\n\n## Common Mistakes\n\n1. **Storing secrets in UserDefaults.** Tokens, passwords, API keys must go in Keychain.\n2. **Hardcoded secrets in source.** No API keys or credentials in Swift files.\n3. **Disabling ATS globally.** `NSAllowsArbitraryLoads = true` is a rejection risk.\n4. **Logging sensitive data.** Never log tokens, passwords, or API keys.\n5. **Missing PrivacyInfo.xcprivacy.** Required for all apps using required-reason APIs.\n6. **Using CommonCrypto instead of CryptoKit.** CryptoKit is safer and modern.\n7. **Missing NSFaceIDUsageDescription.** Crashes on Face ID devices.\n8. **Using `.biometryAny` when `.biometryCurrentSet` is needed.** The former survives enrollment changes, which may be undesirable for high-security items.\n9. **Path traversal vulnerabilities.** Always resolve and validate paths.\n10. **Missing concurrency annotations.** Ensure Keychain wrapper types are Sendable; isolate UI-facing security prompts to `@MainActor`.\n\n## Review Checklist\n\n- [ ] Secrets in Keychain, not UserDefaults or files; no hardcoded credentials\n- [ ] Correct `kSecAttrAccessible` value; `ThisDeviceOnly` for non-backup data\n- [ ] File protection class set for sensitive files (`.complete`)\n- [ ] CryptoKit for encryption (not CommonCrypto); 256-bit symmetric keys\n- [ ] Keys stored in Keychain or Secure Enclave\n- [ ] Biometric auth with fallback; `NSFaceIDUsageDescription` in Info.plist\n- [ ] Correct `SecAccessControl` flags; `LAContext` configured\n- [ ] HTTPS enforced; no `NSAllowsArbitraryLoads`; cert pinning for sensitive APIs\n- [ ] PrivacyInfo.xcprivacy present; all required-reason APIs declared\n- [ ] No sensitive data in logs; Data cleared after use; URLs/paths validated\n\n## References\n\n- CryptoKit advanced patterns: [references/cryptokit-advanced.md](references/cryptokit-advanced.md)\n- Privacy manifest guide: [references/privacy-manifest.md](references/privacy-manifest.md)\n- App Review security guidelines: [references/app-review-guidelines.md](references/app-review-guidelines.md)\n- File storage directory guide and protection: [references/file-storage-patterns.md](references/file-storage-patterns.md)","tags":["ios","security","swift","skills","dpearson2699"],"capabilities":["skill","source-dpearson2699","category-swift-ios-skills"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/ios-security","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"install_from":"skills.sh"}},"qualityScore":"0.300","qualityRationale":"deterministic score 0.30 from registry signals: · indexed on skills.sh · published under dpearson2699/swift-ios-skills","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill:v1","enrichmentVersion":1,"enrichedAt":"2026-05-07T22:40:36.372Z","embedding":null,"createdAt":"2026-04-18T20:33:02.713Z","updatedAt":"2026-05-07T22:40:36.372Z","lastSeenAt":"2026-05-07T22:40:36.372Z","tsv":"'-256':1039,1118 '/applications/cydia.app':1357 '/usr/sbin/sshd':1358 '0':694,1278 '02x':693 '1':1421 '1.2':1031 '10':1529 '2':1434 '2048':1043 '256':1050,1581 '3':1447 '30':940 '4':1457 '5':1468 '6':1480 '7':1491 '8':1499 '9':1520 'accept':598 'access':339,397,459,824,888,901,916,956,959,986,1391 'access-control':458 'accesscontrol':770,781,782 'accident':1314 'account':122,140,174,215,232,274,290,509,890,903 'across':402 'activ':539 'add':1332 'advanc':1634 'ae':625 'aes-gcm':624 'aes.gcm.open':677 'aes.gcm.seal':647 'aes.gcm.sealedbox':672 'afterfirstunlock':370 'agreement':733 'alloweddirectory.path':1302 'also':696 'alway':551,1213,1524 'annot':1532 'anyobject':247 'api':107,452,1101,1306,1318,1334,1347,1401,1428,1440,1466,1479,1612,1619 'apikey':1344 'app':52,57,403,548,601,1014,1074,1080,1230,1385,1474,1643 'app-transport-security-at':56 'appl':22,1070 'appstorag':427,434,480,486,489,501 'async':853,1140 'at':55,60,1017,1018,1028,1449 'atpath':576 'attack':1106 'attribut':573,580 'auth':977,1593 'auth-token':976 'authent':16,48,51,702,724,813,841,886,899,914 'authenticatewithbiometr':852 'authenticationcod':713 'autherror.biometricsunavailable':892 'authtoken':502 'avail':314,524,697,870 'await':882,895 'back':872 'background':338,346,375 'background-access':337 'backup':349,1218,1566 'backup/restore':365 'base':517 'base64encodedsha256hashofspki':1132 'base64encodedstring':1185 'best':25 'better':1041 'biometr':47,50,791,812,817,829,868,949,955,1592 'biometri':995,1004,1009 'biometric-authent':49 'biometryani':1002,1501 'biometrycurrentset':775,963,993,1503 'bit':1044,1051,1582 'bits256':634,708 'bool':855 'box':670,678 'build':1323 'cancelauthenticationchalleng':1158,1174,1195 'cannot':1093,1231 'category-swift-ios-skills' 'caus':923 'cert':1608 'certif':61,64,1042,1095,1098,1122,1154,1164,1205,1206 'certificate-pin':63 'cfdictionari':158,192,195,253,300 'chachapoli':735 'chain':1148 'chain.first':1155 'challeng':1138 'challenge.protectionspace.servertrust':1146 'chang':948,999,1007,1510 'check':569,1351 'checklist':84,87,1415,1548 'cipher':1036 'class':521,522,1113,1570 'clear':1266,1627 'code':67,71,621,1245 'collect':1408 'combin':653,659,673 'commit':1315 'common':78,81,1419 'common-mistak':80 'commoncrypto':613,1482,1580 'compar':944 'complet':527,585,1575 'completefileprotect':568 'completeunlessopen':534 'completeuntilfirstuserauthent':542,596 'complianc':1414 'concurr':1531 'config':1238 'configur':931,1330,1603 'connect':1102,1232 'consid':1233 'contain':589,1291 'content':29 'context':857,934 'context.canevaluatepolicy':863,876 'context.evaluatedpolicydomainstate':943 'context.evaluatepolicy':883,896 'context.localizedfallbacktitle':936 'context.touchidauthenticationallowablereuseduration':939 'control':460,1094 'core':115 'correct':19,97,506,1260,1559,1599 'cover':816 'crash':925,1494 'credenti':118,211,270,325,331,496,1443,1558 'cryptoerror.sealfailed':657 'cryptograph':608 'cryptokit':40,41,603,605,629,1112,1485,1486,1576,1633 'currentst':942 'curve25519':734 'danger':469 'data':15,35,38,102,116,124,125,148,186,220,259,262,268,363,382,464,510,512,533,549,558,593,602,637,638,643,648,662,663,668,674,686,687,715,725,788,798,823,919,1171,1179,1183,1250,1268,1274,1390,1407,1460,1567,1623,1626 'data-protect':37 'data.write':564 'datarepresent':801,810 'declar':1389,1402,1620 'decrypt':661 'default':546,595,1022 'defer':1275 'deleg':1108 'delet':269,385 'deletefromkeychain':273 'deprec':393 'deriv':739 'detail':1409 'detect':946,1350,1364 'devic':316,321,327,368,929,1498 'device-on':315 'deviceownerauthent':877,884 'deviceownerauthenticationwithbiometr':864,897 'didrec':1137 'digit':729 'directori':1651 'disabl':1025,1240,1448 'domain':1054,1076,1085 'download':540 'dpearson2699':8 'ecc':1052 'els':199,203,263,306,477,655,767,867,1156,1172,1294,1303 'embarrass':467 'emerg':1243 'enclav':43,46,744,754,1591 'encrypt':18,446,457,515,623,636,1578 'enforc':1019,1605 'enrol':947,998,1006,1509 'ensur':1533 'error':860,865,866,878,879,1311,1324,1331 'errsecduplicateitem':162 'errsecitemnotfound':305 'errsecsuccess':198,206,257,303 'escap':1362 'etc':1359 'everyth':476 'except':1053,1075,1084 'expir':1228 'expos':471 'face':828,844,927,1496,1542 'fall':871 'fallback':1595 'fals':494 'featur':443 'file':113,516,561,588,1355,1446,1555,1568,1574,1649 'filemanager.default.attributesofitem':575 'fileprotectiontyp':583 'fine':488 'first':334,343,544 'flag':444,992,1601 'flexibl':1013 'flow':833 'follow':21 'foolproof':1367 'forc':1321 'format':692 'former':1507 'forward':1034 'framework':618 'full':1381 'func':120,213,272,635,660,851,1133 'gcm':626 'general':324,600 'global':1450 'go':1431 'goe':473 'greater':1046 'group':398 'guard':196,255,301,651,765,862,1143,1160,1284,1300 'guid':1640,1652 'guidanc':11 'guidelin':1646 'handl':13,536 'hardcod':1435,1557 'hardwar':456,759 'hardware-encrypt':455 'hascompletedonboard':490 'hash':681,684,1119,1177,1184,1202 'hash.compactmap':690 'hashstr':689,1182,1188 'hasonboard':493 'high':1517 'high-secur':1516 'highest':355,747 'hkdf':737 'hmac':700,712,721 'https':1020,1290,1604 'id':845,847,928,1497 'implement':1382 'import':628,849,1111 'includ':908,1214 'info':1127 'info.plist':904,911,1598 'input':1282,1289 'insecur':395 'instead':1483 'invalid':996 'io':1,4,9,28,514 'isol':1539 'isvalid':720,794 'isvalidauthenticationcod':722 'item':383,401,821,953,1519 'jailbreak':1349,1354,1363 'join':695 'justif':1078 'key':108,453,631,640,650,665,680,705,717,727,732,738,750,755,922,1048,1126,1201,1209,1227,1307,1319,1328,1335,1348,1429,1441,1467,1584,1585 'keychain':30,33,90,93,396,400,429,449,475,499,803,820,950,952,1433,1534,1551,1588 'keychain-servic':32 'keychainerror.deletefailed':308 'keychainerror.readfailed':265 'keychainerror.savefailed':208 'keychainerror.updatefailed':201 'kill':1235 'known':1353 'ksecattraccess':149,310,1560 'ksecattraccesscontrol':983 'ksecattraccessgroup':423 'ksecattraccessibleafterfirstunlock':332 'ksecattraccessibleafterfirstunlockthisdeviceon':152,341 'ksecattraccessiblealway':392 'ksecattraccessiblewhenpasscodesetthisdeviceon':350,773,962 'ksecattraccessiblewhenunlock':320 'ksecattraccessiblewhenunlockedthisdeviceon':326 'ksecattraccount':137,171,229,287,417,973 'ksecattrservic':141,175,233,291 'ksecclass':133,167,225,283,413,969 'ksecclassgenericpassword':136,170,228,286,416,972 'ksecmatchlimit':241 'ksecmatchlimiton':244 'ksecreturndata':237 'ksecuseauthenticationcontext':987 'ksecvaluedata':145,183,979 'lacontext':858,930,935,990,1602 'lacontext.evaluatepolicy':834 'last':1055 'later':1033 'layer':1372,1376 'least':1216 'leav':757 'legacy-api.example.com':1060 'let':129,153,163,179,187,221,248,258,279,295,409,572,578,630,644,652,669,683,688,704,710,719,769,777,783,793,806,856,933,941,958,965,1129,1144,1147,1153,1161,1165,1176,1181,1285,1297,1343 'level':571 'localauthent':843,850 'localizedreason':885,898 'lock':538 'log':1248,1255,1263,1458,1462,1625 'logger.debug':1253,1261 'login':837 'lowercas':1293 'mainactor':1546 'manifest':74,77,1384,1639 'may':789,1512 'mechan':838 'memori':1270 'messag':701 'miss':920,1469,1492,1530 'mistak':79,82,1420 'mitm':1105 'modern':1490 'must':497,907,1388,1430 'need':373,557,1505 'never':103,390,756,1065,1247,1461 'new':620 'nil':159,772,776,961,964,1159,1169,1175,1196 'non':437,483,553,1565 'non-backup':1564 'non-sensit':436,482,552 'none':550 'note':1082 'nsallowsarbitraryload':1067,1451,1607 'nsapptransportsecur':1058 'nserror':861 'nsexceptionallowsinsecurehttpload':1061 'nsexceptiondomain':1059 'nsexceptionminimumtlsvers':1062 'nsfaceidusagedescript':909,913,1493,1596 'nsobject':1115 'onboard':441 'one':1217,1371 'open':535 'oper':376,609 'option':567 'p256':761 'p256/ecdsa':731 'parti':1090 'passcod':351,387,874,938,1011 'password':105,450,1427,1464 'path':577,1356,1521,1528 'pattern':68,72,1246,1309,1635 'persist':799 'pin':62,65,1096,1097,1109,1198,1219,1226,1241,1609 'pinnedhash':1130 'pinnedhashes.contains':1187 'pinnedsessiondeleg':1114 'place':98 'placehold':1308,1317 'plan':1223 'practic':26 'prefer':439,485 'present':1614 'prevent':364,1104,1313 'privaci':73,76,1383,1638 'privacy-manifest':75 'privacyinfo.xcprivacy':1393,1470,1613 'privat':491,503,1128,1342 'privatekey':778 'privatekey.datarepresentation':811 'privatekey.publickey.isvalidsignature':795 'privatekey.signature':786 'privatekeyusag':774 'prompt':792,1544 'protect':36,39,513,520,562,570,579,818,951,1569,1654 'protectionkey':581 'public':1125,1200,1208 'publickey':1162,1168 'publickeydata':1166,1180 'queri':130,156,222,251,280,298,410,966 'raw':616 'read':210 'readfromkeychain':214 'readm':1339 'real':1327 'reason':1400,1478,1618 'record':541 'refer':88,89,1632 'references/app-review-guidelines.md':1417,1418,1647,1648 'references/cryptokit-advanced.md':741,742,1378,1379,1636,1637 'references/file-storage-patterns.md':1655,1656 'references/privacy-manifest.md':1395,1396,1641,1642 'reject':1072,1455 'relat':1406 'remot':1237 'remov':389 'requir':905,994,1003,1029,1077,1399,1412,1471,1477,1617 'required-reason':1398,1476,1616 'resolv':1298,1525 'resolved.hasprefix':1301 'resort':1056 'rest':448 'restor':804,807 'result':246,254,260 'return':267,658,675,768,880,893,1157,1173,1189,1194 'review':83,86,1081,1547,1644 'review-checklist':85 'risk':1456 'rotat':1207,1222 'rsa':1047 'rule':357,461,1064,1197 'safer':1488 'sandbox':1361 'savetokeychain':121,508 'sdks':1387 'seal':645 'sealed.combined':654 'secaccesscontrol':991,1600 'secaccesscontrolcreatewithflag':771,960 'seccertif':1152 'seccertificatecopykey':1163 'secitemadd':155 'secitemcopymatch':250 'secitemdelet':297 'secitemupd':189 'seckeycopyexternalrepresent':1167 'secreci':1035 'secret':110,454,1423,1436,1549 'secrets.plist':1337 'secrets.value':1345 'section':815 'sectrustcopycertificatechain':1149 'secur':2,10,24,42,45,54,59,66,70,356,433,617,743,748,753,918,1001,1016,1244,1405,1518,1543,1590,1645 'secure-coding-pattern':69 'secure-enclav':44 'secureenclave.isavailable':766 'secureenclave.p256.signing.privatekey':780,809 'security-rel':1404 'securityerror.invalidurl':1296 'securityerror.pathtraversal':1305 'see':740,839,1338,1377,1394,1416 'sendabl':1538 'sensit':14,101,330,362,381,438,484,495,531,554,592,1100,1249,1267,1459,1573,1611,1622 'sensitivedata':1273 'sensitivedata.count':1279 'sensitivedata.resetbytes':1276 'server':1091 'servic':31,34,91,126,144,178,217,236,276,294,511 'session':1135 'set':352,560,1066,1131,1571 'setup':1341 'sha':1038,1117 'sha256.hash':685,1178 'sha384':698 'sha512':699 'share':399,421 'shared-token':420 'sign':709,831 'sign-in':830 'signatur':711,723,730,784,796 'size':633,707 'skill':5,6,842 'sourc':1438 'source-dpearson2699' 'stabl':1212 'state':442 'status':154,161,205,209,249,256,266,296,302,304,309 'storag':430,1650 'store':100,104,117,749,800,1422,1586 'string':123,127,131,135,139,143,147,151,165,169,173,177,181,185,216,218,223,227,231,235,239,243,275,277,281,285,289,293,411,415,419,425,691,967,971,975,981,985,989,1288 'subject':1124 'submiss':1411 'success':1265 'suit':1037 'support':763 'surviv':537,1005,1508 'swift':3,119,212,271,408,481,559,627,682,703,764,848,932,957,1110,1251,1271,1283,1320,1445 'switch':1236 'symmetr':622,1583 'symmetrickey':632,641,666,706 'system':556 'system-need':555 'team':407 'teamid.com.company.shared':426 'theme':440 'third':1089 'third-parti':1088 'thisdeviceon':359,1562 'throw':128,200,207,219,264,278,307,642,656,667,854,891,1295,1304 'tls':1030 'tlsv1.2':1063 'token':106,340,347,372,422,451,505,978,1258,1259,1426,1463 'tokendata':982 'touch':846 'transport':53,58,1015 'travers':1522 'tri':563,574,646,671,676,779,785,808,881,894 'trigger':790 'true':240,1069,1452 'trust':1145,1150,1192,1193 'type':1536 'ui':1541 'ui-fac':1540 'undesir':1514 'unlock':322,328,335,344,353,530,545 'updat':180,193 'updatequeri':164,190 'updatestatus':188,197,202 'url':566,1286,1287 'url.scheme':1292 'url.standardized.path':1299 'urlauthenticationchalleng':1139 'urlcredenti':1142,1191 'urls/paths':1630 'urlsess':1107,1134,1136 'urlsession.authchallengedisposition':1141 'urlsessiondeleg':1116 'use':318,358,369,377,391,431,479,498,507,525,584,604,612,639,649,664,679,716,726,937,1083,1310,1368,1475,1481,1500,1629 'usecredenti':1190 'user':17,532,591,827,1254,1262 'user-fac':826 'user-sensit':590 'userdefault':112,435,1425,1553 'userpres':1008 'valid':1280,1527,1631 'valu':311,312,1561 'var':245,492,504,859,1272 'variant':360 'verifi':718 'vs':428 'vulner':1523 'whenpasscodesetthisdeviceon':378 'would':465 'wrapper':1535 'wrong':500,1252 'xml':912,1057 'yes':329,345,354","prices":[{"id":"0b0861db-8caa-4787-aa49-564ae3db839c","listingId":"4fd0a945-f55d-450a-9ce2-6f44d792c7c1","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:33:02.713Z"}],"sources":[{"listingId":"4fd0a945-f55d-450a-9ce2-6f44d792c7c1","source":"github","sourceId":"dpearson2699/swift-ios-skills/ios-security","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/ios-security","isPrimary":false,"firstSeenAt":"2026-04-18T22:01:04.464Z","lastSeenAt":"2026-04-22T06:53:43.970Z"},{"listingId":"4fd0a945-f55d-450a-9ce2-6f44d792c7c1","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/ios-security","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/ios-security","isPrimary":true,"firstSeenAt":"2026-04-18T20:33:02.713Z","lastSeenAt":"2026-05-07T22:40:36.372Z"}],"details":{"listingId":"4fd0a945-f55d-450a-9ce2-6f44d792c7c1","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"ios-security","source":"skills_sh","category":"swift-ios-skills","skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/ios-security"},"updatedAt":"2026-05-07T22:40:36.372Z"}}