General:
Forums topic: Privacy & Security
Apple Platform Security support document
Developer > Security
Enabling enhanced security for your app documentation article
Creating enhanced security helper extensions documentation article
Security Audit Thoughts forums post
Cryptography:
Forums tags: Security, Apple CryptoKit
Security framework documentation
Apple CryptoKit framework documentation
Common Crypto man pages — For the full list of pages, run:
% man -k 3cc
For more information about man pages, see Reading UNIX Manual Pages.
On Cryptographic Key Formats forums post
SecItem attributes for keys forums post
CryptoCompatibility sample code
Keychain:
Forums tags: Security
Security > Keychain Items documentation
TN3137 On Mac keychain APIs and implementations
SecItem Fundamentals forums post
SecItem Pitfalls and Best Practices forums post
Investigating hard-to-reproduce keychain problems forums post
App ID Prefix Change and Keychain Access forums post
Smart cards and other secure tokens:
Forums tag: CryptoTokenKit
CryptoTokenKit framework documentation
Mac-specific resources:
Forums tags: Security Foundation, Security Interface
Security Foundation framework documentation
Security Interface framework documentation
BSD Privilege Escalation on macOS
Related:
Networking Resources — This covers high-level network security, including HTTPS and TLS.
Network Extension Resources — This covers low-level network security, including VPN and content filters.
Code Signing Resources
Notarisation Resources
Trusted Execution Resources — This includes Gatekeeper.
App Sandbox Resources
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
General
RSS for tagPrioritize user privacy and data security in your app. Discuss best practices for data handling, user consent, and security measures to protect user information.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
General:
Forums topic: Privacy & Security
Privacy Resources
Security Resources
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Topic:
Privacy & Security
SubTopic:
General
iOS BLE Background Scanning Stops After Few Minutes to Hours
Hi everyone,
I'm developing a Flutter app using flutter_blue_plus that needs continuous BLE scanning in both foreground and background. Foreground scanning works perfectly, but background scanning stops after a few minutes (sometimes 1-2 hours).
Current Implementation (iOS)
Foreground Mode:
Scans for 10 seconds with all target service UUIDs
Batches and submits results every 10 seconds
Works reliably ✅
Background Mode:
Rotates through service UUIDs in batches of 7
Uses 1-minute batch intervals
Maintains active location streaming (Geolocator.getPositionStream)
Switches modes via AppLifecycleState observer
// Background scanning setup
await FlutterBluePlus.startScan(
withServices: serviceUuids, // Batch of 7 UUIDs
continuousUpdates: true,
);
// Location streaming (attempt to keep app alive)
LocationSettings(
accuracy: LocationAccuracy.bestForNavigation,
distanceFilter: 0,
);
Lifecycle Management:
AppLifecycleState.paused -> Start background mode
AppLifecycleState.resumed -> Start foreground mode
Questions:
Is there a documented maximum duration for iOS background BLE scanning? My scanning stops inconsistently (few minutes to 2 hours).
Does iOS require specific Background Modes beyond location updates to maintain BLE scanning? I have location streaming active but scanning still stops.
Are there undocumented limitations when scanning with service UUIDs in background that might cause termination?
Should I be using CoreBluetooth's state preservation/restoration instead of continuous scanning?
Info.plist Configuration:
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>location</string>
</array>
Additional Context:
Total service UUIDs: ~20-50 (varies by company)
Scanning in batches of 7 to work around potential limitations
Android version works fine with foreground service
Location permission: Always
iOS 14+ target
Any insights on iOS BLE background limitations or best practices would be greatly appreciated.
Thanks!
Topic:
Privacy & Security
SubTopic:
General
Tags:
IOBluetooth
External Accessory
Playground Bluetooth
Core Bluetooth
We are using SecItemCopyMatching from LocalAuthentication to access the private key to sign a challenge in our native iOS app twice in a few seconds from user interactions.
This was working as expected up until about a week ago where we started getting reports of it hanging on the biometrics screen (see screenshot below).
From our investigation we've found the following:
It impacts newer iPhones using iOS 26.1 and later. We have replicated on these devices:
iPhone 17 Pro max
iPhone 16 Pro
iPhone 15 Pro max
iPhone 15
Only reproducible if the app tries to access the private key twice in quick succession after granting access to face ID.
Looks like a race condition between the biometrics permission prompt and Keychain private key access
We were able to make it work by waiting 10 seconds between private key actions, but this is terrible UX.
We tried adding adding retries over the span of 10 seconds which fixed it on some devices, but not all.
We checked the release notes for iOS 26.1, but there is nothing related to this.
Screenshot:
Topic:
Privacy & Security
SubTopic:
General
Tags:
Face ID
Entitlements
Touch ID
Local Authentication
Hi, I’m seeing a production issue on iOS 26+ that only affects some users.
symptoms:
It does NOT happen for all users.
It happens for a subset of users on iOS 26+.
If we write a value to Keychain and read it immediately in the same session, it succeeds.
However, after terminating the app and relaunching, the value appears to be gone:
SecItemCopyMatching returns errSecItemNotFound (-25300).
Repro (as observed on affected devices):
Launch app (iOS 26+).
Save PIN data to Keychain using SecItemAdd (GenericPassword).
Immediately read it using SecItemCopyMatching -> success.
Terminate the app (swipe up / kill).
Relaunch the app and read again using the same service -> returns -25300.
Expected:
The Keychain item should persist across app relaunch and remain readable (while the device is unlocked).
Actual:
After app relaunch, SecItemCopyMatching returns errSecItemNotFound (-25300) as if the item does not exist.
Implementation details (ObjC):
We store a “PIN” item like this (simplified):
addItem:
kSecClass: kSecClassGenericPassword
kSecAttrService: <FIXED_STRING>
kSecValueData:
kSecAttrAccessControl: SecAccessControlCreateWithFlags(..., kSecAttrAccessibleWhenUnlockedThisDeviceOnly, 0, ...)
readItem (SecItemCopyMatching):
kSecClass: kSecClassGenericPassword
kSecAttrService: <FIXED_STRING>
kSecReturnData: YES
(uses kSecUseOperationPrompt in our async method)
Question:
On iOS 26+, is there any known issue or new behavior where a successfully added GenericPassword item could later return errSecItemNotFound after app termination/relaunch for only some users/devices?
What should we check to distinguish:
OS behavior change/bug vs.
entitlement/access-group differences (app vs extension, provisioning/team changes),
device state/policies (MDM, passcode/biometrics changes),
query attributes we should include to make the item stable across relaunch?
Build / Dev Environment:
macOS: 15.6.1 (24G90)
Xcode: 26.2
Our Goal: We are implementing a workflow for derived credentials. Our objective is to have a PIV/CAC derived credential (from Entrust), installed via the Intune MDM Company Portal app, and then use it within our (managed) app to generate digital signatures.
Challenge: The Intune Company Portal installs these identities into the System Keychain. Because third-party apps are restricted from accessing private keys in the System Keychain, we are running into a roadblock.
Our Question: 1) Is there an API that allows us to create a signature without us having to pass the private key itself, but instead just pass a handle/some reference to the private key and then the API can access the private key in the system keychain and create the signature under the hood. SecKeyCreateSignature is the API method that creates a signature but requires passing a private key. 2) If #1 is not feasible, is there a way to get access to system keychain to retrieve certs + private key for managed apps
[Q] When is the kTCCServiceEndpointSecurityClient set by macOS and in which conditions?
From what I'm gathering, the kTCCServiceEndpointSecurityClient can not be set by a configuration profile and the end user can only grant full disk access.
I searched for documentation on Apple's develop website (with the "kTCCServiceEndpointSecurityClient" search) and did not get any useful result.
Using a more complete search engine, or the forum search engine, only points to the old annoying big bug in macOS Ventura.
The problem I'm investigating is showing a process being listed as getting granted kTCCServiceEndpointSecurityClient permissions in the TCC database when:
it's not an Endpoint Security client.
it does not have the ES Client entitlement.
the bundle of the process includes another process that is an ES Client and is spawn-ed by this process but I don't see why this should have an impact.
This process is supposed to have been granted kTCCServiceSystemPolicyAllFiles via end user interaction or configuration profile.
AFAIK, the kTCCServiceEndpointSecurityClient permission can only be set by macOS itself.
So this looks like to be either a bug in macOS, an undocumented behavior or I'm missing something. Hence the initial question.
macOS 15.7.3 / Apple Silicon
Critical Privacy and Security Issue: Spotlight disregards explicit exclusions and exposes user files
Apple has repeatedly ignored my reports about a critical privacy issue in Spotlight on macOS 26, and the problem persists in version 26.3 RC. This is not a minor glitch, it is a fundamental breach of user trust and privacy.
Several aspects of Spotlight fail to respect user settings:
• Hidden apps still exposed: In the Apps section (Cmd+1), Spotlight continues to display apps marked with the hidden flag, even though they should remain invisible.
• Clipboard reactivation: The clipboard feature repeatedly turns itself back on after logout or restart, despite being explicitly disabled by the user.
• Excluded files revealed: Most concerning, Spotlight exposes files in Suggestions and Recents (Cmd+3) even when those files are explicitly excluded under System Settings > Spotlight > Search Privacy.
This behavior directly violates user expectations and system settings. It is not only a major privacy issue but also a security risk, since sensitive files can be surfaced without consent.
Apple must address this immediately. Users rely on Spotlight to respect their privacy configurations, and the current behavior undermines both trust and security.
I have filed bug reports on this to no avail, so I am bringing it up here hoping someone at Apple will address this. Since the first beta of 26.3, with voice control enabled there are now two icons in the menu bar (*plus an orange dot in full screen) that never go away. That orange microphone isn't serving its intended purpose to notify me that something is accessing my microphone if it is always displayed. I use voice control extensively, so it is nearly always on. In every prior version of macOS, the orange icon was not on for voice control. Even if voice control is not listening but simply enabled in system settings, the orange icon will be there. And there is no need for this icon to be on for a system service that is always listening. This orange icon in the menu bar at all times is incredibly irritating, as it takes up valuable space to the right of the notch, and causes other actual useful menu bar items to be hidden. As well, if some other application on my system were to turn on the mic and start recording me I would never know since that orange icon is always on. It also places an orange dot next to the control center icon taking up even more of the precious little menu bar real estate. Please fix this! Either exempt voice control (as Siri is always listening and it doesn't get the orange icon) or exempt all system services, or give me a way to turn this off. If you cannot tell, I find this incredibly annoying and frustrating.
Topic:
Privacy & Security
SubTopic:
General
Feedback ticket ID: FB21797397
Summary
When using posix_spawn() with posix_spawnattr_set_uid_np() to spawn a child process with a different UID, the eslogger incorrectly reports a setuid event as an event originating from the parent process instead of the child process.
Steps to Reproduce
Create a binary that do the following:
Configure posix_spawnattr_t that set the process UIDs to some other user ID (I'll use 501 in this example).
Uses posix_spawn() to spawn a child process
Run eslogger with the event types setuid, fork, exec
Execute the binary as root process using sudo or from root owned shell
Terminate the launched eslogger
Observe the process field in the setuid event
Expected behavior
The eslogger will report events indicating a process launch and uid changes so the child process is set to 501. i.e.:
fork
setuid - Done by child process
exec
Actual behavior
The process field in the setuid event is reported as the parent process (that called posix_spawn) - indicating UID change to the parent process.
Attachments
I'm attaching source code for a small project with a 2 binaries:
I'll add the source code for the project at the end of the file + attach filtered eslogger JSONs
One that runs the descirbed posix_spawn flow
One that produces the exact same sequence of events by doing different operation and reaching a different process state:
Parent calls fork()
Parent process calls setuid(501)
Child process calls exec()
Why this is problematic
Both binaries in my attachment do different operations, achieving different process state (1 is parent with UID=0 and child with UID=501 while the other is parent UID=501 and child UID=0), but report the same sequence of events.
Code
#include <cstdio>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
// environ contains the current environment variables
extern char **environ;
extern "C" {
int posix_spawnattr_set_uid_np(posix_spawnattr_t *attr, uid_t uid);
int posix_spawnattr_set_gid_np(posix_spawnattr_t *attr, gid_t gid);
}
int main() {
pid_t pid;
int status;
posix_spawnattr_t attr;
// 1. Define the executable path and arguments
const char *path = "/bin/sleep";
char *const argv[] = {(char *)"sleep", (char *)"1", NULL};
// 2. Initialize spawn attributes
if ((status = posix_spawnattr_init(&attr)) != 0) {
fprintf(stderr, "posix_spawnattr_init: %s\n", strerror(status));
return EXIT_FAILURE;
}
// 3. Set the UID for the child process (e.g., UID 501)
// Note: Parent must be root to change to a different user
uid_t target_uid = 501;
if ((status = posix_spawnattr_set_uid_np(&attr, target_uid)) != 0) {
fprintf(stderr, "posix_spawnattr_set_uid_np: %s\n", strerror(status));
posix_spawnattr_destroy(&attr);
return EXIT_FAILURE;
}
// 4. Spawn the process
printf("Spawning /bin/sleep 1 as UID %d...\n", target_uid);
status = posix_spawn(&pid, path, NULL, &attr, argv, environ);
if (status == 0) {
printf("Successfully spawned child with PID: %d\n", pid);
// Wait for the child to finish (will take 63 seconds)
if (waitpid(pid, &status, 0) != -1) {
printf("Child process exited with status %d\n", WEXITSTATUS(status));
} else {
perror("waitpid");
}
} else {
fprintf(stderr, "posix_spawn: %s\n", strerror(status));
}
// 5. Clean up
posix_spawnattr_destroy(&attr);
return (status == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
// This program demonstrates fork + setuid + exec behavior for ES framework bug report
// 1. Parent forks
// 2. Parent does setuid(501)
// 3. Child waits with sleep syscall
// 4. Child performs exec
int main() {
printf("Parent PID: %d, UID: %d, EUID: %d\n", getpid(), getuid(), geteuid());
pid_t pid = fork();
if (pid < 0) {
// Fork failed
perror("fork");
return EXIT_FAILURE;
}
if (pid == 0) {
// Child process
printf("Child PID: %d, UID: %d, EUID: %d\n", getpid(), getuid(), geteuid());
// Child waits for a bit with sleep syscall
printf("Child sleeping for 2 seconds...\n");
sleep(2);
// Child performs exec
printf("Child executing child_exec...\n");
// Get the path to child_exec (same directory as this executable)
char *const argv[] = {(char *)"/bin/sleep", (char *)"2", NULL};
// Try to exec child_exec from current directory first
execv("/bin/sleep", argv);
// If exec fails
perror("execv");
return EXIT_FAILURE;
} else {
// Parent process
printf("Parent forked child with PID: %d\n", pid);
// Parent does setuid(501)
printf("Parent calling setuid(501)...\n");
if (setuid(501) != 0) {
perror("setuid");
// Continue anyway to observe behavior
}
printf("Parent after setuid - UID: %d, EUID: %d\n", getuid(), geteuid());
// Wait for child to finish
int status;
if (waitpid(pid, &status, 0) != -1) {
if (WIFEXITED(status)) {
printf("Child exited with status %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child killed by signal %d\n", WTERMSIG(status));
}
} else {
perror("waitpid");
}
}
return EXIT_SUCCESS;
}
posix_spawn.json
fork_exec.json
% curl -v https://app-site-association.cdn-apple.com/a/v1/zfcs.bankts.cn
Host app-site-association.cdn-apple.com:443 was resolved.
IPv6: (none)
IPv4: 218.92.226.151, 119.101.148.193, 218.92.226.6, 115.152.217.3
Trying 218.92.226.151:443...
Connected to app-site-association.cdn-apple.com (218.92.226.151) port 443
ALPN: curl offers h2,http/1.1
(304) (OUT), TLS handshake, Client hello (1):
CAfile: /etc/ssl/cert.pem
CApath: none
(304) (IN), TLS handshake, Server hello (2):
(304) (IN), TLS handshake, Unknown (8):
(304) (IN), TLS handshake, Certificate (11):
(304) (IN), TLS handshake, CERT verify (15):
(304) (IN), TLS handshake, Finished (20):
(304) (OUT), TLS handshake, Finished (20):
SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
ALPN: server accepted http/1.1
Server certificate:
subject: C=US; ST=California; O=Apple Inc.; CN=app-site-association.cdn-apple.com
start date: Sep 25 13:58:08 2025 GMT
expire date: Mar 31 17:44:25 2026 GMT
subjectAltName: host "app-site-association.cdn-apple.com" matched cert's "app-site-association.cdn-apple.com"
issuer: CN=Apple Public Server RSA CA 11 - G1; O=Apple Inc.; ST=California; C=US
SSL certificate verify ok.
using HTTP/1.x
GET /a/v1/zfcs.bankts.cn HTTP/1.1
Host: app-site-association.cdn-apple.com
User-Agent: curl/8.7.1
Accept: /
Request completely sent off
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=utf-8
< Content-Length: 10
< Connection: keep-alive
< Server: nginx
< Date: Wed, 04 Feb 2026 02:26:00 GMT
< Expires: Wed, 04 Feb 2026 02:26:10 GMT
< Age: 24
< Apple-Failure-Details: {"cause":"context deadline exceeded (Client.Timeout exceeded while awaiting headers)"}
< Apple-Failure-Reason: SWCERR00301 Timeout
< Apple-From: https://zfcs.bankts.cn/.well-known/apple-app-site-association
< Apple-Try-Direct: true
< Vary: Accept-Encoding
< Via: https/1.1 jptyo12-3p-pst-003.ts.apple.com (acdn/3.16363), http/1.1 jptyo12-3p-pac-043.ts.apple.com (acdn/3.16363), https/1.1 jptyo12-3p-pfe-002.ts.apple.com (acdn/3.16363)
< X-Cache: MISS KS-CLOUD
< CDNUUID: 736dc646-57fb-43c9-aa0d-eedad3a534f8-1154605242
< x-link-via: yancmp83:443;xmmp02:443;fzct321:443;
< x-b2f-cs-cache: no-cache
< X-Cache-Status: MISS from KS-CLOUD-FZ-CT-321-35
< X-Cache-Status: MISS from KS-CLOUD-XM-MP-02-16
< X-Cache-Status: MISS from KS-CLOUD-YANC-MP-83-15
< X-KSC-Request-ID: c4a640c815640ee93c263a357ee919d6
< CDN-Server: KSFTF
< X-Cdn-Request-ID: c4a640c815640ee93c263a357ee919d6
<
Not Found
Connection #0 to host app-site-association.cdn-apple.com left intact
I'm developing a passkey manager using ASCredentialProviderViewController. I've set a custom AAGUID in the attestation object during registration:
let aaguid = Data([
0xec, 0x78, 0xfa, 0xe8, 0xb2, 0xe0, 0x56, 0x97,
0x8e, 0x94, 0x7c, 0x77, 0x28, 0xc3, 0x95, 0x00
])
However, when I test on webauthn.io, the relying party receives:
AAGUID: 00000000-0000-0000-0000-000000000000
Provider Name: "iCloud Keychain"
It appears that macOS overwrites the AAGUID to all zeros for third-party Credential Provider Extensions.
This makes it impossible for relying parties to distinguish between different passkey providers, which is one of the key purposes of AAGUID in the WebAuthn specification.
Is this expected behavior? Is there a way for third-party Credential Provider Extensions to use their own registered AAGUID?
Environment:
macOS 26.2
Xcode 26.2
Topic:
Privacy & Security
SubTopic:
General
Tags:
Extensions
macOS
Authentication Services
Passkeys in iCloud Keychain
(Xcode 26.2, iPhone 17 Pro)
I can't seem to get hardware tag checks to work in an app launched without the special "Hardware Memory Tagging" diagnostics. In other words, I have been unable to reproduce the crash example at 6:40 in Apple's video "Secure your app with Memory Integrity Enforcement".
When I write a heap overflow or a UAF, it is picked up perfectly provided I enable the "Hardware Memory Tagging" feature under Scheme Diagnostics.
If I instead add the Enhanced Security capability with the memory-tagging related entitlements:
I'm seeing distinct memory tags being assigned in pointers returned by malloc (without the capability, this is not the case)
Tag mismatches are not being caught or enforced, regardless of soft mode
The behaviour is the same whether I launch from Xcode without "Hardware Memory Tagging", or if I launch the app by tapping it on launchpad. In case it was related to debug builds, I also tried creating an ad hoc IPA and it didn't make any difference.
I realise there's a wrinkle here that the debugger sets MallocTagAll=1, so possibly it will pick up a wider range of issues. However I would have expected that a straight UAF would be caught. For example, this test code demonstrates that tagging is active but it doesn't crash:
#define PTR_TAG(p) ((unsigned)(((uintptr_t)(p) >> 56) & 0xF))
void *p1 = malloc(32);
void *p2 = malloc(32);
void *p3 = malloc(32);
os_log(OS_LOG_DEFAULT, "p1 = %p (tag: %u)\n", p1, PTR_TAG(p1));
os_log(OS_LOG_DEFAULT, "p2 = %p (tag: %u)\n", p2, PTR_TAG(p2));
os_log(OS_LOG_DEFAULT, "p3 = %p (tag: %u)\n", p3, PTR_TAG(p3));
free(p2);
void *p2_realloc = malloc(32);
os_log(OS_LOG_DEFAULT, "p2 after free+malloc = %p (tag: %u)\n", p2_realloc, PTR_TAG(p2_realloc));
// Is p2_realloc the same address as p2 but different tag?
os_log(OS_LOG_DEFAULT, "Same address? %s\n",
((uintptr_t)p2 & 0x00FFFFFFFFFFFFFF) == ((uintptr_t)p2_realloc & 0x00FFFFFFFFFFFFFF)
? "YES" : "NO");
// Now try to use the OLD pointer p2
os_log(OS_LOG_DEFAULT, "Attempting use-after-free via old pointer p2...\n");
volatile char c = *(volatile char *)p2; // Should this crash?
os_log(OS_LOG_DEFAULT, "Read succeeded! Value: %d\n", c);
Example output:
p1 = 0xf00000b71019660 (tag: 15)
p2 = 0x200000b711958c0 (tag: 2)
p3 = 0x300000b711958e0 (tag: 3)
p2 after free+malloc = 0x700000b71019680 (tag: 7)
Same address? NO
Attempting use-after-free via old pointer p2...
Read succeeded! Value: -55
For reference, these are my entitlements.
[Dict]
[Key] application-identifier
[Value]
[String] …
[Key] com.apple.developer.team-identifier
[Value]
[String] …
[Key] com.apple.security.hardened-process
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.checked-allocations
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.checked-allocations.enable-pure-data
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.dyld-ro
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.enhanced-security-version
[Value]
[Int] 1
[Key] com.apple.security.hardened-process.hardened-heap
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.platform-restrictions
[Value]
[Int] 2
[Key] get-task-allow
[Value]
[Bool] true
What do I need to do to make Memory Integrity Enforcement do something outside the debugger?
In these threads, it was clarified that Credential Provider Extensions must set both Backup Eligible (BE) and Backup State (BS) flags to 1 in authenticator data:
https://developer.apple.com/forums/thread/745605
https://developer.apple.com/forums/thread/787629
However, I'm developing a passkey manager that intentionally stores credentials only on the local device. My implementation uses:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly for keychain items
kSecAttrTokenIDSecureEnclave for private keys
No iCloud sync or backup
These credentials are, by definition, single-device credentials. According to the WebAuthn specification, they should be represented with BE=0, BS=0.
Currently, I'm forced to set BE=1, BS=1 to make the extension work, which misrepresents the actual backup status to relying parties. This is problematic because:
Servers using BE/BS flags for security policies will incorrectly classify these as synced passkeys
Users who specifically want device-bound credentials for higher security cannot get accurate flag representation
Request: Please allow Credential Provider Extensions to return credentials with BE=0, BS=0 for legitimate device-bound passkey implementations.
Environment: macOS 26.2 (25C56), Xcode 26.2 (17C52)
Topic:
Privacy & Security
SubTopic:
General
Tags:
Extensions
macOS
Authentication Services
Passkeys in iCloud Keychain
Hello,
I’m working on an authorization plugin which allows users to login and unlock their computer with various methods like a FIDO key. I need to add smart cards support to it. If I understand correctly, I need to construct a URLCredential object with the identity from the smart card and pass it to the completion handler of URLSessionDelegate.urlSession(_:didReceive:completionHandler:) method. I’ve read the documentation at Using Cryptographic Assets Stored on a Smart Card, TN3137: On Mac keychain APIs and implementations, and SecItem: Pitfalls and Best Practices and created a simple code that reads the identities from the keychain:
CFArrayRef identities = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)@{
(id)kSecClass: (id)kSecClassIdentity,
(id)kSecMatchLimit: (id)kSecMatchLimitAll,
(id)kSecReturnRef: @YES,
}, (CFTypeRef *)&identities);
if (status == errSecSuccess && identities) {
os_log(OS_LOG_DEFAULT, "Found identities: %{public}ld\n", CFArrayGetCount(identities));
} else {
os_log(OS_LOG_DEFAULT, "Error: %{public}ld\n", (long)status);
}
When I use this code in a simple demo app, it finds my Yubikey identities without problem. When I use it in my authorization plugin, it doesn’t find anything in system.login.console right and finds Yubikey in authenticate right only if I register my plugin as non-,privileged. I tried modifying the query in various ways, in particular by using SecKeychainCopyDomainSearchList with the domain kSecPreferencesDomainDynamic and adding it to the query as kSecMatchSearchList and trying other SecKeychain* methods, but ended up with nothing. I concluded that the identities from a smart card are being added to the data protection keychain rather than to a file based keychain and since I’m working in a privileged context, I won’t be able to get them. If this is indeed the case, could you please advise how to proceed? Thanks in advance.
Hi,
I'm using webauthn.io to test my macOS Passkey application. When registering a passkey whichever value I set for User Verification, that's what I get when I check registrationRequest.userVerificationPreference on prepareInterface(forPasskeyRegistration registrationRequest: any ASCredentialRequest).
However, when authenticating my passkey I can never get discouraged UV on prepareInterfaceToProvideCredential(for credentialRequest: any ASCredentialRequest).
In the WWDC 2022 Meet Passkeys video, it is stated that Apple will always require UV when biometrics are available. I use a Macbook Pro with TouchID, but if I'm working with my lid closed, shouldn't I be able to get .discouraged?
Topic:
Privacy & Security
SubTopic:
General
Tags:
Authentication Services
Passkeys in iCloud Keychain
Trusted execution is a generic name for a Gatekeeper and other technologies that aim to protect users from malicious code.
General:
Forums topic: Code Signing
Forums tag: Gatekeeper
Developer > Signing Mac Software with Developer ID
Apple Platform Security support document
Safely open apps on your Mac support article
Hardened Runtime document
WWDC 2022 Session 10096 What’s new in privacy covers some important Gatekeeper changes in macOS 13 (starting at 04: 32), most notably app bundle protection
WWDC 2023 Session 10053 What’s new in privacy covers an important change in macOS 14 (starting at 17:46), namely, app container protection
WWDC 2024 Session 10123 What’s new in privacy covers an important change in macOS 15 (starting at 12:23), namely, app group container protection
Updates to runtime protection in macOS Sequoia news post
Testing a Notarised Product forums post
Resolving Trusted Execution Problems forums post
App Translocation Notes (aka Gatekeeper path randomisation) forums post
Most trusted execution problems are caused by code signing or notarisation issues. See Code Signing Resources and Notarisation Resources.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Estou compartilhando algumas observações técnicas sobre Crash Detection / Emergency SOS no ecossistema Apple, com base em eventos amplamente observados em 2022 e 2024, quando houve chamadas automáticas em massa para serviços de emergência.
A ideia aqui não é discutir UX superficial ou “edge cases isolados”, mas sim comportamento sistêmico em escala, algo que acredito ser relevante para qualquer time que trabalhe com sistemas críticos orientados a eventos físicos.
Contexto resumido
A partir do iPhone 14, a Detecção de Acidente passou a correlacionar múltiplos sensores (acelerômetros de alta faixa, giroscópio, GPS, microfones) para inferir eventos de impacto severo e acionar automaticamente chamadas de emergência. Em 2022, isso resultou em um volume significativo de falsos positivos, especialmente em atividades com alta aceleração (esqui, snowboard, parques de diversão). Em 2024, apesar de ajustes, houve recorrência localizada do mesmo padrão.
Ponto técnico central
O problema não parece ser hardware, nem um “bug pontual”, mas sim o estado intermediário de decisão:
Aceleração ≠ acidente
Ruído ≠ impacto real
Movimento extremo ≠ incapacidade humana
Quando o classificador entra em estado ambíguo, o sistema depende de uma janela curta de confirmação humana (toque/voz). Em ambientes ruidosos, com o usuário em movimento ou fisicamente ativo, essa confirmação frequentemente falha. O sistema então assume incapacidade e executa a ação fail-safe: chamada automática.
Do ponto de vista de engenharia de segurança, isso é compreensível. Do ponto de vista de escala, é explosivo.
Papel da Siri
A Siri não “decide” o acidente, mas é um elo sensível na cadeia humano–máquina. Falhas de compreensão por ruído, idioma, respiração ofegante ou ausência de resposta acabam sendo interpretadas como sinal de emergência real. Isso é funcionalmente equivalente ao que vemos em sistemas automotivos como o eCall europeu, quando a confirmação humana é inexistente ou degradada.
O dilema estrutural
Há um trade-off claro e inevitável:
Reduzir falsos negativos (não perder um acidente real)
Aumentar falsos positivos (chamadas indevidas)
Para o usuário individual, errar “para mais” faz sentido. Para serviços públicos de emergência, milhões de dispositivos errando “para mais” criam ruído operacional real.
Por que isso importa para developers
A Apple hoje opera, na prática, um dos maiores sistemas privados de segurança pessoal automatizada do mundo, interagindo diretamente com infraestrutura pública crítica. Isso coloca Crash Detection / SOS na mesma categoria de sistemas safety-critical, onde:
UX é parte da segurança
Algoritmos precisam ser auditáveis
“Human-in-the-loop” não pode ser apenas nominal
Reflexões abertas
Alguns pontos que, como developer, acho que merecem discussão:
Janelas de confirmação humana adaptativas ao contexto (atividade física, ruído).
Cancelamento visual mais agressivo em cenários de alto movimento.
Perfis de sensibilidade por tipo de atividade, claramente comunicados.
Critérios adicionais antes da chamada automática quando o risco de falso positivo é estatisticamente alto.
Não é um problema simples, nem exclusivo da Apple. É um problema de software crítico em contato direto com o mundo físico, operando em escala planetária. Justamente por isso, acho que vale uma discussão técnica aberta, sem ruído emocional.
Curioso para ouvir perspectivas de quem trabalha com sistemas similares (automotivo, wearables, safety-critical, ML embarcado).
— Rafa
Topic:
Privacy & Security
SubTopic:
General
Tags:
Siri Event Suggestions Markup
Core ML
App Intents
Communication Safety
Hi Apple Developers,
I'm having a problem with evaluatedPolicyDomainState: on the same device, its value keeps changing and then switching back to the original. My current iOS version is 26.1.
I upgraded my iOS from version 18.6.2 to 26.1.
What could be the potential reasons for this issue?
{
NSError *error;
BOOL success = YES;
char *eds = nil;
int edslen = 0;
LAContext *context = [[LAContext alloc] init];
// test if we can evaluate the policy, this test will tell us if Touch ID is available and enrolled
// success = [context canEvaluatePolicy: LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
if (SystemVersion > 9.3) {
// test if we can evaluate the policy, this test will tell us if Touch ID is available and enrolled
success = [context canEvaluatePolicy: LAPolicyDeviceOwnerAuthentication error:&error];
}
else{
// test if we can evaluate the policy, this test will tell us if Touch ID is available and enrolled
success = [context canEvaluatePolicy: LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
}
if (success)
{
if (@available(iOS 18.0, *)) {
NSData *stateHash = nil;
if ([context respondsToSelector:@selector(domainState)]) {
stateHash = [[context performSelector:@selector(domainState)] performSelector:@selector(stateHash)];
}else{
stateHash = [context evaluatedPolicyDomainState];
}
eds = (char *)stateHash.bytes;
edslen = (int)stateHash.length;
} else {
eds = (char *)[[context evaluatedPolicyDomainState] bytes];
edslen = (int)[[context evaluatedPolicyDomainState] length];
}
CC_SHA256(eds, edslen, uviOut);
*poutlen = CC_SHA256_DIGEST_LENGTH;
}
else
{
*poutlen = 32;
gm_memset(uviOut, 0x01, 32);
}
}
After registe Passkey with webauthn library, i create a passkeyRegistration with follow,
let passkeyRegistration = ASPasskeyRegistrationCredential(relyingParty: serviceIdentifier, clientDataHash: clientDataHashSign, credentialID: credentialId, attestationObject: attestationObject)
and then completeRegistrationRequest like that,
extensionContext.completeRegistrationRequest(using: passkeyRegistration)
But a bad outcome occurred from user agent. NotAllowedError:The request is not allowed by the user agent or the platform in the current context.
And the return data rawID & credentialPublicKey is empty,
Topic:
Privacy & Security
SubTopic:
General
Tags:
Autofill
Authentication Services
Passkeys in iCloud Keychain
My application is supporting hybrid transport on FIDO2 webAuthn specs to create credential and assertion. And it support legacy passkeys which only mean to save to 1 device and not eligible to backup.
However In my case, if i set the Backup Eligibility and Backup State flag to false, it fails on the completion of the registrationRequest to save the passkey credential within credential extension, the status is false instead of true.
self.extension.completeRegistrationRequest(using: passkeyRegistrationCredential)
The attestation and assertion flow only works when both flags set to true.
Can advice why its must have to set both to true in this case?
Hi
I'm developing an app that autofills Passkeys. The app allows the user to authenticate to their IdP to obtain an access token. Using the token the app fetches from <server>/attestation/options.
The app will generate a Passkey credential using a home-grown module - the extension has no involvement, neither does ASAuthorizationSecurityKeyPublicKeyCredentialProvider. I can confirm the passkey does get created.
Next the credential is posted to <server>/attestation/results with the response JSON being parsed and used to create a ASPasskeyCredentialIdentity - a sample of the response JSON is attached.
Here is my save function:
static func save(authenticator: AuthenticatorInfo) async throws {
guard let credentialID = Data(base64URLEncoded: authenticator.attributes.credentialId) else {
throw AuthenticatorError.invalidEncoding("Credential ID is not a valid Base64URL string.")
}
guard let userHandle = authenticator.userId.data(using: .utf8) else {
throw AuthenticatorError.invalidEncoding("User handle is not a valid UTF-8 string.")
}
let identity = ASPasskeyCredentialIdentity(
relyingPartyIdentifier: authenticator.attributes.rpId,
userName: authenticator.userId, // This is what the user sees in the UI
credentialID: credentialID,
userHandle: userHandle,
recordIdentifier: authenticator.id
)
try await ASCredentialIdentityStore.shared.saveCredentialIdentities([identity])
}
Although no error occurs, I don't get any identities returned when I call this method:
let identities = await ASCredentialIdentityStore.shared.credentialIdentities(
forService: nil,
credentialIdentityTypes: [.passkey]
)
Here is the Info.plist in the Extension:
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>ASCredentialProviderExtensionCapabilities</key>
<dict>
<key>ProvidesPasskeys</key>
<true/>
</dict>
<key>ASCredentialProviderExtensionShowsConfigurationUI</key>
<true/>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.authentication-services-credential-provider-ui</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).CredentialProviderViewController</string>
</dict>
</dict>
</plist>
The entitlements are valid and the app and extension both support the same group.
I'm stumped as to why the identity is not getting saved. Any ideas and not getting retrieved.
attestationResult.json