cfgaudit

Rules / CFG025

CFG025 custom organisation policy (require-deny / forbid-allow)

Severity: error OWASP: LLM03:2026 – Excessive Agency

What cfgaudit checks

CFG025 enforces an organisation's own permission policy, declared in .cfgaudit.yml. Unlike the other rules (which encode universal CVE/hardening checks), CFG025 is inert until you configure a policy: section. It evaluates against the project's permissions.allow / permissions.deny.

# .cfgaudit.yml
policy:
  # Commands that MUST be blocked by permissions.deny.
  require-deny:
    - "Bash(git commit:*)"
    - "Bash(git push:*)"
  # Commands that MUST NOT be grantable by permissions.allow.
  forbid-allow:
    - "Bash(git commit:*)"
    - "Bash(curl:*)"

Containment-aware matching

Matching is not a plain string compare — broader patterns subsume narrower ones:

Policy target require-deny satisfied by a deny of forbid-allow violated by an allow of
Bash(git commit:*) Bash(git commit:*), Bash(git:*), Bash(*) Bash(git commit:*), Bash(git:*), Bash(*), or any Bash(git commit …)

So a narrower deny (Bash(git commit --amend)) does not satisfy require-deny: Bash(git commit:*), while any allow that grants even one git commit invocation violates forbid-allow.

Evaluated against the project settings.json (the committed artifact a CI policy applies to).

Example: "Claude may never run commit/push"

policy:
  require-deny: ["Bash(git commit:*)", "Bash(git push:*)"]
  forbid-allow: ["Bash(git commit:*)", "Bash(git push:*)"]

Limitation

Matching covers the common Claude Code permission shapes (Tool(cmd), Tool(cmd:*), Tool(cmd *), Tool(*)). Exotic glob forms inside the body are not interpreted.

References

Documentation for cfgaudit 1.18.1. Source of this page · Rule implementation