Lesson 7 / 25

A 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.

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.

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

  • 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.