Guía de seguridad de agentes IA: JSON malicioso, prompt injection, Tool Calling y riesgos de datos estructurados

A 14 de septiembre de 2026: cuando un agente puede llamar herramientas e ingerir JSON externo, la superficie pasa de una respuesta engañosa a una invocación real. Mapa defensivo: JSON malicioso, inyección indirecta, herramientas con exceso de privilegio y esquemas demasiado flojos.

Conclusión primero: en 2026, la seguridad de un AI Agent no es «¿dirá el modelo la frase equivocada?». Es «¿pueden datos estructurados no confiables convertirse en una invocación real de herramienta?». Un chatbot inyectado, en el peor caso, emite una respuesta engañosa. Un Agent inyectado, en el peor caso, envía correo, edita archivos, consulta una base o dispara un pago. JSON es a la vez el contrato y la carga. Prompt Injection a menudo no vive en el cuadro de chat — vive en campos de tickets, páginas web, cuerpos de correo y respuestas de API, todos llegando como JSON.

Escrito a fecha de 14 de septiembre de 2026. Ya cubrimos por qué Tool Calling depende de JSON Schema, el flujo de datos JSON del Agent, JSON Schema como contrato y MCP / Skills / Tools / Subagents. Este texto solo responde qué arriesgan el JSON malicioso, la inyección indirecta, el Tool Calling con exceso de privilegio y los Schema flojos — y qué debe imponer el Host. Es una guía defensiva. No ofrece pasos de ataque reproducibles ni texto de inyección listo para usar.

Una tabla: superficie de ataque de chatbot vs Agent

Lo que el usuario teclea es solo una franja de lo que un Agent ingiere. En la pila por defecto de 2026 el modelo también lee resultados de tools, Resources de MCP, extractos de página, campos de ticket y cuerpos de correo — casi siempre como JSON, o como cadenas envueltas en JSON. Las listas del sector suelen meterlo en Prompt Injection, Insecure Output Handling y Excessive Agency. No hace falta la taxonomía primero. Hace falta saber quién habla y quién ejecuta.

DimensiónChatbotAgent que puede llamar tools
Modo de falloUna respuesta errónea o inducidaUn efecto secundario real: escribir, pedir, mutar datos
Entrada no confiableEl mensaje actual del usuarioTexto del usuario + JSON externo + resultados de tools + campos de página/correo
EjecutorEl modelo solo emite textoHost / MCP Server ejecuta arguments
ContratoEl prompt (blando)JSON Schema + validación del Host (duro)
Arreglo mínimoAjustes de prompt, política de rechazoSchema más estrecho, lista blanca de tools, autorización independiente

Una frase: el modelo se puede engañar; el Host no debe ejecutarlo de arrastre. El límite de seguridad está entre generar tool_calls y ejecutarlos de verdad. Ahí caben validación, autorización y auditoría — no «tratar la salida del modelo como un RPC de confianza».

JSON malicioso: campos de más, confusión de tipos, payloads anidados

El JSON malicioso suele estar bien formado y seguir siendo peligroso. Un parser estricto rechaza comas finales o comentarios. Un parser laxo, la concatenación de cadenas o «trátalo como texto y luego JSON.parse» solo retrasan el fallo. En sistemas Agent el daño empieza cuando el Host ya trata el valor como objeto.

Tres formas a defender primero (solo patrones — no un how-to):

PatrónQué pareceSi el Host no validaDefensa
Campos de másUn objeto de negocio con claves no declaradasSe fusiona en la config, o se reenvía al siguiente tooladditionalProperties: false; descartar claves desconocidas
Confusión de tiposUn number/array llega como string u objectLas ramas de autorización fallan, o un blob entero se vuelve argumentoFijar type, enum, format
Payload anidadoUn campo string que embebe más JSON o un brief largoEl texto interior entra al prompt y se lee como instruccionesLímites de longitud; validar el JSON interior; nunca tratar el texto crudo como voz de system

Un contrato de parámetros de tool que «funciona» porque casi no restringe nada, y luego la versión apretada. Son muestras defensivas, no material de ataque:

