Why does NSEvent.addGlobalMonitorForEvents still work in a Sandboxed macOS app

I am building a macOS utility using SwiftUI and Swift that records and displays keyboard shortcuts (like Cmd+C, Cmd+V) in the UI. To achieve this, I am using NSEvent.addGlobalMonitorForEvents(matching: [.keyDown]).

I am aware that global monitoring usually requires the app to be non-sandboxed. However, I am seeing some behavior I don't quite understand during development:

  1. I started with a fresh SwiftUI project and disabled the App Sandbox.

  2. I requested Accessibility permissions using AXIsProcessTrustedWithOptions, manually enabled it in System Settings, and the global monitor worked perfectly.

  3. I then re-enabled the App Sandbox in "Signing & Capabilities."

  4. To my surprise, the app still records global events from other applications, even though the Sandbox is now active.

Is this expected behavior? Does macOS "remember" the trust because the Bundle ID was previously authorized while non-sandboxed, or is there a specific reason a Sandboxed app can still use addGlobalMonitor if the user has manually granted Accessibility access?

My app's core feature is displaying these shortcuts for the user's own reference (productivity tracking). If the user is the one explicitly granting permission via the Accessibility privacy pane, will Apple still reject the app for using global event monitors within a Sandboxed environment?

Code snippet of my monitor:

// This is still firing even after re-enabling Sandbox

eventMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.keyDown]) { event in

    print("Captured: \(event.charactersIgnoringModifiers ?? "")")

}

I've tried cleaning the build folder and restarting the app, removing the app from accessibility permission, but the events keep coming through. I want to make sure I'm not relying on a "development glitch" before I commit to the App Store path.

Here is the full code anyone can use to try this :-


import SwiftUI



import Cocoa

import Combine



struct ShortcutEvent: Identifiable {

    let id = UUID()

    let displayString: String

    let timestamp: Date

}



class KeyboardManager: ObservableObject {

    @Published var isCapturing = false

    @Published var capturedShortcuts: [ShortcutEvent] = []

    private var eventMonitor: Any?



    // 1. Check & Request Permissions

    func checkAccessibilityPermissions() -> Bool {

        let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true]

        let accessEnabled = AXIsProcessTrustedWithOptions(options)

        return accessEnabled

    }



    // 2. Start Capture

    func startCapture() {

        guard checkAccessibilityPermissions() else {

            print("Permission denied")

            return

        }

        

        isCapturing = true

        let mask: NSEvent.EventTypeMask = [.keyDown, .keyUp]

        

        eventMonitor = NSEvent.addGlobalMonitorForEvents(matching: mask) { [weak self] event in

            self?.processEvent(event)

        }

    }



    // 3. Stop Capture

    func stopCapture() {

        if let monitor = eventMonitor {

            NSEvent.removeMonitor(monitor)

            eventMonitor = nil

        }

        isCapturing = false

    }



    private func processEvent(_ event: NSEvent) {

            // Only log keyDown to avoid double-counting the UI display

            guard event.type == .keyDown else { return }



            var modifiers: [String] = []

            var symbols: [String] = []



            // Map symbols for the UI

            if event.modifierFlags.contains(.command) {

                modifiers.append("command")

                symbols.append("⌘")

            }

            if event.modifierFlags.contains(.shift) {

                modifiers.append("shift")

                symbols.append("⇧")

            }

            if event.modifierFlags.contains(.option) {

                modifiers.append("option")

                symbols.append("⌥")

            }

            if event.modifierFlags.contains(.control) {

                modifiers.append("control")

                symbols.append("⌃")

            }



            let key = event.charactersIgnoringModifiers?.uppercased() ?? ""

            

            // Only display if a modifier is active (to capture "shortcuts" vs regular typing)

            if !symbols.isEmpty && !key.isEmpty {

                let shortcutString = "\(symbols.joined(separator: " ")) + \(key)"

                

                DispatchQueue.main.async {

                    // Insert at the top so the newest shortcut is visible

                    self.capturedShortcuts.insert(ShortcutEvent(displayString: shortcutString, timestamp: Date()), at: 0)

                }

            }

        }

}

PS :- I just did another test by creating a fresh new project with the default App Sandbox enabled, and tried and there also it worked!!

Can I consider this a go to for MacOs app store than?

Why does NSEvent.addGlobalMonitorForEvents still work in a Sandboxed macOS app
 
 
Q