{"slug": "owasp-llm-top-10-en-produccion-como-audite-mi-pipeline-de-agentes-typescript-los", "title": "OWASP LLM Top 10 en producción: cómo audité mi pipeline de agentes TypeScript contra los 10 riesgos y qué encontré", "summary": "A developer audited their TypeScript agent pipeline against the OWASP LLM Top 10 and found critical prompt injection vulnerabilities. The agent's MCP tool accepted external API responses without sanitization, allowing potential system prompt overwrites. The developer mitigated this by adding Zod schema validation and output escaping, and documented model-level risks as a trust boundary.", "body_md": "Estaba revisando un system prompt de un agente MCP que había escrito tres semanas antes cuando me di cuenta de algo perturbador: el prompt aceptaba instrucciones de la respuesta de una tool externa. Sin sanitización. Sin validación. Sin ningún límite sobre qué podía hacer con esa salida. La tool llamaba a una API pública, recibía JSON, y ese JSON llegaba directo al contexto del modelo.\n\nAhí fue cuando abrí el [OWASP LLM Top 10](https://owasp.org/www-project-top-10-for-large-language-model-applications/) y paré de leerlo como lista de buenas prácticas para empezar a usarlo como lo que en realidad es: un framework de auditoría.\n\nMi tesis es esta: la mayoría de los posts sobre OWASP LLM Top 10 te explican los diez riesgos. Ninguno te muestra cómo correrlos contra tu stack propio y qué encontrás cuando lo hacés en serio. Esa es la diferencia entre \"leer el checklist\" y \"auditar el pipeline\". Acá está lo segundo.\n\nAntes de entrar al checklist, el contexto: tengo un pipeline de agentes en TypeScript con tres capas que interactúan:\n\nCada capa tiene una superficie de ataque diferente. Eso es lo que el OWASP LLM Top 10 me permitió ver con precisión quirúrgica.\n\nEste fue el hallazgo más gordo. Mi agente MCP recibía output de tools externas y lo incorporaba al contexto sin ninguna capa de sanitización. En un escenario adversarial, cualquier API que el agente consultara podría devolver texto diseñado para sobrescribir las instrucciones del system prompt.\n\nEl patrón roto era este:\n\n```\n// ❌ Patrón inseguro: output externo directo al contexto\nasync function fetchContextAndInject(url: string): Promise<string> {\n  const response = await fetch(url);\n  const data = await response.json();\n  // data.content llega sin ningún filtro al contexto del modelo\n  return data.content;\n}\n```\n\nLo que cambié:\n\n``` js\n// ✅ Validación de estructura antes de incorporar al contexto\nimport { z } from \"zod\";\n\nconst ExternalResponseSchema = z.object({\n  // Solo acepto campos con tipo definido — string libre marcado como sospechoso\n  title: z.string().max(200),\n  summary: z.string().max(1000),\n  // Descarto cualquier campo que no esté en el schema\n});\n\nasync function fetchContextSafe(url: string): Promise<string> {\n  const response = await fetch(url);\n  const raw = await response.json();\n  // Si el schema falla, el agente recibe un error estructurado, no el payload crudo\n  const parsed = ExternalResponseSchema.parse(raw);\n  return `Título: ${parsed.title}\\nResumen: ${parsed.summary}`;\n}\n```\n\nUsé Zod — que ya tenía en el stack para validación de API — como primera línea. No es una solución completa al prompt injection, pero reduce la superficie de ataque estructural.\n\nEl segundo problema: el output del agente llegaba a la UI sin escaping. En un agente que genera HTML o Markdown, eso es XSS potencial si el output se renderiza directamente.\n\nRevisé dónde el output del modelo llegaba al DOM y agregué sanitización explícita antes de cualquier render. Si el agente genera código, ese código va a un bloque `<pre>`\n\ncon escape de caracteres; no a un `innerHTML`\n\n.\n\nAcá el OWASP LLM Top 10 apunta a riesgos del modelo base, no de la aplicación. En mi caso el modelo es Claude vía API — no controlo el fine-tuning ni el dataset. Mi única acción fue documentar esta dependencia explícitamente: **si Anthropic tiene un problema acá, yo tengo un problema acá**. Ningún sistema prompt lo compensa.\n\nLímite honesto: no podés auditar esto desde la aplicación. Es una dependencia que tomás como trust boundary.\n\nRevisé si tenía rate limiting en los endpoints que disparan llamadas al modelo. No lo tenía en el contexto de pruebas locales. En un escenario de producción esto es crítico: un loop mal diseñado o una tool que llama recursivamente puede generar decenas de requests al modelo en segundos.\n\nAgregué un límite simple de iteraciones al loop del agente:\n\n``` js\n// Control de iteraciones para evitar loops infinitos en el agente\nconst MAX_ITERATIONS = 10;\nlet iterations = 0;\n\nwhile (agentShouldContinue && iterations < MAX_ITERATIONS) {\n  iterations++;\n  const result = await runAgentStep();\n  agentShouldContinue = result.continueLoop;\n}\n\nif (iterations >= MAX_ITERATIONS) {\n  // Log explícito — quiero saber si esto se dispara\n  console.warn(\"[agente] Límite de iteraciones alcanzado — revisar loop\");\n}\n```\n\nEste riesgo me hizo revisar dos cosas: los paquetes npm que uso para interactuar con la API del modelo y las dependencias de mis MCP tools. Con pnpm workspaces (tema que ya cubrí en [el post de monorepo con Railway](https://juanchi.dev/es/blog/pnpm-workspaces-monorepo-ci-railway-problemas)) tenés visibilidad del lockfile, pero eso no es auditoría.\n\nLo que agregué: `pnpm audit`\n\ncomo paso explícito en CI antes del deploy de cualquier agente. No elimina el riesgo, pero lo hace visible.\n\nAcá encontré el segundo hallazgo incómodo: en los system prompts tenía contexto de configuración que incluía nombres de tools internas, estructura de datos y algunos defaults del sistema. Ese contexto llega al modelo — y si el modelo lo repite en su output, lo expone.\n\nLa regla que apliqué: **nada que no quieras ver en un log público debería estar en un system prompt sin marcado explícito de confidencialidad**. Y eso tampoco es garantía — es mitigación.\n\n``` js\n// Separar configuración técnica de instrucciones del agente\nconst SYSTEM_PROMPT_PUBLIC = `\nSos un asistente de desarrollo. Podés usar las herramientas disponibles\npara responder preguntas técnicas.\n`;\n\n// Esto NO va al system prompt — va a una capa de configuración separada\nconst AGENT_CONFIG_PRIVATE = {\n  toolEndpoints: process.env.TOOL_ENDPOINTS,\n  internalSchema: process.env.INTERNAL_SCHEMA,\n};\n```\n\nMis MCP tools son básicamente plugins. El riesgo acá es que una tool tenga permisos más amplios de lo necesario. Revisé cada tool y apliqué el principio de mínimo privilegio: una tool que lee archivos no necesita escribir; una tool que consulta una API no necesita acceso al filesystem.\n\nEsto conecta con lo que escribí sobre [OAuth scope creep](https://juanchi.dev/es/blog/oauth-scope-creep-auditoria-integraciones-terceros-seguridad) — el mismo patrón de auditoría aplica a las tools de un agente.\n\nEste es el riesgo que más me preocupa en Cline específicamente. El agente tiene acceso a terminal, puede ejecutar comandos, puede modificar archivos. Si el loop de razonamiento falla, puede hacer daño real.\n\nLo que implementé: modo \"confirm before execute\" para cualquier tool con efecto secundario irreversible. No es automatizable — requiere fricción humana deliberada. Y esa fricción es el punto.\n\n```\n// Clasificación explícita de tools por impacto\ntype ToolImpact = \"read-only\" | \"reversible\" | \"destructive\";\n\nconst TOOL_IMPACT_MAP: Record<string, ToolImpact> = {\n  readFile: \"read-only\",\n  listDirectory: \"read-only\",\n  writeFile: \"reversible\",\n  deleteFile: \"destructive\",\n  runCommand: \"destructive\",\n};\n\nasync function executeTool(toolName: string, args: unknown) {\n  const impact = TOOL_IMPACT_MAP[toolName] ?? \"destructive\"; // fallback seguro\n  if (impact === \"destructive\") {\n    // Pausa y espera confirmación humana antes de ejecutar\n    await requireHumanApproval(toolName, args);\n  }\n  return runTool(toolName, args);\n}\n```\n\nNo es un riesgo técnico puro — es organizacional. El problema es confiar en el output del agente sin validación externa. En mi pipeline, cualquier output que va a producción pasa por una capa de validación estructural antes de ser usado como input de otro sistema. El modelo puede estar seguro, el pipeline puede estar seguro, y el output puede seguir siendo incorrecto.\n\nEste riesgo no se cierra con código. Se cierra con proceso y revisión humana en los nodos críticos.\n\nEn mi contexto de agente TypeScript, esto aplica principalmente a la protección de los system prompts. Un system prompt elaborado representa trabajo real — y si se expone, puede ser replicado o usado para evadir restricciones.\n\nLo que implementé: los system prompts no viven en el código del frontend. Se sirven desde un endpoint autenticado, no se loguean en texto plano y no se exponen en el bundle del cliente.\n\nAcá está lo que la lista no resuelve sola:\n\n**No te dice el orden de prioridad para tu stack.** LLM01 (prompt injection) fue crítico en mi caso; LLM03 (training data poisoning) es irrelevante desde la aplicación. Sin aplicarlo contra tu arquitectura concreta, no sabés cuál es urgente.\n\n**No te da criterio para el trust boundary del modelo base.** Si usás Claude, GPT-4 o cualquier API externa, LLM03 y parte de LLM05 son dependencias que tomás como dadas. El framework las nombra, pero la mitigación no está en tus manos.\n\n**No distingue entre riesgos de runtime y riesgos de diseño.** LLM01 y LLM02 son problemas que podés detectar y mitigar en runtime. LLM08 (excessive agency) es un problema de diseño — si el agente tiene demasiados permisos, un patch de runtime no lo arregla.\n\nTengo un post sobre [OpenTelemetry en Next.js](https://juanchi.dev/es/blog/opentelemetry-nextjs-traces-edge-runtime-contexto) donde hablo de traces que sobreviven el edge. Ese tipo de observabilidad también ayuda acá: si no podés ver qué tools llamó el agente y con qué args, no podés auditar LLM08 en producción.\n\n| Riesgo | Estado encontrado | Acción tomada |\n|---|---|---|\n| LLM01 Prompt Injection | ❌ Vulnerable | Zod schema en output de tools externas |\n| LLM02 Insecure Output | ⚠️ Parcial | Escaping explícito antes de render |\n| LLM03 Training Data | 🔵 Fuera de scope | Documentado como trust boundary |\n| LLM04 Model DoS | ⚠️ Sin límite | Agregué max iterations + log |\n| LLM05 Supply Chain | ⚠️ Invisible |\n`pnpm audit` en CI |\n| LLM06 Info Disclosure | ❌ Leaky prompts | Separé config de system prompt |\n| LLM07 Plugin Flaws | ⚠️ Parcial | Revisión de permisos por tool |\n| LLM08 Excessive Agency | ⚠️ Sin fricción | Confirm before execute en tools destructivas |\n| LLM09 Overreliance | 🔵 Proceso | Validación humana en nodos críticos |\n| LLM10 Model Theft | ⚠️ Prompts expuestos | Prompts a endpoint autenticado |\n\n❌ = hallazgo crítico | ⚠️ = mitigación parcial | 🔵 = fuera del control de la aplicación\n\n**¿El OWASP LLM Top 10 aplica a agentes basados en Claude o GPT-4 vía API?**\n\nSí, con matices. LLM01, LLM02, LLM06, LLM07, LLM08 y LLM10 son riesgos de la aplicación — aplican sin importar qué modelo uses. LLM03 (training data) y parte de LLM05 son riesgos del proveedor: si usás una API externa, los tomás como trust boundary. La auditoría empieza por los riesgos que sí podés controlar.\n\n**¿Con Zod alcanza para mitigar prompt injection?**\n\nNo. Zod valida la estructura del output externo antes de que llegue al contexto — eso reduce la superficie, pero no elimina el riesgo. Un payload adversarial bien formado puede pasar la validación de schema. Zod es una capa, no una solución completa. La mitigación real combina schema validation, restricciones en el system prompt y revisión humana en puntos críticos.\n\n**¿Cline es seguro para usar en producción como orquestador de agentes?**\n\nCline tiene acceso a filesystem, terminal y otras herramientas con efecto real. Eso no es inherentemente inseguro — es la funcionalidad que lo hace útil. El riesgo (LLM08) está en el diseño: si el agente puede ejecutar comandos destructivos sin confirmación humana, el riesgo es real independientemente de qué tan bien esté configurado Cline. La regla que aplico: cualquier tool con efecto irreversible requiere aprobación explícita.\n\n**¿Cada cuánto hay que correr esta auditoría?**\n\nCada vez que cambiás la arquitectura del agente: agregás una tool nueva, cambiás el system prompt o modificás cómo el agente consume outputs externos. No es una auditoría de una sola vez — es un checklist que corre contra cada cambio estructural. Si agregás observabilidad ([OpenTelemetry](https://juanchi.dev/es/blog/opentelemetry-nextjs-traces-edge-runtime-contexto) es una opción), podés detectar anomalías en runtime entre auditorías.\n\n**¿El OWASP LLM Top 10 cubre riesgos de multi-agente o solo agente único?**\n\nLa versión actual ([2025](https://owasp.org/www-project-top-10-for-large-language-model-applications/)) cubre principalmente el riesgo por agente. En arquitecturas multi-agente, la superficie de LLM01 se multiplica: cada agente puede ser un vector de inyección para los demás. El framework nombra el riesgo, pero el detalle de mitigación para pipelines multi-agente queda en manos de cada equipo.\n\n**¿Qué riesgo debería atacar primero si tengo tiempo limitado?**\n\nLLM01 (prompt injection) si tu agente consume output externo — es el más explotable y el más ignorado. LLM08 (excessive agency) si el agente tiene acceso a herramientas con efecto irreversible — es el que más daño puede hacer en un fallo. Los demás dependen de tu stack, pero estos dos son el piso mínimo.\n\nMi postura es clara: el OWASP LLM Top 10 no sirve para leerlo y darlo por cubierto. Sirve para llevarlo a una sesión de revisión con el diagrama de arquitectura enfrente y preguntar, por cada riesgo, dónde exactamente en el pipeline eso puede fallar.\n\nLo que no compro es la idea de que \"seguir las buenas prácticas\" alcanza. Las prácticas son abstractas; el pipeline es concreto. En mi caso, LLM01 y LLM06 eran problemas reales que no habría encontrado sin hacer el ejercicio de auditoría sistemática. Los habría descubierto cuando alguien con motivación los explotara.\n\nSi ya tenés agentes en TypeScript con MCP tools o system prompts elaborados, hacé el ejercicio: abrí el OWASP LLM Top 10, abrí el diagrama de arquitectura y preguntá riesgo por riesgo. El resultado va a ser más interesante que el listado.\n\nPróximo paso concreto: tomá el checklist de esta tabla, reemplazá los estados con los propios y publicá los hallazgos. La auditoría que no se documenta no existe.\n\n**Fuente original:**\n\n*Este artículo fue publicado originalmente en juanchi.dev*", "url": "https://wpnews.pro/news/owasp-llm-top-10-en-produccion-como-audite-mi-pipeline-de-agentes-typescript-los", "canonical_source": "https://dev.to/jtorchia/owasp-llm-top-10-en-produccion-como-audite-mi-pipeline-de-agentes-typescript-contra-los-10-riesgos-11fd", "published_at": "2026-06-20 12:02:53+00:00", "updated_at": "2026-06-20 12:07:14.287055+00:00", "lang": "en", "topics": ["large-language-models", "ai-safety", "ai-agents", "developer-tools"], "entities": ["OWASP", "Claude", "Anthropic", "Zod", "MCP"], "alternates": {"html": "https://wpnews.pro/news/owasp-llm-top-10-en-produccion-como-audite-mi-pipeline-de-agentes-typescript-los", "markdown": "https://wpnews.pro/news/owasp-llm-top-10-en-produccion-como-audite-mi-pipeline-de-agentes-typescript-los.md", "text": "https://wpnews.pro/news/owasp-llm-top-10-en-produccion-como-audite-mi-pipeline-de-agentes-typescript-los.txt", "jsonld": "https://wpnews.pro/news/owasp-llm-top-10-en-produccion-como-audite-mi-pipeline-de-agentes-typescript-los.jsonld"}}