C.W.K.
Stream
Lesson 05 of 05 · published

The TCC Signed Launcher

~11 min · war-story, permissions, code-signing, process-group

Level 0Empty Shelf
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"The permission wasn't denied to your code. It was denied to your code's identity — and a bare interpreter barely has one."

The Same Python, Two Different Answers

Second war story, same maddening shape as the last. The executor needs to read the video archive off a mounted volume — which, on modern macOS, requires the user to grant full-disk file access. You grant it, you SSH in, you run the Python, and it reads the archive perfectly. Then the service manager launches that exact same interpreter, on the same machine, and the operating system denies it file access.

The reason is that the permission system doesn't grant access to code. It grants access to an identity — a code-signed application, attributed to a responsible process. An interactive shell session carries an identity the system already trusts, and your Python inherits that context. A bare interpreter binary launched by the service manager carries no such grantable identity, so the request lands on nothing the user ever approved, and gets denied.

And it gets worse in a way that's easy to miss: even if you tried to grant access directly to that Python binary, a routine environment update can replace the binary — and the grant, which was attached to that specific binary, evaporates. You'd have built a permission that silently expires the next time you update your tooling. A grant to a swappable file is a grant with an expiry date you didn't choose and won't be told about.

The Fix: Give the Grant a Stable Identity to Live On

Recall's answer is to stop asking the OS to trust a replaceable file. The service manager hosts a small, machine-local, code-signed application bundle with a stable bundle identifier. That launcher is what the system sees; it's the responsible parent process, and it spawns the runtime Python as a child. Full-disk access is granted once, to the stable app identity — not to an interpreter that conda might replace next week. The permission now outlives every routine update to the toolchain underneath it, because it was attached to something that doesn't churn.

That's the transferable principle: permission grants attach to identity, so the identity must be stable and deliberately owned. If your grant is pinned to something that gets regenerated, upgraded, or reinstalled as a matter of routine, you haven't been granted access — you've been lent it until the next update.

The Detail That Bites Later: Forward the Signals

Inserting a launcher between the service manager and the real work creates one new obligation, and skipping it produces a nasty class of bug. When the service manager stops the job, it signals the process it knows about — the launcher. If the launcher just dies, the Python child it spawned can be orphaned: a transcription still running, still holding a lease, invisible to the lifecycle you thought you controlled. So the launcher forwards termination signals to the entire worker process group. Restart means restart, not 'restart plus a ghost still working.' Whenever you put a supervisor in front of real work, its job isn't just to start things — it's to make sure stopping actually stops everything it started.

Code

Grant the identity, not the interpreter — and forward the signals·text
WRONG: service manager -> bare interpreter binary
  - no stable grantable identity -> file access DENIED
  - even if granted: an env update REPLACES the binary
    -> the grant silently evaporates

RIGHT: service manager -> signed app bundle (stable bundle id)
                            └─ spawns runtime Python as a child
  - full-disk access granted ONCE to the stable app identity
  - survives conda/toolchain updates underneath it
  - launcher is the RESPONSIBLE PARENT process

OBLIGATION: the launcher must forward termination signals to the
WHOLE worker process group -- or 'stop' orphans a live transcription
that still holds a lease.

External links

Exercise

Find a permission, credential, or access grant in your own setup that's attached to a specific artifact — a binary path, a generated key file, a container image tag, an interpreter. Ask: what routine action would replace that artifact, and would the grant survive it? Then look at any supervisor or wrapper in your stack: when you stop it, does it forward termination to everything it started, or could a child survive as a ghost?
Hint
Two probes: (1) name the update that replaces the thing your grant is pinned to (a package upgrade, a rebuild, a redeploy) — if the grant dies with it, re-pin to a stable identity you own on purpose. (2) Stop the supervisor and check whether any child process is still alive afterward. A stop that leaves work running is worse than no stop, because your tooling reports success while a ghost keeps holding resources.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.