Skip to content

fix: add missing TCC resource-access entitlements and usage strings - #398

Merged
umputun merged 5 commits into
umputun:masterfrom
skkap:fix/tcc-resource-access-entitlements
Aug 9, 2026
Merged

fix: add missing TCC resource-access entitlements and usage strings#398
umputun merged 5 commits into
umputun:masterfrom
skkap:fix/tcc-resource-access-entitlements

Conversation

@skkap

@skkap skkap commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #396. Same mechanism as #142, one layer down: that was a missing usage string, these are missing hardened-runtime entitlements.

agterm is signed with hardened runtime, under which Apple gates seven resource-access entitlements. agterm carried one (device.audio-input). For the other six, tccd refuses to prompt and records nothing — so a tool run inside a session cannot obtain the permission, and the user cannot grant it by hand either, because with no TCC record agterm never appears in the relevant Privacy & Security pane. Grants agterm already holds keep working, which is why this went unnoticed: only new grants are impossible.

Changes

agterm/agterm.entitlements — the six missing resource-access entitlements:

automation.apple-events   device.camera   personal-information.addressbook
personal-information.calendars   personal-information.location   personal-information.photos-library

agterm/Info.plist — usage strings for the services with a real command-line path, worded to match the existing microphone string: AppleEvents, Camera, Contacts, Calendars (+ full-access, + write-only), Reminders (+ full-access), PhotoLibrary, Location, Bluetooth, LocalNetwork, SpeechRecognition.

Every key was checked against Apple's documented platform availability rather than copied from other terminals — that caught three keys iTerm2/Ghostty/kitty ship which are iOS/iPadOS/visionOS only and do nothing on macOS (NSLocationWhenInUseUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSPhotoLibraryAddUsageDescription). The macOS key is NSLocationUsageDescription — 10.14+, not deprecated there.

The deprecated NSCalendarsUsageDescription / NSRemindersUsageDescription are kept deliberately. Both are deprecated at macOS 14, but agterm is the responsible app for third-party tools of unknown SDK vintage, and one linked against a pre-14 SDK still resolves against the legacy key.

project.yml — the reseal applied --deep --entitlements, which stamped the app's entitlement set onto the bundled agtermctl, a standalone CLI on the user's PATH. Measured before the fix, the helper carried all six TCC entitlements. Now --deep signs without entitlements and a second non-deep pass applies them to the app alone. Shipped artifacts were never affected — release.sh re-signs the helper itself — only make build / make deploy output.

Verification

Each service was exercised from a CLI inside agterm 0.20.2 / macOS 26.5.2, against the released build — the failures this PR fixes, reproduced per service:

Service Result tccd
Apple Events -1743, no prompt kTCCServiceAppleEvents requires entitlement …automation.apple-events but it is missing
Camera denied kTCCServiceCamera requires …device.camera…
Contacts denied kTCCServiceAddressBook requires …addressbook…
Calendars denied kTCCServiceCalendar requires …calendars…
Photos denied kTCCServicePhotos requires …photos-library…
Location notDetermined, no prompt none — handled by locationd, not tccd

All carry promptPolicy = 4 and responsible={com.umputun.agterm}.

That the entitlements are the cure was then shown with two minimal apps identical except for the entitlements passed to codesign — same binary, same usage strings, both flags=0x10002(adhoc,runtime), distinct bundle ids:

no entitlements with entitlements
Apple Events -1743, no prompt prompt → granted
Camera denied, no prompt prompt → granted
Location notDetermined prompt → authorizedAlways

tccd un-entitled: Policy disallows prompt … denied. Entitled: AUTHREQ_PROMPTING, all three dialogs confirmed on screen. This also settles location, which produces no tccd line of its own.

Build: make build and the signing phase pass from an empty DerivedData, with agtermctl carrying no entitlements, the app all six, and codesign --verify --deep --strict clean.

✅ Resolved — NSSystemAdministrationUsageDescription

Decision reached and addressed. Approved in review; the key is in the diff as of b5f641c. The
follow-up asked alongside it — whether a missing purpose string terminates the process rather than
denying — was checked: it denies. Details in the review thread.

Original question kept below for the record.