{
  "type": "object",
  "properties": {
    "payload": { "type": "object" }
  }
}

Ese Schema casi no es contrato: pasa cualquier clave, cualquier anidación. En producción, lista blanca de campos y extras prohibidos:

{
  "type": "object",
  "properties": {
    "order_id": { "type": "string", "pattern": "^A-[0-9]{4,8}$" },
    "amount": { "type": "number", "minimum": 0, "maximum": 100000 },
    "note": { "type": "string", "maxLength": 200 }
  },
  "required": ["order_id", "amount"],
  "additionalProperties": false
}

Al revisar muestras, usa el validador JSON de este sitio contra ese Schema, y JSON Diff para comparar «arguments que emitió el modelo» con «el objeto mínimo que permites». Claves de más y tipos volteados son la primera alarma.

Otro olvido: no fusiones objetos no confiables en la config interna ni en estructuras que toquen la cadena de prototipos. En un Host JavaScript, claves desconocidas en un objeto de config pueden significar más que «un campo extra». El arreglo es el mismo: proyecta un objeto de lista blanca desde el Schema y pasa ese hacia abajo.

Prompt Injection: instrucciones escondidas en datos estructurados

Inyección directa es el usuario hablando al cuadro de chat, intentando pisar el system prompt. Inyección indirecta encaja con cómo corren de verdad los Agents de 2026: las instrucciones se esconden en datos que el modelo leerá después — títulos de ticket, cuerpos de correo, párrafos de página, extractos de PDF, cadenas JSON que devolvió otro tool. Los modelos no separan de forma natural «reglas que escribió el desarrollador» de «frases dentro de los datos».

Los datos estructurados lo hacen más silencioso. Una nota de cliente es solo customer_note en una API. Una vez en contexto, comparte el flujo de tokens con el system prompt. Si el Host concatena resultados de tools al siguiente turno tal cual, un sitio externo o un remitente está, de facto, escribiendo prompts para el Agent.

La defensa no es otra frase del tipo «por favor ignora instrucciones maliciosas». Los prompts bajan el riesgo; no son un límite. Controles más estables:

  • Etiqueta la procedencia — el texto externo entra con un rol o envoltorio distinto (por ejemplo tool / untrusted), nunca plegado en system.
  • Encoge la superficie visible — resume en vez de pegar; envía solo los campos que necesitas, no el documento JSON entero.
  • Pon puerta a las acciones de alto impacto — transferencias, borrados, envíos salientes necesitan un motor de políticas o un humano, independientes del modelo.
  • Trata los resultados de tools como datos, no como instrucciones — valida los retornos con Schema antes de que vuelvan al prompt.

El mismo instinto que cómo Apple Intelligence protege los datos personales: lo que se filtra o se abusa suele ser un campo que tú pusiste en JSON. En el modelo, un campo se puede leer como habla; en los arguments de un tool, como una acción.

Tool Calling: abuso de privilegio que parece una llamada válida

El peligro no es «¿puede el modelo emitir JSON?». Es si el Host trata al modelo como un llamante ya autorizado. Es el clásico confused deputy: el modelo propone; el permiso pertenece a la sesión de usuario, al tenant y a la cuenta de servicio. «Llama a export_orders» no es lo mismo que «este usuario puede exportar la tabla».

El exceso de privilegio en 2026 suele verse así:

  • Un único run_command / http_request con cadenas casi arbitrarias;
  • Tools que corren con una cuenta de servicio más amplia que el usuario final;
  • El Host comprueba la sintaxis JSON pero no «¿puede este usuario tocar este recurso?»;
  • El JSON del usuario o de la página se pasa tal cual como arguments del tool, saltándose tu Schema.

Una declaración demasiado abierta, y luego una que sí se puede auditar:

{
  "name": "fetch_url",
  "description": "Fetch any URL and return text.",
  "parameters": {
    "type": "object",
    "properties": {
      "url": { "type": "string" }
    },
    "required": ["url"]
  }
}
{
  "name": "fetch_public_doc",
  "description": "Fetch an allowlisted documentation URL.",
  "parameters": {
    "type": "object",
    "properties": {
      "doc_id": {
        "type": "string",
        "pattern": "^doc-[a-z0-9-]{1,32}$"
      }
    },
    "required": ["doc_id"],
    "additionalProperties": false
  }
}

