fix: add missing TCC resource-access entitlements and usage strings - #398
Conversation
|
add it. your read on the exclusion is right: the CLI installer path is 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. |
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.
1354f2c to
b5f641c
Compare
|
Rebased on master — you were right about #400, I should have pulled first. ✅ Decision reached and addressed —
|
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
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,tccdrefuses 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: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 isNSLocationUsageDescription— 10.14+, not deprecated there.The deprecated
NSCalendarsUsageDescription/NSRemindersUsageDescriptionare 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 bundledagtermctl, a standalone CLI on the user'sPATH. Measured before the fix, the helper carried all six TCC entitlements. Now--deepsigns without entitlements and a second non-deep pass applies them to the app alone. Shipped artifacts were never affected —release.shre-signs the helper itself — onlymake build/make deployoutput.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:
tccd-1743, no promptkTCCServiceAppleEvents requires entitlement …automation.apple-events but it is missingkTCCServiceCamera requires …device.camera…kTCCServiceAddressBook requires …addressbook…kTCCServiceCalendar requires …calendars…kTCCServicePhotos requires …photos-library…notDetermined, no promptlocationd, nottccdAll carry
promptPolicy = 4andresponsible={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, bothflags=0x10002(adhoc,runtime), distinct bundle ids:-1743, no promptnotDeterminedauthorizedAlwaystccdun-entitled:Policy disallows prompt … denied. Entitled:AUTHREQ_PROMPTING, all three dialogs confirmed on screen. This also settles location, which produces notccdline of its own.Build:
make buildand the signing phase pass from an emptyDerivedData, withagtermctlcarrying no entitlements, the app all six, andcodesign --verify --deep --strictclean.✅ Resolved —
NSSystemAdministrationUsageDescriptionI 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:
CLIInstaller.elevatedSymlinkalready elevates without it. That path isdo 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.ODRecordSetValue— the OpenDirectory API behinddscl. A tool runningdscl . -createin a session lands exactly here, with agterm responsible.One word and I'll push it.Still deliberately out of scope: the Files & Folders family (
NSDesktopFolderUsageDescriptionand 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-appfailuremake test-appfails locally onLiveMenuKeyEquivalentsTests.testUppercaseKeyEquivalentReportsImpliedShift(), on unmodifiedmastertoo. 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 onaddItem, before the code under test runs.