I think this should go in, and I'd like to add it. It is not in the diff because you asked me to leave it out, and I'm not going to include it over that without checking first — but I don't think the reason it was excluded holds up:

  1. The exclusion tested a different subsystem. It was ruled out because CLIInstaller.elevatedSymlink already elevates without it. That path is do shell script … with administrator privileges — Authorization Services, not TCC. This key gates a TCC service, so the installer working says nothing either way about it.
  2. Apple documents it as macOS-only and required. macOS 10.14+, not deprecated: "A message in macOS that tells people why the app is requesting to manipulate the system configuration", "This key is required if your app uses APIs that manipulate the system configuration", naming ODRecordSetValue — the OpenDirectory API behind dscl. A tool running dscl . -create in a session lands exactly here, with agterm responsible.
  3. It is the same class as every other key here, and all three peers ship it. Leaving this one out makes agterm the lone outlier for no mechanical reason, and the failure mode is the silent one this whole PR exists to remove.

One word and I'll push it.

Still deliberately out of scope: the Files & Folders family (NSDesktopFolderUsageDescription and friends). Those prompts already work — the keys would only replace Apple's generic wording with agterm's — so that is a copy improvement, not a fix, and belongs in its own change.

✅ Resolved — local make test-app failure

Decision reached and addressed. Already fixed on master by 16d8cbc; this branch is rebased onto it
and make test-app is green. The diagnosis below was wrong — see #400, which is closed as redundant.

Original note kept below for the record.

make test-app fails locally on LiveMenuKeyEquivalentsTests.testUppercaseKeyEquivalentReportsImpliedShift(), on unmodified master too. Unrelated to this PR and fixed separately in #400 — the fixture uses the title "Paste and Match Style", which AppKit recognises as a standard item and rewrites to ⇧⌘V on addItem, before the code under test runs.

@skkap
skkap marked this pull request as ready for review August 8, 2026 14:32
@skkap
skkap requested a review from umputun as a code owner August 8, 2026 14:32
@umputun

umputun commented Aug 8, 2026

Copy link
Copy Markdown
Owner

add it.

your read on the exclusion is right: the CLI installer path is do shell script ... with administrator privileges, which is Authorization Services, not TCC, so it says nothing about NSSystemAdministrationUsageDescription either way. And dscl . -create in a session is exactly the "real command-line path" test the other strings pass, so leaving this one out is the outlier.

one thing worth checking while you have the scratch apps out: this key has no hardened-runtime entitlement behind it, unlike the six in the diff. If a TCC service that requires a purpose string and has none terminates the touching process instead of denying it, the failure mode here is the tool dying rather than the silent deny the rest of this PR fixes. I have not verified that, so treat it as a guess until you check.

push it and I'll merge.

umputun added a commit that referenced this pull request Aug 8, 2026
- tree reports split:false for a hidden split while splitRatio/splitFocused
  stay populated, surfaced investigating #402
- no user-facing note on the TCC prompts the hardened-runtime entitlements
  can raise
- nothing catches the build phase stamping entitlements onto agtermctl

Related to #398.
@skkap
skkap marked this pull request as draft August 9, 2026 06:06
skkap added 5 commits August 9, 2026 15:07
agterm is signed with hardened runtime, under which Apple gates seven
resource-access entitlements. Only device.audio-input was present, so
for the other six tccd refuses to prompt and records nothing: a program
run inside a session cannot obtain the permission, and the user cannot
grant it by hand either, because with no TCC record agterm never appears
in the relevant Privacy & Security pane.

Adds the six missing entitlements, plus usage strings for the services
with a real command-line path, worded to match the existing microphone
string. Same mechanism as umputun#142, one layer down.

Fixes umputun#396
The postBuild re-seal used --deep --entitlements, which re-signs every
nested Mach-O with the app's entitlement set. That stamped the bundled
agtermctl -- a standalone CLI the user puts on their PATH -- with every
TCC entitlement the app declares. Seal inside-out instead, matching what
scripts/release.sh already does for Developer ID builds; the helper keeps
the entitlement-free signature from the line above.

Also add the split-service usage strings macOS documents alongside the
ones already here: EventKit full-access (macOS 14+), the Photos add-only
service, and the always-authorization location key. Drop the deprecated
NSLocationUsageDescription -- unreachable at LSMinimumSystemVersion 14.0
and a byte-for-byte duplicate of the WhenInUse string.
Dropping --deep broke a CLEAN build: this phase can run BEFORE Xcode signs
agterm.debug.dylib, so sealing the app without --deep failed with "code
object is not signed at all" on that subcomponent. Incremental builds hid
it, because the dylib was already signed from a previous run.

