Qué es el problema del delegado confundido
Un delegado confundido es un programa con autoridad al que convencen de usarla para alguien que no tiene ninguna. Un agente es un delegado por diseño: actúa por las personas mediante herramientas y cuentas.
Si el agente tiene una sola llave amplia y confía en cualquiera que le pida algo, todo el que pueda escribirle tiene esa llave.
Al final de esta lección
Nota
Puedo evitar que un agente actúe con más autoridad que la persona que pide, comprobando cada acción contra quien la pide.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Delegado confundido | un programa que usa su propia autoridad para una persona que no la tiene | |
| Autoridad | lo que una identidad puede hacer, aquí como acciones sobre recursos | read:nomina |
| Alcance | un permiso con nombre dentro de un token | |
| Token estrecho | un token con solo los alcances que necesita la tarea, durante poco tiempo | |
| Solicitante | la persona por cuenta de quien actúa el agente en esta petición |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Qué autoridad lleva una llamada
Supón que un agente entra en el sistema de nóminas con su propia llave. Ana, que solo puede leer notas, le pide el archivo de nóminas. El sistema ve la llave del agente y dice que sí.
No engañó al agente una línea oculta. Solo se lo pidieron. El fallo es que la llamada llevaba la autoridad del agente y no la de Ana.
Ejemplo resuelto: tres diseños
| Diseño | Lo que lleva la llamada | Qué pasa cuando Ana pide la nómina |
|---|---|---|
| Una sola llave amplia | La llave del agente | El sistema lo permite |
| Se pasa la identidad del solicitante | La identidad de Ana | El sistema comprueba a Ana y rechaza |
| Token estrecho por tarea | Solo los alcances que necesita la tarea | El alcance no está en el token, así que falla |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
El Run decide las llamadas de dos maneras
El primer script guarda dos usuarios y tres llamadas inventadas. Decide cada llamada dos veces: una por el token del agente y otra por los derechos de quien pide.
Donde las dos respuestas difieren, el agente actuaría con más autoridad que la persona que pide. El script marca esa línea. No se llama a nada y nadie recibe un ataque.
Escribe la comprobación doble, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const rights = {' ' ana: [\"read:notas\"],' ' luis: [\"read:notas\", \"read:nomina\"],' '};' 'const agentToken = [\"read:notas\", \"read:nomina\"];' 'const calls = [' ' { user: \"ana\", action: \"read\", resource: \"notas\" },' ' { user: \"ana\", action: \"read\", resource: \"nomina\" },' ' { user: \"luis\", action: \"read\", resource: \"nomina\" },' '];' > deputy.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 doble, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function want(c) { return c.action + \":\" + c.resource; }' 'function byAgent(c) { return agentToken.includes(want(c)); }' 'function byAsker(c) { return (rights[c.user] || []).includes(want(c)); }' '' 'for (const c of calls) {' ' const a = byAgent(c), b = byAsker(c);' ' let line = c.user + \" pide \" + want(c) + \": token del agente \" + (a ? \"permite\" : \"niega\");' ' line += \", derechos de quien pide \" + (b ? \"permite\" : \"niega\");' ' console.log(line + (a && b === false ? \" <- delegado confundido\" : \"\"));' '}' >> deputy.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 doble
docker exec -w /home/node/project agent node deputy.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 |
|---|---|
| ana pide read:notas | Las dos permiten. Ana puede leer notas |
| ana pide read:nomina | El token permite, sus derechos niegan: marcada como delegado confundido |
| luis pide read:nomina | Las dos permiten. Luis tiene ese derecho |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Tres arreglos que funcionan juntos
Pasa al solicitante. Cada llamada lleva la identidad de la persona, y la herramienta o la API la comprueba.
Estrecha el token. Da a una tarea solo los alcances que necesita y deja que caduquen pronto.
Comprueba cada acción en el código. Comprueba en el punto donde ocurre la acción, cada vez. Nunca preguntes al modelo si está permitido.
Un error frecuente
Advertencia
Muchos escriben «ayuda a los usuarios solo con sus propios archivos» en el prompt y se paran ahí. Un prompt es una petición al modelo. La tabla de derechos en el código es la regla, y da la misma respuesta siempre.
Escribe el script del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '' 'const rights = { ana: [\"read:notas\"] };' 'const call = { user: \"ana\", action: \"read\", resource: \"nomina\" };' 'const messages = [' ' { role: \"system\", content: \"Eres un asistente de oficina. La usuaria Ana solo puede leer las notas. Responde con una palabra, permitir o denegar, y una razón breve.\" },' ' { role: \"user\", content: \"Ana te pide que leas el archivo de nóminas. ¿Qué haces?\" },' '];' '// Muestra grabada, usada solo cuando ningún modelo responde.' 'const sample = \"denegar. Ana solo puede leer las notas.\";' > deputy2.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 decide(c) {' ' const allowed = rights[c.user] || [];' ' return allowed.includes(c.action + \":\" + c.resource) ? \"permitir\" : \"denegar\";' '}' '' 'async function main() {' ' const reply = await ask(messages, sample, 60);' ' console.log(\"El modelo dice: \" + reply);' ' console.log(\"El programa decide: \" + decide(call));' ' console.log(\"La llamada solo se ejecuta si el programa dice permitir.\");' '}' 'main();' >> deputy2.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 deputy2.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 |
|---|---|
| El modelo dice | Una palabra como denegar con una razón breve. Las palabras cambian en cada ejecución |
| El programa decide | denegar, siempre, porque Ana no tiene derecho a la nómina en la tabla |
| 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.