La segunda no es «más lista». Convierte una capacidad abierta en un identificador auditable. El Host resuelve doc_id contra su propia lista blanca, rellena el origen y aplica timeouts y techos de tamaño. El modelo nunca ve una URL arbitraria, así que hay un camino menos hacia una fuente no confiable.

Antes de ejecutar, tres preguntas: ¿está este tool en la lista blanca de la sesión? ¿Pasaron los arguments un Schema y una comprobación de autorización independientes del modelo? Ante el fallo, ¿rechazas — o devuelves el detalle del error como texto fresco inyectable? Errores de parámetros y validación cubre el pipeline. En seguridad, añade: falla cerrado, y no dejes que el modelo reintente con arguments nuevos en un bucle sin tope.

Un Schema demasiado flojo es una vulnerabilidad

Structured Output y Tool Calling usan ambos JSON Schema para bloquear tokens ilegales — el contrato por defecto de 2026, ver qué es Structured Output. El Schema garantiza forma, no sentido seguro.

Los contratos flojos suelen verse así:

  • type: object sin properties, o additionalProperties dejado en true;
  • Un nombre de acción que debería ser enum, escrito como cualquier string;
  • Un campo identificador que puede cargar un brief de una página;
  • El modo strict del proveedor cubre un subconjunto, y el Host asume que «el lado del modelo ya bloqueó todo».

Apila dos puertas: el Schema del lado del modelo reduce el disparate; el Host valida otra vez con el mismo Schema (o uno más estricto) y proyecta a un tipo interno. Los Structured Outputs del proveedor no sustituyen tu autorización. Las diferencias de campos entre proveedores son riesgo en sí — ver la comparación de Structured Output. Una migración que afloja el contrato ensancha la superficie de ataque.

Cuando el Schema es un control de seguridad, prioriza enum, const, pattern, maxLength, minimum / maximum, required, additionalProperties: false. Si necesitas texto libre, aíslo, ponle techo y nunca mapees texto libre a un nombre de tool ni a una URL.

MCP y herramientas externas: dónde está el límite de confianza

MCP resuelve el descubrimiento y la invocación entre procesos. No resuelve «¿este Server es benigno?». Qué es MCP ya traza la línea JSON-RPC: API del modelo a un lado, Host ↔ Server al otro. La seguridad necesita una segunda línea: descripciones de tools, texto de Resources y JSON de retorno de un MCP Server de terceros son entrada no confiable.

El riesgo no es la fecha del protocolo. Es la confianza puesta en la capa equivocada:

  • description / inputSchema de tools/list pegados en el system prompt;
  • result de tools/call entrando al siguiente turno sin validación;
  • Demasiados Servers en un Host, nombres o capacidades que se solapan, y se ejecuta igual;
  • Tratar «el usuario permitió este Server» como «cada frase que devuelve es una instrucción».

Skills tienen el mismo tipo de problema: un Skill es un how-to, no una capa de autorización. Cargar SKILL.md de un repo no confiable añade un flujo que el modelo seguirá. Ver qué poseen las cuatro capas — no sustituyas Schema por un Skill, ni el aislamiento de permisos por un Subagent. Un Subagent puede estrechar tools; el padre sigue decidiendo qué secretos recibe.

Lista de defensa: validar, lista blanca, mínimo privilegio

