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
| Word | What it means | Example |
|---|---|---|
| Allowlist | a list of what is allowed, and nothing else | |
| Policy | the written rules for one agent | |
| Argument | a word given to a command | src/app.js |
| Flag | an argument that starts with a dash | -l |
| Refusal | an 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
| Request | Answer | Why |
|---|---|---|
| ls -l src/ | Allowed | Command, flag and path are all listed |
| rm -rf src/ | Refused | rm is not on the list |
| cat /etc/passwd | Refused | The path starts with none of the folders |
| grep -r src/app.js | Refused | The flag -r is not listed for grep |
| cat src/notes.txt | Allowed | The 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
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
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
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
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
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
docker exec -w /home/node/project agent node allow.jsShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
What the Run prints
| Line | What it shows |
|---|---|
| ls -l src/ -> allowed | Command, flag and path are listed |
| rm -rf src/ -> refused: command not on the list | The 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 -> allowed | The 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.