ios-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ios-security (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill makes you an expert iOS security engineer. Every piece of code you write must treat security as a first-class concern — not an afterthought bolted on later.
This skill covers 5 detailed reference files. Read the relevant one(s) based on what the user is building:
| User's task involves... | Read |
|---|---|
| Storing passwords, tokens, credentials | references/keychain.md |
| Face ID, Touch ID, biometric login | references/biometrics.md |
| Encryption, hashing, signing, Secure Enclave | references/cryptokit.md |
| Sign in with Apple, OAuth2, login flows | references/authentication.md |
| Privacy manifests, permissions, tracking, Info.plist | references/privacy.md |
If the user's task spans multiple areas (common — e.g., "add login with Face ID and store tokens"), read all relevant files.
These rules are non-negotiable. Violating them creates real vulnerabilities.
SecItemAdd/SecItemCopyMatching. UserDefaults is plaintext on disk and trivially readable on jailbroken devices.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly for background refresh support, or kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly for highest security (item deleted if passcode removed).errSecDuplicateItem by calling SecItemUpdate instead of failing — this is the idiomatic save-or-update pattern.KeychainManager wrapper with Codable generics so the rest of the app never touches Security framework directly. See references/keychain.md for the complete implementation.NSFaceIDUsageDescription to Info.plist when using Face ID. The app crashes without it — no warning, no graceful failure, just a crash.canEvaluatePolicy before calling evaluatePolicy. Handle every LAError case — especially .biometryNotEnrolled and .biometryLockout — with meaningful user-facing messages, not generic "auth failed".SecAccessControlCreateWithFlags with .biometryCurrentSet (invalidated when biometrics change — more secure) or .biometryAny (survives re-enrollment — more convenient). The choice depends on the threat model..deviceOwnerAuthentication (biometrics + passcode fallback) over .deviceOwnerAuthenticationWithBiometrics for critical flows — users must always have a way in.dataRepresentation in Keychain for persistence across app launches.SHA256 for hashing. SHA-1 is broken for security purposes — never use it for integrity checks.NSExceptionDomains — don't use NSAllowsArbitraryLoads which disables ATS globally.NSAllowsLocalNetworking for connecting to local dev servers.credential.user (the stable identifier) in Keychain. Check credential state on every app launch via getCredentialState.ASWebAuthenticationSession — not SFSafariViewController or WKWebView. Set prefersEphemeralWebBrowserSession = true to avoid sharing cookies with Safari.async/await for all auth flows — it's cleaner and easier to reason about than completion handlers.PrivacyInfo.xcprivacy for any app that uses Required Reason APIs (UserDefaults, file timestamps, disk space, system boot time, active keyboards). This is mandatory for App Store submission since May 2024..denied and .restricted states gracefully — guide the user to Settings if they previously denied a permission..active state. IDFA returns all zeros without authorization.STRIP_SWIFT_SYMBOLS = YES).references/keychain.md for the detection pattern.memset_s (not memset) to zero sensitive data — the compiler can optimize away plain memset calls.┌─────────────────────────────────────┐
│ UI Layer (SwiftUI) │
│ SignInWithAppleButton, permission │
│ dialogs, biometric prompts │
├─────────────────────────────────────┤
│ Auth Service Layer │
│ AuthManager, BiometricService, │
│ TokenManager │
├─────────────────────────────────────┤
│ Security Layer │
│ KeychainManager, CryptoService, │
│ SecurityAuditor │
├─────────────────────────────────────┤
│ Apple Frameworks │
│ Security, LocalAuthentication, │
│ CryptoKit, AuthenticationServices │
└─────────────────────────────────────┘The UI layer never touches Security framework directly. Auth services orchestrate flows. The security layer wraps Apple frameworks with clean Swift APIs. This separation makes code testable and prevents security logic from leaking into views.
Security/
├── Keychain/
│ ├── KeychainManager.swift // Generic Codable wrapper
│ └── KeychainError.swift // Typed errors
├── Biometrics/
│ ├── BiometricService.swift // LAContext wrapper
│ └── BiometricError.swift // User-friendly errors
├── Crypto/
│ ├── CryptoService.swift // AES-GCM, hashing
│ └── SecureEnclaveManager.swift // P256 Secure Enclave ops
├── Auth/
│ ├── AuthManager.swift // Orchestrates login flows
│ ├── AppleSignInHandler.swift // SIWA implementation
│ ├── OAuthHandler.swift // ASWebAuthenticationSession
│ └── TokenManager.swift // Token storage + refresh
├── Privacy/
│ ├── PermissionManager.swift // Unified permission requests
│ └── PrivacyInfo.xcprivacy // Privacy manifest
└── AppSecurity/
├── SecurityAuditor.swift // Jailbreak detection, integrity
└── SecureWipe.swift // memset_s wrappersSecurity errors should be specific internally but generic to the user:
// Internal — specific for debugging and logging
enum KeychainError: Error {
case duplicateItem
case itemNotFound
case unexpectedStatus(OSStatus)
case invalidData
case accessDenied
}
// User-facing — never expose internal security details
enum AuthError: LocalizedError {
case authenticationRequired
case biometricUnavailable
case sessionExpired
case networkError
var errorDescription: String? {
switch self {
case .authenticationRequired: "Please sign in to continue"
case .biometricUnavailable: "Biometric authentication is not available"
case .sessionExpired: "Your session has expired. Please sign in again"
case .networkError: "Unable to connect. Please check your connection"
}
}
}Never expose Keychain error codes, token values, or security implementation details in user-facing messages or logs.
Security code is hard to test but critical to get right:
kSecAttrService per test suite to isolate. Clean up in tearDown.BiometricAuthenticating protocol, implement with LAContext in production and a mock in tests.URLProtocol subclass to simulate certificate challenges in tests.| Scenario | Solution |
|---|---|
| Store user's auth token | Keychain, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly |
| Store highly sensitive key | Keychain + biometric protection via SecAccessControl |
| Generate encryption key that never leaves device | Secure Enclave (SecureEnclave.P256) |
| Encrypt local file | AES.GCM.seal with SymmetricKey(size: .bits256) |
| Hash password for comparison | SHA256.hash(data:) (but prefer server-side hashing with bcrypt/scrypt) |
| Verify data integrity | HMAC<SHA256> |
| Sign data for server verification | P256.Signing.PrivateKey + send publicKey to server |
| User login with Apple | ASAuthorizationAppleIDProvider + nonce + Keychain for user ID |
| User login with third-party OAuth | ASWebAuthenticationSession + Keychain for tokens |
| Protect app on locked device | NSFileProtectionComplete on sensitive files |
| Check if user upgraded from old iOS | getCredentialState for SIWA, re-validate Keychain items |
| Need HTTP for local dev server | NSAllowsLocalNetworking = true in ATS config |
| Banking/healthcare app network security | Certificate pinning via URLSessionDelegate |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.