# A Parsed Allowlist — AI Coding-Agent Guardrails

Source: https://www.geekswithgeeks.com/en/coding-agent-guardrails/perm-parsed-allowlist

> Parse commands into arguments, reject shell metacharacters and match exact command prefixes.

## Parse, then match

A stronger approach is to first **reject** any line containing shell metacharacters (`; & | ` $ < > \` and newlines), then split the rest with a proper parser (`shlex` in Python) into an argument list, and check that the list starts with an **approved command**. Unknown or complex lines go to "ask" instead of running. This is deliberately conservative: it is better to ask a human about a harmless command than to allow a harmful one.

## The parsed check, run

I ran this. The chained and substituted commands are rejected, safe ones pass, and `npm install evil` is not on the list.

```python
import shlex, re
SAFE = {("git", "status"), ("git", "diff"), ("git", "log"), ("npm", "test"), ("ls",)}
META = re.compile(r"[;&|`$<>\n\\]")

def allowed(cmd):
    if META.search(cmd): return False
    try: argv = shlex.split(cmd)
    except ValueError: return False
    return any(tuple(argv[:len(p)]) == p for p in SAFE)

for c in ("git status", "git diff --stat", "git status; rm -rf /tmp/x",
          "echo $(whoami)", "npm test", "npm install evil", "ls -la"):
    print(allowed(c), repr(c))
```

Output:

```
True 'git status'
True 'git diff --stat'
False 'git status; rm -rf /tmp/x'
False 'echo $(whoami)'
True 'npm test'
False 'npm install evil'
True 'ls -la'
```

## Match arguments for risky tools

Some commands are safe or dangerous depending on flags. `git push` may be fine while `git push --force` is not. Write rules at the argument level for such tools, not just the program name.

**Quiz:** In the parsed allowlist, what happens to a line containing `$(...)`?

- [x] It is rejected because of the metacharacter
- [ ] It runs with extra logging
- [ ] It is rewritten by the shell
- [ ] It is always allowed

*Answer:* It is rejected because of the metacharacter. Metacharacters are rejected up front, so command substitution cannot hide a second command.
