Skip to content

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 29 Sep 00:09
· 14 commits to main since this release
v0.3.0
62ba787

Newly blocked

Every behavior fixture that 0.2.0 already contained still passes except one: printf '%s\n' ~/.npmrc | xargs cat was allowed and is now denied. Everything else below is a new fixture row. The rest of the range since 0.2.0 (exact dependency pinning, Renovate's commit prefix, the release skill) has no runtime effect.

  • Shell functions and pipelines move the directory. A function call runs its body in the caller's shell with the caller's current directory, so f() { cd ~/Library; }; f; cat Containers/… and f() { find .; }; cd ~; f are denied. The last element of a pipeline may run in the current shell (zsh does this), so true | cd ~/Library; cat Containers/… is denied. A chain of function calls that multiplies past 256 body runs is denied with the syntax reason instead of being inspected.
  • The current directory spelled out. $PWD, ${PWD}, $(pwd), `pwd` and ~+ expand to the directory the command was read in, including the old directory when a cd before it may have failed. cd -P, cd -L, cd -q, and cd -s with no directory go home, so cd -P && find . is a scan of the home directory.
  • Wildcards in redirect targets. cat < ~/Library/Cont*/… and wc -c < .en* are denied because the shell expands the target before the program reads it.
  • Brace sequences. cat ~/Library/{C..C}ontainers/… and cat ~/.e{n..n}v are denied. The guard reads {a..z} and {1..3} as a wildcard, because Bun's brace expansion handles only {a,b}.
  • Option values that name a file. node --env-file=$HOME/Library/Containers/… and git --work-tree=$HOME/Library/Containers status are denied because a value glued to its option with = is a path. rg --ignore-file PATH and grep --exclude-from PATH are denied when PATH is under App Data, because the program opens that file.
  • Git reads of credential files. git show HEAD:.env, git show :.env, git cat-file -p HEAD:.env, git diff -- .env, git diff --no-index /dev/null .env, git log -p -- .env, git grep TOKEN .env, git grep -f .env, and git grep … -- '*.pem' are denied. The guard checks the operands of show, diff, log, cat-file, blame, annotate, grep, archive, format-patch, whatchanged, difftool, diff-index, and diff-tree, and the path after a rev: prefix; for git grep the pattern is not an operand. git credential fill is denied. git add .env, git grep .env, and git show HEAD:.env.example stay allowed.
  • egrep and fgrep. They follow the grep rules, including recursive searches.
  • find -exec and fd -x with a reader. find … -exec cat {} + and the -execdir, -ok, and -okdir forms are denied whenever the program is a file reader, a search tool, a shell, or a wrapper, whatever -name or -type filters come first and however many -exec clauses there are, because find reaches hidden files and a filter such as -name '*.json' still reaches ~/.docker/config.json. find … | xargs cat, fd -H … | xargs cat, ls -a | xargs cat, ls .env | xargs cat, and echo .* | xargs cat are denied for the same reason: the names come from a walk that reaches dotfiles, an ls that lists them, or a dotfile glob. Names another command prints into xargs, as in ls *.pem | xargs cat or fd -e pem | xargs cat, are not checked. find src -name '*.ts' -exec grep -l TODO {} \; is therefore denied too; use rg -l TODO src -g '*.ts'. An interpreter such as python3 is not on that list. fd -x and -X are denied when -H, --hidden, or -u is present; without them fd skips hidden files and stays allowed.
  • More wrappers and readers. sudo, doas, and arch are wrappers, so sudo cat .env and sudo bash -c "cat .env" are inspected. script is a wrapper. tac, column, pr, vim, vi, nvim, view, ed, ex, hg, svn, perl, ruby, dd if=, zip, and the shells sh, bash, zsh, dash, and ksh are readers, because sh -v on a credential file prints it. tcsh -c and csh -c are parsed like bash -c, and a literal echo or printf piped into a shell is parsed as the command line it prints, so echo 'cmd' | sh, printf 'cat %s\n' FILE | sh, and printf '%s %s' cat FILE | sh are inspected; escapes such as \n, \xHH, \uHHHH, and octal \NNN are decoded and \c ends the text, and a printf conversion other than %s, %b, and %% is not expanded. Csh syntax is parsed as bash, so a valid tcsh -c 'if ( -f x ) echo y' is denied. scp and rsync are denied with the upload reason when they send a credential file. xargs -a FILE is checked as a read of FILE, and a literal echo or printf value piped into xargs, or a here-string or here-document given to it, is checked as the argument it becomes, with or without -I. wget, php, zgrep, zless, and zmore are readers too, and the operands and --flag=PATH values of gh are checked, so gh gist create .env and wget --post-file=.env URL are denied. |& is treated like | for xargs and shell input. tar --exclude=.env is allowed: an exclude pattern is not a file the command reads.
  • Commands that print a secret. security dump-keychain, security export, and combined flags such as security find-generic-password -ws NAME are denied with the keychain reason. gcloud auth print-access-token and print-identity-token, az account get-access-token, aws configure get of a name that holds SECRET, TOKEN, KEY, PASSWORD, or CREDENTIAL, npm config get of an auth, token, or password key, kubectl config view --raw, and gpg --export-secret-keys are denied with a new reason that tells the agent to use the credential without printing it. The list is not exhaustive: a command that prints a secret through another subcommand is allowed.
  • More credential files. ~/.netrc, ~/.git-credentials, ~/.docker/config.json, ~/.kube/config, ~/.pypirc, ~/.pgpass, ~/.cargo/credentials*, and ~/.config/gh/hosts.yml are listed. A search rooted at a directory that holds such a file, or at ~/.config above ~/.config/gh, is denied, and so is a search with no path operand run from such a directory (cd ~/.docker && rg auths). tar, zip, scp, rsync, and git grep handed such a directory are denied as well (tar -cf - ~/.aws, scp -r ~/.aws host:/tmp), but the last operand of cp, rsync, and scp and the directory after tar -C are written to, not read, so rsync -a ~/dotfiles/.config/ ~/.config/ and tar -xzf a.tgz -C ~/.config stay allowed. Inside a named directory, a glob that could match a listed name counts, so cat ~/.cargo/*.toml and cat ~/.cargo/credential[s].toml are denied. Otherwise a glob matches such a name only when its directory segment matches too, so cat conf* and cat *.json in a project stay allowed, while a bare wildcard in the directory that holds a listed file, as in cat ~/.docker/* or cat ~/.config/gh/*, is denied. This also narrows the older .aws/credentials* entry: cat cred* outside ~/.aws is no longer denied.
  • curl file operands through a link. curl -d @link, --data-binary=@link, -F f=@link, -Tlink, --upload-file=link, and --config=link resolve the link before the upload check.

Not covered: process substitution as input, such as xargs cat < <(echo …), a wrapper the guard does not list, such as xcrun sh, and a read whose target the program picks while it runs, such as an interpreter opening a file itself, git diff without a path operand, fd -x over credential names outside hidden files, and a path built by command substitution, held in a variable, or reached through a file moved earlier. Only the operating system's read restrictions on the credential stores cover those. A command that prints a secret through a subcommand not listed above is allowed, because it has no path for the guard to check. A program that is not a file reader is allowed even when it is handed a credential path, as in aws s3 cp .env s3://bucket/x and rclone copy .env remote:.