Aprieta a lo largo de la ruta de ejecución, de fuera hacia dentro — no empieces por el prompt:

  1. Escribe el contrato primero. Un JSON Schema por tool: required completo, additionalProperties: false, identificadores vía pattern / enum.
  2. Valida otra vez en el Host. No confíes en «el modelo ya siguió el Schema». Usa ajv o equivalente; falla cerrado.
  3. Proyecta; no reenvíes. Copia campos de lista blanca a un DTO interno y luego llama a la API de negocio.
  4. Minimiza tools. Prefiere fetch_public_doc a fetch_url; prefiere solo lectura a escritura.
  5. Autoriza como el usuario, no como el modelo. Comprueba sesión, tenant y ACL del recurso dentro de la implementación del tool.
  6. Pon puerta a los efectos secundarios de salida. Enviar, transferir, borrar, desplegar en producción: motor de políticas o confirmación humana.
  7. Degrada el texto externo. Resultados de tools, páginas y correo no son system. Si hace falta, un Subagent de solo lectura devuelve un resumen al padre.
  8. Audita arguments. Registra nombre del tool, parámetros validados, quién autorizó, si se denegó.

Los prompts siguen ayudando: di que los datos no son instrucciones, lista acciones prohibidas. Son una capa de apoyo. Diez frases más de prompt no sustituyen un Schema o un ACL que faltan.

Revisar contratos con herramientas JSON locales

Antes de publicar, mira tres archivos juntos: los parameters del tool / el inputSchema de MCP, un objeto de arguments del «camino feliz», y una muestra deliberadamente fea (claves de más, tipos equivocados, cadenas desmesuradas). Sin secretos reales, sin datos reales de usuario.

  • Validador JSON — ¿la muestra es JSON legal, y satisface tu Schema?
  • JSON Diff — ¿qué añadió el modelo más allá del objeto mínimo?
  • Visor de árbol — ¿el anidamiento es más profundo de lo que debería; un campo string esconde otra estructura?

Nada sale del navegador. Encaja con revisar un contrato a punto de ir a producción, y con comparar arguments de una llamada fallida. Estabiliza nombres de campo y required, luego cablea el Host o el MCP Server.

FAQ

¿Puede JSON Schema detener Prompt Injection?

No por sí solo. El Schema limita la forma y el rango de los arguments de un tool, y baja la probabilidad de que una cadena arbitraria se vuelva una acción arbitraria. La inyección indirecta ocurre cuando el texto entra al contexto — siguen haciendo falta procedencia, degradación y puertas de alto impacto.

El modelo ya usa Structured Output / modo strict. ¿Debe validar el Host?

Sí. Las restricciones del proveedor aplican en la generación, y cada proveedor soporta un subconjunto distinto de Schema. Las decisiones de seguridad deben ocurrir antes de ejecutar, con tu propio validador y tu autorización.

¿Qué tiene de malo pasar el JSON del usuario directo a un tool?

Te saltas el contrato. Campos de más, confusión de tipos y texto anidado llegan sin cambio a código con efectos secundarios. Valida, luego proyecta; pasa solo campos de lista blanca.

¿Se puede usar la salida de un MCP Server como system prompt?

No. Las descripciones de tools/list, el texto de Resources y los resultados de tools/call son datos no confiables. Valídalos y reentra al diálogo con menor privilegio.

¿Cuál es la línea base mínima viable de seguridad de un Agent?

JSON Schema estricto, revalidación en el Host, lista blanca de tools, autorización por usuario y puertas en los efectos secundarios de salida. Sin esas cinco, no conectes el Agent a datos de producción.

¿Cómo compruebo en local si el JSON de un tool es peligroso?

Mete Schema y muestras de arguments en la caja de herramientas JSON para validación y Diff. Confirma required, additionalProperties y límites de longitud/enum antes de cablear el Host o el MCP Server.

Resumen

La superficie de ataque del Agent está entre los datos estructurados y la ejecución de tools: el JSON malicioso se cuela por el contrato, Prompt Injection desvía la intención, Tool Calling convierte la intención en efectos secundarios, y un Schema flojo abre la puerta a los tres. La pila por defecto de 2026 (Tools, MCP, Skills, Subagents) hace a los Agents más útiles — y hace que «engañar una invocación» valga más que «engañar una respuesta».

Defiende de dentro hacia fuera: haz de JSON Schema un contrato duro; el Host valida, proyecta y autoriza; mantén los tools pequeños; no trates el texto externo como instrucciones. Los prompts ayudan; no son el límite. Valida muestras en local antes de publicar — los modelos pueden cambiar; los nombres de campo, required y «quién puede ejecutar» no deberían.