We are qualifying a local macOS control service on Apple Silicon, macOS 26.5.2 (25F84). Browser control sessions must be revoked on graphical screen lock; unlock must not restore old sessions. Background project execution and durable pending decisions continue independently.
We built a correctly signed user-context helper using its own data protection Keychain access group and a nonsecret WhenUnlockedThisDeviceOnly item. Every call uses a fresh noninteractive LAContext and requests actual item data. In one user-confirmed stock screen lock/unlock test, reads returned errSecInteractionNotAllowed while locked and expected data after unlock. This is one observation, not a general screen-state contract.
The installed EndpointSecurity SDK explicitly states that es_graphical_session_id_t is not an audit session ID. CoreGraphics public caller-session properties expose UID, on-console and login-done, but no documented lock-state/current ES graphical-session identifier.
- What supported API maps a caller Aqua user-context process or NSXPCConnection audit session to the relevant ES graphical_session_id? If none exists, what supported binding should a trusted user-context helper use for current graphical lock authority, including Fast User Switching and Screen Sharing contexts?
- Does a fresh successful macOS DP WhenUnlockedThisDeviceOnly data read imply that this graphical session is unlocked? How does the documented SSH path that unlocks the login Keychain and DP Keychain interact with a still-locked graphical session or another context of the same UID?
- Is there a supported coherent current-lock snapshot/subscription mechanism for initial startup or observer restart? What completeness, ordering and maximum latency contracts apply to userspace-produced ES LoginWindow lock events? Does any supported fence detect a final delayed/lost lock or a lock+unlock between two protected reads?
- Does any supported API bind the unlocked condition to a subsequent application control decision, such as an application-owned SQLite COMMIT? A receiver-side monotonic freshness check can reject late replies during coordinator stalls, but its final check and COMMIT remain separate operations. We seek a supported contract or OS-enforced mechanism for this boundary.
We can conservatively revoke all broker control sessions on any trusted graphical lock instead of guessing an ASID→graphical-ID mapping, but this still leaves bootstrap/current-state and event completeness open. We have not assumed private CGSession keys, audit authentication flags, a helper heartbeat, or a queue-tail fence prove screen-unlocked state.
Primary references: TN3137, SecItem: Pitfalls and Best Practices, ES graphical session ID, CG session dictionary, XPC audit session.
Thanks for that explanation. It was very clear.
Given those requirements I’m inclined to recommend the NSApplicationProtectedDataDidBecomeAvailable notification. It’s not directly tied to screen lock state but rather to the state of the keybag [1] that controls access to protected data on the file system and in the keychain. If, for example, you store a credential in the keychain [2] such that it’s accessible when unlocked, this tells you whether it’s accessible or not. That seems appropriate for your goal here.
Keep in mind that this state is only accessible from within GUI login session (it’s an AppKit API and AppKit is only supported within a GUI login session). So you’ll need some sort of process running within the session to pick this up, for example, a launchd agent. However, I don’t think that’ll be a problem in your case because you’re already doing stuff, like dealing with web browsers, that requires you to be running within the GUI login session.
Also note that if you search the ’net you’ll find a bunch of info about screen lock and unlock notifications. AFAICT these are all implementation details rather than API.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] See Apple Platform Security for a definition of that term.
[2] Specifically, the data protection keychain.