sandbox-macos
Use when a Mac command is refused by confinement. Explains the five activity classes (research, project, build, offline, media), what each Seatbelt profile lets a command read, write and reach, why th
SKILL.mdiclone/skills/sandbox-macos/SKILL.md
GitHubConfinement on macOS — Seatbelt profiles
When to use
- A terminal command fails with
Operation not permitted, exit 126, orRefused by icloneOS confinement. - You need to know whether a command may write a path, reach the network, or run a binary before you run it.
On macOS, icloneOS runs every terminal command, execute_code and the file tools (read_file, write_file, patch
run through the terminal backend) inside a Seatbelt profile the app generates. The profile starts from deny by
default: anything it does not name is refused, so write_file outside the project is refused even when the
permission mode would allow it. MCP servers and the browser are not inside it — the permission mode governs those.
A network grant opens the internet only: localhost, 127.0.0.1, ::1 and this Mac's own addresses stay refused.
The five activity classes
| Class | Reads | Writes | Network |
|---|---|---|---|
| research | the OS, toolchains, the project, its scratch | its scratch only | only with a grant |
| project (default) | same | the project and its scratch | only with a grant |
| build | + package caches (npm, pip, cargo, go, gradle, maven, bun) | + those caches | only with a grant |
| offline | same as project | the project and its scratch | never |
| media | the project and one output folder | the output folder and its scratch | never |
- The project is the git root above the session's folder, fixed when the session starts. A
cdelsewhere never makes that folder writable. Your home,/,~/Libraryand the app's own state are never a project: the session then works in its scratch dir ($TMPDIRpoints there). - Plan mode keeps the project read-only. Ignore permissions skips the asks, never the confinement.
- The network opens only when the permission mode allows it or the owner granted it for this session, project or standing. An "Allow once" on a command does not open a socket in the middle of it.
Never, in any class or mode
~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/.netrc, ~/.npmrc, ~/.pypirc, ~/.docker, ~/.kube, the
keychains (~/Library/Keychains, /Library/Keychains) and their services, the app's own data, Hermes' .env,
auth.json and config.yaml; launchctl, osascript, open, security, sudo; other apps' processes; unix
sockets other than DNS.
Hard links into the project
Seatbelt judges a path, not an inode, so before every command the app scans the project and the session's scratch
for files that are hard links to inodes outside them, and refuses to start (confinement refused to start in …,
exit 126) until the owner removes them or allows them, one folder group at a time, in the Dock's Containers
window. A folder the scan cannot list, or whose contents it cannot stat (cannot scan …), is refused the same way
until its permissions are fixed; a link that appears while a command runs is recorded when it exits. In the Build
class alone, a link whose every other name is inside the pnpm store (~/Library/pnpm, ~/.local/share/pnpm,
$PNPM_HOME — a real directory, not a symlink, with pnpm's store/v<N> layout and never a denied folder) runs
without a gesture; each name counts once, so a file with any name elsewhere still asks. A store is never a project: a
command run from inside one gets its scratch folder instead. Package caches (npm, pip, cargo, pnpm…) are not scanned for hard links: a Build
command may read and fill them anyway.
When a command is refused
- Do not try another route to the same effect (a different binary, a copy, a symlink): the refusal is the answer.
- Say what you needed and why. If the network is the blocker, ask the owner for a network grant through the consent
door (
~/.iclone-os/bin/acp consent request webBrowsing --reason "…"). - If a path outside the project is needed, ask the owner to open that folder as the project instead.
The owner sees every confined command, every refusal and the rule that fired in the Dock's Containers window.