一覧に載るものだけ
エージェントが使えるコマンドとパスを、一つずつ書き出したものが許可リストです。載っていないものは、すべて断ります。
コマンドを動かせるエージェントは、間違ったコマンドも動かせます。許可するものの一覧は短く、確かめられます。禁止するものの一覧は、いつまでも終わりません。
このレッスンのゴール
メモ
エージェントが使えるコマンドとパスを、はっきり決めることができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 許可リスト | 許すものだけを書いた一覧。ほかはすべて断る | |
| ポリシー | 一つのエージェントに決めた、書かれたルール | |
| 引数 | コマンドに渡す言葉 | src/app.js |
| フラグ | ハイフンで始まる引数 | -l |
| 拒否 | 断ることと、その理由を伝える答え |
学生が入力するセルは、このプレビューでは空白です。
ポリシーは許すものを書く
ポリシーは、コマンドごとに、使えるパスとフラグを書きます。要求は、コマンドと引数でできています。
まずコマンドを確かめます。コマンドは一覧にありますか。次に、引数を順に見ます。フラグは一覧にありますか。パスは許されたフォルダで始まりますか。最初に失敗したところが答えです。
例: 五つの要求
| 要求 | 答え | 理由 |
|---|---|---|
| ls -l src/ | 許可 | コマンドもフラグもパスも一覧にある |
| rm -rf src/ | 拒否 | rm は一覧にない |
| cat /etc/passwd | 拒否 | どのフォルダでも始まっていない |
| grep -r src/app.js | 拒否 | grep に -r は許されていない |
| cat src/notes.txt | 許可 | パスが src/ で始まる |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
許可されたフォルダで始まるパスは安全だ、と考える人が多いです。そうではありません。src/../.env は src/ で始まりますが、外を指します。前方一致の確認は、最初の一歩にすぎません。レッスン 2 で、パスが本当に行き着く先を確かめます。
拒否は理由を言う
「だめ」だけの拒否では、エージェントは当てずっぽうで試します。もっと悪い手を試すかもしれません。
どこで失敗したかを言いましょう。コマンドか、フラグか、パスかです。エージェントは許されたコマンドを試せますし、ログを読む人も何が起きたか分かります。
助っ人 model.js を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答のトークン数。なければ null。' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
助っ人 model.js を書く(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(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
許可リストを書く(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"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
許可リストを書く(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 \"拒否: コマンドが一覧にありません\";' ' for (const a of req.args) {' ' if (a.startsWith(\"-\")) {' ' if (rule.flags.indexOf(a) < 0) return \"拒否: フラグ \" + a + \" は許されていません\";' ' } else if (rule.paths.some(function (p) { return a.startsWith(p); }) === false) {' ' return \"拒否: パス \" + a + \" は許されていません\";' ' }' ' }' ' return \"許可\";' '}' >> allow.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
許可リストを書く(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 = \"短く一文で答えてください。禁止するコマンドの一覧より、許可するコマンドの一覧のほうが安全なのはなぜですか。\";' ' const sample = \"危ないコマンドをすべて先に思いつくことはできませんが、安全なコマンドは少数なので一覧にできるからです。\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 80);' ' console.log(\"モデル: \" + reply.text);' '}' 'main();' >> allow.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
許可リストを動かす
docker exec -w /home/node/project agent node allow.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
Run の出力
| 行 | 意味 |
|---|---|
| ls -l src/ -> 許可 | コマンドもフラグもパスも一覧にある |
| rm -rf src/ -> 拒否: コマンドが一覧にありません | 最初の確認で失敗した |
| cat /etc/passwd -> 拒否: パス ... | パスがフォルダの外にある |
| grep -r src/app.js -> 拒否: フラグ -r ... | フラグが一覧にない |
| cat src/../.env -> 許可 | 穴: 前方一致はだまされる |
| モデル: ... | 一文。言葉は毎回変わる |
学生が入力するセルは、このプレビューでは空白です。