Check an agent's shell command against an allowlist

Agent buildMedium25 min

Your task

Your coding agent may run shell commands, but only ones on a short allowlist. A line from the model can carry a second command, so a guard reads the whole line before anything runs. The guard follows the small part of the shell's rules below.

Write allowed(command, allowlist) that returns True when the line may run and False when it may not.

Input and output:

  • command: the line, up to 100,000 characters of printable ASCII, tabs and newlines.
  • allowlist: up to 50 command names. None is empty, and they are compared exactly, case included.
  • Return a boolean.

Rules:

  • Outside quotes, each of ;, &, | and newline ends one command and starts the next. A command with no words is fine.
  • Outside quotes, spaces and tabs separate words. Every other character is part of a word, including >, (, = and $.
  • The first word of every command must be on the allowlist, read after quotes and escaping backslashes come off: "rm", r'm' and r\m are all rm. An empty pair of quotes makes an empty word.
  • Inside single quotes, every character is literal up to the next single quote.
  • Inside double quotes, every character is literal up to the next double quote, with these exceptions. A backslash before $, a backtick, " or \ is dropped and makes that character literal. Any other backslash stays as a backslash.
  • Outside quotes, a backslash is dropped and makes the next character literal.
  • Return False if the line has a $( or a backtick that is outside quotes or inside double quotes and is not made literal by a backslash.
  • Return False if the line has a <( or >( that is outside quotes and is not made literal by a backslash.
  • Return False if a quote never closes, or if a backslash outside single quotes is the last character or comes right before a newline.

Examples

allowed(command: str, allowlist: list[str]) -> bool
Example 1
Inputallowed("git status && ls -la | grep src", ["ls", "cat", "git", "grep"])
OutputTrue
Example 2
Inputallowed("ls; rm -rf /", ["ls", "cat", "git", "grep"])
OutputFalse
Example 3
Inputallowed("grep 'a;b|c' notes.txt", ["ls", "cat", "git", "grep"])
OutputTrue