On this page

macOS privacy grants

macOS decides per program whether it may open Documents, Desktop and Downloads. A decision is made once, when the person at the Mac answers the system's prompt or flips the switch in System Settings → Privacy & Security → Files and Folders, and it stays until someone changes it there. No program can grant itself access. A denial clicked once therefore stops every background program that reads those folders, with the kernel's Operation not permitted. Stado's own background roles read none of them: the host process's product sync works in ~/.stado/products/workspace, a workspace Stado owns and clones each product's source into, so it needs no grant (see Product catalog).

Stado measures what its own host process may read, judges that against the grants the registry declares for the host, and reports both; it does not change the decision.

Declare what a host needs

Each registry target may carry privacy_grants, one entry per program and folder it must be allowed to read:

"privacy_grants": [
  {
    "program": "~/.local/bin/<product>",
    "folder": "documents",
    "reason": "<why the program reads that folder>"
  }
]

program is the executable macOS judges: an absolute path, or one under ~/, which is the home of the user whose beacon measured the folders. folder is documents, desktop or downloads. reason says why the program needs the folder and is printed beside every verdict. Grants are allowed only on a darwin-* release_platform, because only macOS decides folder access per program.

Add, change or remove a grant

The grants are one registry field, and stado registry set writes it under the generation it read, so a registry another writer changed meanwhile is refused with exit 75 instead of overwritten. The list given is the whole new list: add an entry by setting the list with it, remove one by setting the list without it, and remove them all with []. A target that carries no privacy_grants yet gets the field from the first set.

# list what is declared, then what the host measured beside it
stado registry pull --path targets.<host>.privacy_grants
stado host privacy <host>

# add: a backup tool that reads ~/Documents on this Mac
stado registry set --path targets.<host>.privacy_grants \
  --value '[{"program":"~/.local/bin/<product>","folder":"documents","reason":"backs up ~/Documents nightly"}]'

# change one field of an existing grant, by its index
stado registry set --path targets.<host>.privacy_grants.0.reason --value 'backs up ~/Documents hourly'

# remove every grant
stado registry set --path targets.<host>.privacy_grants --value '[]'

stado registry pull --path targets.<host>.privacy_grants on a target that has never declared one is refused with the keys the target does carry. Declaring a grant changes nothing on the Mac: it says what the host's programs need, and stado host privacy then fails while macOS still withholds a declared folder.

The registry refuses a malformed grant by its path before any command reads it: registry.targets[<n>].privacy_grants[<i>].folder: must be one of ['documents', 'desktop', 'downloads'], ….program: must be an executable's absolute path or a path under ~/, ….reason: must say why the program needs the folder, an unknown field, the same program and folder twice (declares <program> on <folder> a second time), and a grant on a Linux host (is allowed only on a darwin release_platform). registry set runs that validation before it writes, so a refused list leaves the registry as it was.

What the beacon carries

Every beacon stado host beacon collect builds carries a privacy block: the measuring program and, for documents, desktop and downloads, one of

State Meaning
granted The program listed the folder.
denied The kernel answered EPERM: macOS's privacy controls withhold the folder from this program.
absent The folder does not exist.
unreadable Another operating-system error, carried verbatim in detail.

macOS attributes an access to the app a command was started from. The beacon the host process publishes — the --health-interval-seconds role of com.wisent.stado — is therefore the background process's answer. A stado host beacon collect typed in a terminal measures that terminal's grant instead.

stado host health TARGET prints the measurement on one privacy: line.

Read it

stado host privacy my-laptop
stado host privacy my-laptop --json

The command reads TARGET's latest beacon and prints the program, the time it was measured and each folder's state, with the operating system's own error for a folder that is not granted, and then one declared: line per grant: the program's path on that host, the folder, its state and the declared reason. A host with no grants prints declared: none (targets.<target>.privacy_grants). A grant for a program other than the Stado process that published the beacon is not measured: macOS answers only the program asking.

--json prints host, reported_at, privacy, grants (each with program, folder, reason, path, state and refused) and settings_url. state is one of granted, absent, denied, unreadable or not measured; refused is true for denied and unreadable, the reads that failed for a reason other than absence. Stado Desktop's privacy gate shows the same field.

It exits non-zero when a declared grant is refused, with macOS denies declared grants on <host>: <path> <Folder> (declared: <reason>); allow each in System Settings → Privacy & Security → Files and Folders (or add the program to Full Disk Access), which stado host privacy --open opens on <host>. A folder that no grant declares is reported with its state and fails nothing; an absent folder is no denial. A beacon without the block is refused with <host>'s latest beacon (reported <time>) carries no privacy measurement: the Stado publishing it predates the measurement; install a current Stado on <host>. A beacon whose measurement cannot be read is refused rather than shown as a state nobody measured: one that names no host (<target>'s latest beacon names no host, so its privacy measurement cannot be attributed), one whose measurement names no Documents path (… names no Documents path, so the home its declared programs live in is not known), and a folder entry with no state this Stado reads (the beacon's privacy entry for <folder> carries no state this Stado reads (<why>)). A program the measuring process could not name prints as not named (<the operating system's reason>), and then every grant is not measured.

Change it

Only the person at that Mac can allow or withdraw a folder. --open opens System Settings → Privacy & Security → Files and Folders on the machine running the command:

stado host privacy my-laptop --open

For another host it is refused with --open opens System Settings on the machine running this command, and <host> is another machine; run stado host privacy --open on <host>. Full Disk Access accepts a program added with its + button (Cmd-Shift-G, then the program's path, such as ~/.stado/bin/stado). tccutil reset SERVICE BUNDLE_ID only clears decisions, and it addresses an app by bundle identifier, not a program by its path; without an identifier it clears that service for every app on the Mac.

--open runs /usr/bin/open with the settings address. When that program cannot start, the refusal is /usr/bin/open <address> could not start: <operating-system error>, classed by the error's kind (not_found for a missing program); when it runs and fails, /usr/bin/open <address> exited <status>: macOS did not open Privacy & Security → Files and Folders, classed refused.

A stable code identity keeps the decision across updates: see Native macOS signatures. An operating-system refusal in any Stado command is reported as unclassified with its detail, naming the refused program and this setting, never as a rejected credential.

Stado Desktop

The Hosts inspector shows Folders macOS lets Stado read for each host from the same command: the program, the measurement time, each folder's state, each declared grant with its path, state and reason (a refused grant in red), the refusal sentence when a declared grant is denied, and Open Files and Folders, which runs stado host privacy HOST --open through the selected Stado API host and is the primary action while a declared grant is refused.

Under the measurement the same box lists the declared grants with Edit and Remove, and a form (program, folder, reason) whose Declare grant adds one and Save change writes an edited one. The exact stado registry set --path targets.HOST.privacy_grants --value … command the button runs is shown under the form before it runs, with the whole new list; the registry's refusal of a malformed grant or of a registry that moved since it was read appears below the form as the command wrote it, and a written change reads the host's privacy again.

Source: this website