TCC Permission Inheritance Failure: Swift Parent -> Python Child

TCC Permission Inheritance for Python Process Launched by Swift App in Enterprise Deployment

We are developing an enterprise monitoring application that requires a hybrid Swift + Python architecture due to strict JAMF deployment restrictions. We must deploy a macOS application via ABM/App Store Connect, but our core monitoring logic is in a Python daemon. We need to understand the feasibility and best practices for TCC permission inheritance in this specific setup.


Architecture

ComponentBundle IDRoleDeployment
Swift Launchercom.athena.AthenaSentryRequests TCC permissions, launches Python child process.Deployed via ABM/ASC.
Python Daemoncom.athena.AthenaSentry.HelperCore monitoring logic using sensitive APIs.Nested in Contents/Helpers/.

Both bundles are signed with the same Developer ID and share the same Team ID.


Required Permissions

The Python daemon needs to access the following sensitive TCC-controlled services:

  • Screen Recording (kTCCServiceScreenCapture) - for capturing screenshots.
  • Input Monitoring (kTCCServiceListenEvent) - for keystroke/mouse monitoring.
  • Accessibility (kTCCServiceAccessibility) - a prerequisite for Input Monitoring.

Attempts & Workarounds

We have attempted to resolve this using:

  • Entitlement Inheritance: Added com.apple.security.inherit to the Helper's entitlements.
  • Permission Proxy: Swift app maintains active event taps to try and "hold" the permissions for the child.
  • Foreground Flow: Keeping the Swift app in the foreground during permission requests.

Questions

  1. Is this architecture supported? Can a Swift parent app successfully request TCC permissions that a child process can then use?
  2. TCC Inheritance: What are the specific rules for TCC permission inheritance between parent/child processes in enterprise environment?
  3. What's the correct approach for this enterprise use case? Should we:
    • Switch to a Single Swift App? (i.e., abandon the Python daemon and rewrite the core logic natively in Swift).
    • Use XPC Services? (instead of launching the child process directly).
our core monitoring logic is in a Python daemon.

I’d like to clarify what you mean by this. On macOS we generally use the term daemon to mean a launchd daemon, that is, something that launchd runs in the global context, usually as the result of a property list file in /Library/LaunchDaemons [1]. However, it sounds like you’re use in in the more general sense of a program that runs in the background.

So, how is this program actually launched? You mentioned it’s a child process, so presumably you’re using fork / exec* or posix_spawn or something layered on top of that. In which case, how is the parent process launched?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Or installed as a daemon via SMAppService.

Hi,

Thank you for the clarification request. You are correct in your assumption.

1. "Daemon" Clarification

We are using the term "daemon" in the general sense of a persistent background helper process, not a system-wide launchd daemon (it is not registered in /Library/LaunchDaemons or managed by SMAppService).

2. Child Process (Python) Launch

The Python process is launched directly as a child process of the main Swift application.

As detailed in the "Python Daemon Launch" section of our document, we are using Swift's high-level Process API (which, as you noted, is layered on top of technologies like posix_spawn).

The specific code used is:

let process = Process()
process.executableURL = URL(fileURLWithPath: pythonExecutablePath) 
// path is "AthenaSentry.app/Contents/Helpers/AthenaSentry.app/Contents/MacOS/AthenaSentry"
process.environment = /* ... */
try process.run()

3. Parent Process (Swift) Launch

The parent process is the main Swift wrapper application, AthenaSentry.app (Bundle ID com.athena.AthenaSentry). It is a standard macOS application deployed via ABM/App Store Connect and installed by JAMF. The user launches it normally from the /Applications folder, at which point it requests TCC permissions and then attempts to launch its child Python helper process.

Our central issue is that the TCC permissions (Screen Recording, Input Monitoring, Accessibility) granted by the user to the parent Swift AthenaSentry.app are not being inherited by the child Python process launched via this Process API method.

Thanks for all the answers.

What you’re dealing with here is the responsible code problem. When a process performs a privileged operation, macOS has to track down the responsible code so that it can check whether that code has TCC privileges or not.

In general, macOS will follow the chain from child process to parent process to determine process responsibility. However, things are not that simple:

  • The child process might be doing things to break the chain.
  • There are also situations where macOS doesn’t follow the chain as you might expect.

I have a test project that I use to exercise cases like this, and it seems to be working for me. Here’s what I did today:

  1. On macOS 15.7.1, I went to System Settings > Privacy & Security > Input Monitoring and confirmed that there’s no entry for my test app there.
  2. I used Xcode 26.0.1 to build and run the app.
  3. I started input monitoring within the app.
  4. The system presented the permissions alert. I clicked Open System Settings.
  5. And enabled Input Monitoring in System Settings. I chose not to quit and relaunch the app (so that I can re-run it from Xcode).
  6. Back in the app, I quit it.
  7. I re-ran the app from Xcode.
  8. I repeated step 3. This time there was no alert and the app’s input monitoring code worked as expected.
  9. I stopped input monitoring.
  10. I changed the app’s settings so that it does its input monitoring in a child process.
  11. I started input monitoring again. This behaved like step 8, that is, the system was able to track responsibility from the child to the parent, see that the parent has the Input Monitoring privilege, and thus allow the operation.

So, at least in this specific case, things work as expected. Which raises the question as to why it’s not working for you.

Before I dig into that, I want to ask you about the expected lifecycle of this helper process. Does it run while the app is running? Or do you expect it to continue running after the app has quit?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

TCC Permission Inheritance Failure: Swift Parent -> Python Child
 
 
Q