Rules / CFG023
CFG023 permissions.allow grants a dangerous command with open-ended args
Severity: error (network / privilege / remote-pkg / shell) · warn (interpreters, exec-via-flags, lateral movement)
OWASP: LLM03:2026 – Excessive Agency
What cfgaudit checks
cfgaudit inspects every permissions.allow entry of the form Bash(<command> …*…) — i.e. one that grants the command with open-ended (wildcard) arguments — and flags binaries that hand far more capability than the entry appears to. CFG001 already covers the catch-all Bash(*); this rule covers the specific commands below.
A finding fires only when the entry contains a wildcard. An exactly-pinned command such as Bash(git status) or Bash(npm test) is exempt — the user spelled out precisely what may run.
| Category | Severity | Binaries | Why |
|---|---|---|---|
| Network egress | error |
curl, wget |
exfiltrate data / fetch and run remote payloads |
| Privilege escalation | error |
sudo, doas |
run as root |
| Remote package exec | error |
npx, bunx |
run arbitrary packages from a registry |
| Shell interpreter | error |
bash, sh, dash, zsh, ksh, csh, tcsh, fish, powershell, pwsh, cmd |
open-ended args = arbitrary command execution |
| Windows LOLBins | error |
certutil, bitsadmin, mshta, regsvr32, rundll32 |
living-off-the-land binaries used to download and execute remote code |
| Language interpreter | warn |
python, python3, perl, ruby, node, deno |
open-ended args can execute arbitrary code |
| Exec-via-flags | warn |
find, sed, awk, gawk, xargs, env, tar |
find -exec, GNU sed e///, awk system(), env/xargs <cmd>, tar --checkpoint-action all run arbitrary commands |
| Exec-via-flags, per subcommand | warn |
git |
git -c core.pager=…, plus the subcommand vectors listed below |
| Lateral movement | warn |
ssh, scp, rsync |
remote command execution / data movement to other hosts |
Standard build/dev tooling (npm, pip, docker, make, go, cargo, yarn, …) is not flagged: allow-listing those with arguments is normal, deliberate practice.
git is judged per subcommand
git's headline exec vector sits in the flags it takes before the subcommand (git -c core.pager=<cmd> log, git -c alias.x=!<cmd>). An entry that leaves that position open is always flagged: the unscoped Bash(git *) / Bash(git:*), and any entry that already spells a flag there (Bash(git -c core.pager=x status:*)).
An entry that pins a subcommand puts those flags out of reach, so it is flagged only when the pinned subcommand runs a command through its own arguments:
| Pinned subcommand | Vector |
|---|---|
git rebase:* |
git rebase -x <cmd> runs a command per commit |
git bisect:* |
git bisect run <cmd> runs a command per revision |
git submodule:* |
git submodule foreach <cmd> runs a command per submodule |
git filter-branch:* |
--tree-filter <cmd> runs a command per commit |
git difftool:* |
--extcmd=<cmd> runs a command per changed file |
git grep:* |
-O<cmd> runs a command over the matching files |
git clone:* |
--upload-pack=<cmd> runs a command; --template=<dir> installs hooks that run during the clone |
git fetch:*, git pull:*, git ls-remote:* |
--upload-pack=<cmd> runs a command |
git push:* |
--receive-pack=<cmd> runs a command |
git archive:* |
--remote=<repo> --exec=<cmd> runs a command |
git config:* |
writes alias.x=!<cmd> or core.pager=<cmd>, which a later git run executes |
git daemon:* |
--access-hook=<cmd> runs a command per request |
git instaweb:* |
--httpd=<cmd> runs a command |
git send-email:* |
--sendmail-cmd=<cmd> runs a command |
Every other pinned subcommand is exempt, including the ones that write to the repository: Bash(git add:*), git commit, git branch, git checkout, git tag, git stash, git merge, as well as read-only ones such as git status, git diff, git log, git rev-parse, git show, git blame. The question the rule asks is whether the grant reaches arbitrary command execution, not whether it writes to the repository.
Two vectors that look like they belong are deliberately absent because they do not hold on current git: git mergetool has no --extcmd (only git difftool does), and an ext::<cmd> remote URL is refused by default (fatal: transport 'ext' not allowed), which would otherwise make every remote-taking subcommand exec-capable.
Trigger example
{
"permissions": {
"allow": ["Bash(curl *)", "Bash(find *)", "Bash(git rebase:*)"]
}
}
Bash(curl *) → error (unrestricted network). Bash(find *) → warn (find -exec runs anything). Bash(git rebase:*) → warn (git rebase -x <cmd> runs a command per commit), while Bash(git add:*) in the same file is not reported.
Safe alternative
Pin the exact command and arguments you actually need, with no wildcard:
{ "permissions": { "allow": ["Bash(git status)", "Bash(curl https://api.internal.example/health)"] } }
References
- Adversa AI denylist-bypass research (April 2026)
- Claude Code permissions documentation
- CFG001:
permissions.allowcontains the catch-allBash(*)
Documentation for cfgaudit 1.18.1. Source of this page · Rule implementation