Solo lo que está en la lista
Una lista permitida dice con exactitud qué comandos y qué rutas puede usar un agente. Lo que no está en la lista se rechaza.
Importa porque un agente que ejecuta comandos puede ejecutar el equivocado. Una lista de lo permitido es corta y se puede revisar. Una lista de lo prohibido nunca se termina.
Al final de esta lección
Nota
Puedo decir con exactitud qué comandos y qué rutas puede usar un agente.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Lista permitida | una lista de lo que se permite, y nada más | |
| Política | las reglas escritas de un agente | |
| Argumento | una palabra que se da a un comando | src/app.js |
| Opción | un argumento que empieza por un guion | -l |
| Rechazo | una respuesta que dice no y da el motivo |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una política dice qué se permite
Para cada comando, la política lista las rutas y las opciones que puede usar. Una petición trae un comando y algunos argumentos.
Comprueba en orden. ¿Está el comando en la lista? ¿Está cada opción en su lista? ¿Empieza cada ruta por una carpeta permitida? El primer fallo es la respuesta.
Ejemplo resuelto: cinco peticiones
| Petición | Respuesta | Motivo |
|---|---|---|
| ls -l src/ | Permitida | Comando, opción y ruta están en la lista |
| rm -rf src/ | Rechazada | rm no está en la lista |
| cat /etc/passwd | Rechazada | La ruta no empieza por ninguna carpeta permitida |
| grep -r src/app.js | Rechazada | La opción -r no está permitida para grep |
| cat src/notas.txt | Permitida | La ruta empieza por src/ |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que una ruta que empieza por src/ es segura. No lo es. La ruta src/../.env empieza por src/ y apunta fuera. Comprobar el prefijo es solo un primer paso. La lección 2 comprueba adónde va de verdad una ruta.
Un rechazo dice por qué
Un rechazo que solo dice no obliga al agente a adivinar. Puede volver a intentarlo con una idea peor.
Di qué parte falló: el comando, la opción o la ruta. Así el agente puede probar un comando permitido, y quien lea el registro verá qué fue mal.
Escribe el ayudante del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el ayudante del modelo, parte 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(\"estado \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la lista permitida, parte 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"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la lista permitida, parte 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 \"rechazado: comando fuera de la lista\";' ' for (const a of req.args) {' ' if (a.startsWith(\"-\")) {' ' if (rule.flags.indexOf(a) < 0) return \"rechazado: opción \" + a + \" no permitida\";' ' } else if (rule.paths.some(function (p) { return a.startsWith(p); }) === false) {' ' return \"rechazado: ruta \" + a + \" no permitida\";' ' }' ' }' ' return \"permitido\";' '}' >> allow.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la lista permitida, parte 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 = \"En una frase corta: ¿por qué una lista de comandos permitidos es más segura que una de comandos prohibidos?\";' ' const sample = \"No puedes pensar de antemano en todos los comandos malos, pero sí puedes listar los pocos buenos.\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 60);' ' console.log(\"el modelo dijo: \" + reply.text);' '}' 'main();' >> allow.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta la lista permitida
docker exec -w /home/node/project agent node allow.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Lo que imprime el Run
| Línea | Qué muestra |
|---|---|
| ls -l src/ -> permitido | Comando, opción y ruta están en la lista |
| rm -rf src/ -> rechazado: comando fuera de la lista | Falló la primera comprobación |
| cat /etc/passwd -> rechazado: ruta ... | La ruta está fuera de las carpetas |
| grep -r src/app.js -> rechazado: opción -r ... | La opción no está permitida |
| cat src/../.env -> permitido | El hueco: el prefijo engaña a la comprobación |
| el modelo dijo: ... | Una frase. Las palabras cambian en cada ejecución |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.