Newly blocked
The guard now decides from the paths a command touches and what it does with each one (read, write, list, use, enter, name), inferred under the command semantics that the program table in src/programs.ts models. Before, it decided from lists of command shapes. The changes users can see are below.
- Programs the table does not model read every path they are handed.
aws s3 cp .env s3://bucket/x,python3 script.py .env,cmp -l x .env,open .env, andfind . -name .env -exec python3 -c x {} \;are denied. A file that a client consumes itself is allowed through the client's own option:--env-file,--kubeconfig,ssh -i/-F,scp -i/-F,sftp -i/-F,ssh-add,ssh-keygen -f,dotenvx -f,npm --userconfig,curl --cacert/--cert/--key. The guard does not control what such a client does with the contents. - Inline interpreter code. Each token of
python3 -c,node -e,ruby -e,perl -e,deno eval, or a here-document on an interpreter's standard input that resolves to a credential file or a directory holding one is a read:python3 -c "print(open('.env').read())"andpython3 -c 'x = ".env"'are denied with a new reason that tells the agent to write the file with the Write or Edit tool or hand it to the runtime's own option. The old scan for a reader program named in the code is gone, sonode -e "... cat ~/.ssh/config"is allowed. - Directories as targets.
find -name xandfind -type fwith no path scan the working directory, so they are denied at~;cat < ~/.aws,curl -T ~/.ssh,git log -p ~/.ssh, anddd if=x of=~/.sshare denied because the directory itself is the target;fd x ~/.aws -x catis denied without-H. Inside an App Data container, a program that names no path (pwd,make,python3 -c) or that the table does not model is denied, whilels /tmpstays allowed. dd if=andof=. Both values are resolved, sodd if=~/Library/Containers/xand anof=naming a private key under~/.sshare denied.- Option values that name a file the client reads, sends, or writes.
- curl:
-w @FILE,--proxy-header @FILE,--etag-compare,-b/--cookiewith a file,--pubkey,--pinnedpubkey,--proxy-pinnedpubkey,--unix-socket, and the output files of-c/--cookie-jar,--etag-save,--libcurl,--stderr,--hsts,--alt-svc,--trace,--trace-ascii, and--ssl-sessions(-is standard output). - wget:
--ca-certificate,--ca-directory,--certificate,--private-key,--crl-file,--random-file. - docker:
--file,--label-file,--cidfile,--iidfile,--tlscacert,--tlscert,--tlskey,--config,--env-file, and-fofbuildandcompose, in the spellings-f=PATHand-qf PATH;cpoperands,load -i,--secret src=,-v, and--mount. - ssh, scp, and sftp:
-iand-F(and-Efor ssh), sftp's-bbatch file, and theIdentityFile,CertificateFile,GlobalKnownHostsFile,UserKnownHostsFile,RevokedHostKeys, andPKCS11Providersettings of-o, in the spellings-oKEY=VALUE,-o "KEY VALUE", quoted values, several files,%d, and${HOME}. The cluster letters come from each client's synopsis in OpenSSH 10.3p1; the ssh command after the destination is not scanned. - A value glued to a short option of a program the table models is a path:
ssh -idata,ssh-keygen -fdata,curl -Edata,dotenvx -fdata. @pathand httpiefield=@pathread the file;-t DIRmakes every operand a source; git's--pathspec-from-file,-Foftagandmerge, a glued--work-tree=, and operands after the last-C;git bundle createwrites its file; tar's implicit extraction target and-O;curl -O,--output-dir, wget's download into the working directory, and wgetrc commands through-e.- A client that consumes the file itself (
ssh -i,docker run --env-file,node --env-file) is allowed unless the file is App Data.
- curl:
- Paths spelled through a firmlink or a link.
/System/Volumes/Datain any case, including..from a directory that is also reached from the root, is judged as the plain path, which removes that entry from the known limits. A link whose text leads into App Data or~/.sshis judged by where it leads, and a home directory that is itself spelled through a link (/tmp/...) is compared by the spelling the link walk reports. - The guard's own probes stay out of App Data. A glob operand behind a link into App Data, such as
cache/*.txt, used to reachstat, so the kernel searched the tree the guard exists to protect. Link traversal now usesreadlinkalone and follows only the part of a glob before its first wildcard, and the~/.sshinode comparison runs only for a target already in~/.sshscope. Areadlinkorstatfailure other than "not a link" or "does not exist" is a denial.
Newly allowed
- Writing a credential file through a command's destination.
cp dotfiles/config.json ~/.docker/config.json,cp .env.example .env,tee .env < x,tar -cf .env.tar src,curl -o .env URL, andwget -O .env URLare allowed. A private key under~/.sshis still denied (cp x ~/.ssh/id_rsa,curl -o ~/.ssh/id_rsa URL,install -m 600 x ~/.ssh/id_rsa). - Names are not paths.
--exclude,--exclude-dir,--include, curl and wget operands, ssh operands, git refs, remotes, and names (git branch .env), the first operand ofyq, and ajqfilter are not judged as files.wget URL/.envis allowed. - Words after a container or a remote host.
docker run img cat --file X,docker exec web cat -v X, andssh host grep -E error app.logbelong to the command run inside, not to docker or ssh.docker compose logs -f SERVICEfollows the log, andcurl --stderr -,curl -w @-, andcurl -b name=valuename no file. - Reasons that change. A copy sends what it reads only when an operand names another machine (
host:,user@host:,rsync://);rsync -a ~/.aws/ backup/reportsfileinstead ofupload, whilescpandrsyncto a host keepupload.wget --post-fileand--body-filereportupload.jq . .envandtar -czf x.tgz -C ~/.aws .reportfile.curl -d @~/xkeeps the~literal, while@$HOME/xis denied.
Not covered
The limits are listed in the "Safety model and limits" section of docs/setup.md, grouped as observation coverage, execution semantics, and state and resource identity. New in this release: docker's build context and its --build-context, --cache-from, --cache-to, --output, --ssh, --metadata-file, and --security-opt values, the words docker compose run and compose exec pass to the container command, the other file settings of ssh -o, git's clone --reference, --template, --separate-git-dir, and worktree add, and a - that a client reads as standard input in a sensitive working directory. The guard is a bounded preflight check, not a sandbox, and exit code 0 means only that it found no objection to the targets it inferred.
Release pipeline
The publish job skips a version the registry already holds and reuses an existing GitHub Release, so re-running the job reaches the tap dispatch instead of failing before it; the tap is dispatched when a version is published, and its token is minted from the app's client id.