Skip to content

v0.4.0

Latest

Choose a tag to compare

@github-actions github-actions released this 29 Sep 20:38
v0.4.0
26c85c5

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, and find . -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())" and python3 -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, so node -e "... cat ~/.ssh/config" is allowed.
  • Directories as targets. find -name x and find -type f with no path scan the working directory, so they are denied at ~; cat < ~/.aws, curl -T ~/.ssh, git log -p ~/.ssh, and dd if=x of=~/.ssh are denied because the directory itself is the target; fd x ~/.aws -x cat is 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, while ls /tmp stays allowed.
  • dd if= and of=. Both values are resolved, so dd if=~/Library/Containers/x and an of= naming a private key under ~/.ssh are denied.
  • Option values that name a file the client reads, sends, or writes.
    • curl: -w @FILE, --proxy-header @FILE, --etag-compare, -b/--cookie with 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 -f of build and compose, in the spellings -f=PATH and -qf PATH; cp operands, load -i, --secret src=, -v, and --mount.
    • ssh, scp, and sftp: -i and -F (and -E for ssh), sftp's -b batch file, and the IdentityFile, CertificateFile, GlobalKnownHostsFile, UserKnownHostsFile, RevokedHostKeys, and PKCS11Provider settings 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.
    • @path and httpie field=@path read the file; -t DIR makes every operand a source; git's --pathspec-from-file, -F of tag and merge, a glued --work-tree=, and operands after the last -C; git bundle create writes 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.
  • Paths spelled through a firmlink or a link. /System/Volumes/Data in 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 ~/.ssh is 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 reach stat, so the kernel searched the tree the guard exists to protect. Link traversal now uses readlink alone and follows only the part of a glob before its first wildcard, and the ~/.ssh inode comparison runs only for a target already in ~/.ssh scope. A readlink or stat failure 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, and wget -O .env URL are allowed. A private key under ~/.ssh is 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 of yq, and a jq filter are not judged as files. wget URL/.env is allowed.
  • Words after a container or a remote host. docker run img cat --file X, docker exec web cat -v X, and ssh host grep -E error app.log belong to the command run inside, not to docker or ssh. docker compose logs -f SERVICE follows the log, and curl --stderr -, curl -w @-, and curl -b name=value name 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/ reports file instead of upload, while scp and rsync to a host keep upload. wget --post-file and --body-file report upload. jq . .env and tar -czf x.tgz -C ~/.aws . report file. curl -d @~/x keeps the ~ literal, while @$HOME/x is 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.