Sign every nested dylib explicitly before the seal, so the bundle is signed
inside-out without handing the app's entitlements to the nested agtermctl
executable. Verified from an empty DerivedData: build succeeds, agtermctl
carries no entitlements, the app carries all six, and codesign --verify
--deep --strict passes.
Checked every added key against Apple's documented availability instead of
copying what other terminals ship. Three were wrong for macOS:

  NSLocationWhenInUseUsageDescription        iOS/iPadOS/visionOS only
  NSLocationAlwaysAndWhenInUseUsageDescription  iOS/iPadOS/visionOS only
  NSPhotoLibraryAddUsageDescription          iOS/iPadOS/visionOS only

Apple states plainly: "If you need location information in a macOS app, use
NSLocationUsageDescription instead" -- which is macOS 10.14+ and NOT
deprecated there, so restoring it is right and dropping it was not.

Add NSCalendarsWriteOnlyAccessUsageDescription (macOS 14.0+): EventKit split
into full-access and write-only at macOS 14, and only the full-access half
was covered.

The deprecated NSCalendarsUsageDescription / NSRemindersUsageDescription stay
deliberately. They are deprecated at macOS 14, but agterm is the responsible
app for third-party CLI tools of unknown SDK vintage, and a tool linked
against a pre-14 SDK still resolves against the legacy key.

Also restore --deep on the reseal, with entitlements applied in a second
non-deep pass. Scoping the previous find to Contents/MacOS/*.dylib missed the
injected XCTest frameworks and agtermTests.xctest that --deep had covered,
and BSD xargs -I{} caps arguments at 255 bytes. Two passes keep agtermctl
entitlement-free without narrowing what gets signed.
Approved on review. The key was originally left out because CLIInstaller.elevatedSymlink
already elevates without it, but that path is Authorization Services, not TCC, so it says
nothing about this key either way.

Apple documents it as macOS 10.14+, not deprecated, and required for APIs that manipulate
the system configuration -- naming ODRecordSetValue, the OpenDirectory call behind dscl. A
tool running `dscl . -create` in a session lands there with agterm responsible, which is
the same command-line path the other strings are justified by.
@skkap
skkap force-pushed the fix/tcc-resource-access-entitlements branch from 1354f2c to b5f641c Compare August 9, 2026 06:15
@skkap

skkap commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on master — you were right about #400, I should have pulled first.

✅ Decision reached and addressed — NSSystemAdministrationUsageDescription

Added in b5f641c, worded to match the rest:

Command-line tools you run inside agterm (for example, dscl editing a directory record) request permission to change system configuration through agterm.

✅ Decision reached and addressed — missing purpose string

Checked it with the scratch apps: it denies, it does not terminate. Same binary in two bundles differing only in the usage string — the stringed one prompts and is granted, the string-less one gets a plain "Access Denied" and runs to completion with no crash. So the failure mode here is the same silent deny as the rest of this PR, not a dying tool. Full measurements on request.

Two limits, since you asked me to treat it as a guess until checked: I could not drive the SysAdmin service itself to a TCC decision — OpenDirectory rejects the write on credentials before TCC is consulted — and the behaviour may vary by framework. So that result is measured for Contacts and expected, not verified, for the rest.

Gates

Clean on the rebase: swift test 2411 tests, make test-app 176 tests, swiftlint --strict 0 violations.

Description

Both open items are marked ✅ Resolved with what was decided and where it landed, originals kept struck through.

@skkap
skkap marked this pull request as ready for review August 9, 2026 06:29

@umputun umputun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@umputun
umputun merged commit ab1256c into umputun:master Aug 9, 2026
7 checks passed
umputun added a commit that referenced this pull request Aug 9, 2026
scripts/release.sh carried the inside-out ordering but never its reason, so a
nested-signing failure there invites --deep, which would stamp the app's TCC
entitlements onto the agtermctl on the user's PATH. The guard in
docs/backlog/no-guard-against-agtermctl-entitlement-stamping.md runs before
this re-sign and would not catch it.

Also drops the XCTest clause from the project.yml re-seal comment: the
agtermTests scheme builds serially (project.yml:22-24), so the test bundle is
not in the app when that phase runs.

Related to #398
@skkap
skkap deleted the fix/tcc-resource-access-entitlements branch August 9, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Programs run inside agterm can never be granted Automation (and most other TCC permissions) — missing hardened-runtime resource-access entitlements

2 participants