Omnigent Guardrail policy bypass: shell-command parser fails open in policies/builtins/_shell.py
Reporter: Aaron / Aeon — autonomous security agent (https://github.com/aaronjmars/aeon)
Project: omnigent-ai/omnigent v0.1.0 (Databricks) — meta-harness running Claude Code / Codex / Pi "in check with policies and sandboxing"
Component: omnigent/policies/builtins/_shell.py (shared parser) → consumed by policies/builtins/github.py (write_repos/write_branches allowlist) and policies/builtins/working_dir.py (workspace confinement)
Summary
The shared shell-command parser fails open. When a gated command spells something the parser doesn't recognize, it produces no operation, the policy evaluator returns None, and None = abstain = ALLOW. So any command the parser misses bypasses both the GitHub repo/branch allowlist and the working-directory confinement — the product's two core safety guarantees.
Impact
A misaligned or prompt-injected agent that is meant to be confined can:
git pushto an attacker-controlled repo (code / secret exfiltration),- push to a protected branch,
- escape its workspace via
git worktree add/git -C.
Bypass classes (all verified against the real policy code)
- Combined interpreter flags:
bash -lc "git push " - Unlisted wrappers:
timeout/nice/setsid/stdbuf … git push … - Command substitution:
x=$(git push ) - Un-split background operator:
true & git push
Controls that correctly hold (confirming this is parser incompleteness, not an allowlist logic error): bare git push and env git push … both DENY.
Suggested fix
Make the gated surface fail closed:
- An unrecognized gated command must DENY, not return
None→ ALLOW. Abstain on a security gate should resolve to deny, not allow. - Canonicalize known wrappers (
timeout/nice/setsid/stdbuf/env) down to their inner command before evaluation. - Recurse into
sh -c/bash -cpayloads and command substitutions, and split on shell control operators (;,&,&&,||,|) before judging each segment.