Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Allowlists for agent commands and paths

After this lesson: I can say exactly which commands and paths an agent may use.

60 min lesson6 commandsAI coding agent lab: use opencode to explain and fix code, and check its work

Only what is on the list

An allowlist is a list of exactly which commands and paths an agent may use. Anything not on the list is refused.

That matters because an agent that can run commands can run the wrong one. A list of what is allowed is short and can be checked. A list of what is banned is never finished.

By the end of this lesson

Note

I can say exactly which commands and paths an agent may use.

Key words

WordWhat it meansExample
Allowlista list of what is allowed, and nothing else
Policythe written rules for one agent
Argumenta word given to a commandsrc/app.js
Flagan argument that starts with a dash-l
Refusalan answer that says no and gives the reason

The cells students fill in are left blank in this preview.

A policy says what is allowed

For each command, the policy lists the paths and flags it may use. A request has a command and some arguments.

Check the command first: is it on the list? Then take the arguments in order. Is a flag on its list? Does a path start with an allowed folder? The first failure is the answer.

Worked example: five requests

RequestAnswerWhy
ls -l src/AllowedCommand, flag and path are all listed
rm -rf src/Refusedrm is not on the list
cat /etc/passwdRefusedThe path starts with none of the folders
grep -r src/app.jsRefusedThe flag -r is not listed for grep
cat src/notes.txtAllowedThe path starts with src/

The cells students fill in are left blank in this preview.

A common mistake

Warning

Many people think a path that starts with src/ is safe. It is not. The path src/../.env starts with src/ and points outside it. A prefix check is only a first step. Lesson 2 checks where a path really goes.

A refusal says why

A refusal that only says no leaves the agent to guess. It may try again with a worse idea.

Say which part failed: the command, the flag or the path. The agent can then try a command that is allowed, and a person reading the log can see what went wrong.

Write the model helper, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// The token counts of a reply, or null when the reply carries none.' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the model helper, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    const data = await res.json();' '    const text = data.choices[0].message.content;' '    return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"Recorded sample: no model answered (\" + error.message + \").\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the allowlist, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const policy = {' '  ls: { paths: [\"src/\", \"docs/\"], flags: [\"-l\"] },' '  cat: { paths: [\"src/\", \"docs/\"], flags: [] },' '  grep: { paths: [\"src/\"], flags: [\"-n\", \"-i\"] },' '};' > allow.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the allowlist, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function decide(req) {' '  const rule = policy[req.cmd];' '  if (rule === undefined) return \"refused: command not on the list\";' '  for (const a of req.args) {' '    if (a.startsWith(\"-\")) {' '      if (rule.flags.indexOf(a) < 0) return \"refused: flag \" + a + \" not allowed\";' '    } else if (rule.paths.some(function (p) { return a.startsWith(p); }) === false) {' '      return \"refused: path \" + a + \" not allowed\";' '    }' '  }' '  return \"allowed\";' '}' >> allow.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the allowlist, part 3

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const requests = [' '  { cmd: \"ls\", args: [\"-l\", \"src/\"] },' '  { cmd: \"rm\", args: [\"-rf\", \"src/\"] },' '  { cmd: \"cat\", args: [\"/etc/passwd\"] },' '  { cmd: \"grep\", args: [\"-r\", \"src/app.js\"] },' '  { cmd: \"cat\", args: [\"src/../.env\"] },' '];' 'async function main() {' '  requests.forEach(function (r) { console.log(r.cmd + \" \" + r.args.join(\" \") + \" -> \" + decide(r)); });' '  const q = \"In one short sentence, why is a list of allowed commands safer than a list of banned ones?\";' '  const sample = \"You cannot think of every bad command in advance, but you can list the few good ones.\";' '  const reply = await ask([{ role: \"user\", content: q }], sample, 60);' '  console.log(\"model said: \" + reply.text);' '}' 'main();' >> allow.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run the allowlist

Command
docker exec -w /home/node/project agent node allow.js

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

What the Run prints

LineWhat it shows
ls -l src/ -> allowedCommand, flag and path are listed
rm -rf src/ -> refused: command not on the listThe first check failed
cat /etc/passwd -> refused: path ...The path is outside the folders
grep -r src/app.js -> refused: flag -r ...The flag is not listed
cat src/../.env -> allowedThe gap: a prefix check is fooled
model said: ...A sentence. The words differ each run

The cells students fill in are left blank in this preview.

About this lab and its source: AI coding agent lab: use opencode to explain and fix code, and check its work

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.