¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Mantener a un agente en una carpeta

Después de esta lección: Puedo mantener a un agente dentro de una carpeta comprobando la ruta resuelta.

Lección de 65 min6 comandosLaboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

Un agente dentro de su carpeta

Una carpeta confinada es la única carpeta donde un agente puede leer y escribir. Cada ruta que pida debe acabar dentro de ella.

Es más difícil de lo que parece. Una ruta puede salir con dos puntos, o pasar por un enlace que apunta a otro sitio. La solución es comprobar adónde acaba de verdad la ruta.

Al final de esta lección

Nota

Puedo mantener a un agente dentro de una carpeta comprobando la ruta resuelta.

Palabras clave

PalabraQué significaEjemplo
Carpeta confinadala única carpeta que un agente puede usar
Resolvercalcular adónde va de verdad una ruta
Enlaceun archivo que apunta a otro lugaratajo
Escaparacabar fuera de la carpeta
Carpeta superiorla carpeta de arriba, escrita con dos puntos..

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Primero resuelve, después compara

Resolver una ruta es seguir cada par de puntos y cada enlace hasta tener la dirección real. Solo entonces preguntas: ¿está dentro de la carpeta?

Compara con una barra al final. La carpeta caja no es la carpeta caja-vieja, aunque un nombre empiece como el otro.

Ejemplo resuelto: carpeta /trabajo/caja

Se pideVa de verdad a¿Dentro?
nota.txt/trabajo/caja/nota.txtSí
../fuera/clave.txt/trabajo/fuera/clave.txtNo
/etc/hosts/etc/hostsNo
../caja-vieja/x.txt/trabajo/caja-vieja/x.txtNo
atajo/clave.txt/trabajo/fuera/clave.txtNo: atajo es un enlace

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Un error frecuente

Advertencia

Mucha gente cree que quitar los dos puntos de una ruta la hace segura. No es así. Un enlace dentro de la carpeta puede apuntar fuera: el texto parece seguro y el lugar real no. Comprueba siempre la ruta real, no el texto.

Si no puedes resolverla, rechaza

Una ruta puede apuntar a una carpeta que no existe, o a enlaces que dan vueltas sin fin. No puedes decir adónde va, así que no puedes decir que sea segura.

Recházala. Un rechazo le cuesta al agente un paso. Un sí equivocado puede costar un disco entero.

Escribe el ayudante del modelo, parte 1

Comando
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

Comando
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 comprobación de carpeta, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const fs = require(\"fs\");' 'const path = require(\"path\");' 'const base = path.join(process.cwd(), \"sandbox-lab\");' 'const box = path.join(base, \"caja\");' 'fs.rmSync(base, { recursive: true, force: true });' 'fs.mkdirSync(box, { recursive: true });' 'fs.mkdirSync(path.join(base, \"caja-vieja\"));' 'fs.mkdirSync(path.join(base, \"fuera\"));' 'fs.writeFileSync(path.join(box, \"nota.txt\"), \"dentro\");' 'fs.writeFileSync(path.join(base, \"fuera\", \"clave.txt\"), \"secreto\");' 'fs.symlinkSync(path.join(base, \"fuera\"), path.join(box, \"atajo\"));' > confine.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 comprobación de carpeta, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const root = fs.realpathSync(box);' '// Adónde va de verdad una ruta: sigue los enlaces y compara con la carpeta.' 'function inside(p) {' '  try {' '    const t = path.resolve(root, p);' '    const real = fs.existsSync(t) ? fs.realpathSync(t) : path.join(fs.realpathSync(path.dirname(t)), path.basename(t));' '    return real === root || real.startsWith(root + path.sep);' '  } catch (e) { return false; }' '}' 'function naive(p) { return path.resolve(root, p).startsWith(root); }' >> confine.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 comprobación de carpeta, parte 3

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const asked = [\"nota.txt\", \"nueva.txt\", \"../fuera/clave.txt\", \"/etc/hosts\", \"../caja-vieja/x.txt\", \"atajo/clave.txt\"];' 'async function main() {' '  asked.forEach(function (p) { console.log(p + \" texto:\" + naive(p) + \" real:\" + inside(p)); });' '  console.log(\"archivos en caja: \" + fs.readdirSync(box).sort().join(\" \"));' '  const q = \"En una frase corta: ¿por qué hay que resolver una ruta antes de comprobarla?\";' '  const sample = \"Una ruta puede usar puntos o un enlace para apuntar a otro sitio, así que se comprueba dónde acaba de verdad.\";' '  const reply = await ask([{ role: \"user\", content: q }], sample, 60);' '  console.log(\"el modelo dijo: \" + reply.text);' '}' 'main();' >> confine.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 comprobación de carpeta

Comando
docker exec -w /home/node/project agent node confine.js

Se 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íneaQué muestra
nota.txt texto:true real:trueUn archivo dentro de la carpeta
../fuera/clave.txt texto:false ...Las dos comprobaciones cazan los puntos
../caja-vieja/x.txt texto:true real:falseUn nombre que empieza igual engaña al texto
atajo/clave.txt texto:true real:falseUn enlace real que sale de la carpeta
archivos en caja: atajo nota.txtEl Run creó estos archivos de verdad
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.

Sobre este laboratorio y su código: Laboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.