ASI07
OWASP Agentic Top 10 MODELO

Comunicación insegura entre agentes

Descripción

Los sistemas multiagente intercambian mensajes, delegan tareas y comparten contexto sin autenticación, cifrado o verificación de integridad, lo que habilita la suplantación, la escucha no autorizada y la delegación no autorizada.

Riesgo

Panorama del riesgo

Las arquitecturas agénticas modernas despliegan múltiples agentes especializados que se comunican para lograr tareas complejas — un orquestador delega a subagentes, agentes pares comparten hallazgos y agentes supervisores revisan resultados. Estos canales de comunicación entre agentes se implementan con frecuencia como simples llamadas a funciones, solicitudes HTTP o colas de mensajes sin autenticación, sin cifrado y sin validación de integridad de mensajes. Un atacante que pueda interceptar, suplantar o repetir mensajes entre agentes puede redirigir flujos de trabajo, inyectar instrucciones maliciosas o exfiltrar datos.

Superficie de ataque

  • Delegación no autenticada: el agente A envía una tarea al agente B sin probar su identidad; cualquier proceso puede suplantar al agente A
  • Canales sin cifrar: los mensajes entre agentes atraviesan las redes en texto plano, exponiendo detalles de tareas, credenciales y datos sensibles a quienes intercepten el tráfico
  • Repetición de mensajes: mensajes legítimos capturados se repiten para disparar acciones del agente fuera de contexto (p. ej., repetir un mensaje de 'aprobar despliegue')
  • Comunicación sin esquema: los agentes aceptan payloads arbitrarios de JSON/texto sin validación de esquema, lo que habilita la inyección de campos o instrucciones inesperados
  • Transitividad de la confianza: si el agente A confía en el agente B y el agente B confía en el agente C, el agente A confía implícitamente en el agente C sin verificación directa

Impacto en el negocio

  • Secuestro del flujo de trabajo: mensajes de delegación suplantados redirigen a los agentes para realizar tareas no autorizadas
  • Interceptación de datos: datos sensibles intercambiados entre agentes son capturados por atacantes a nivel de red
  • Acciones no autorizadas: mensajes de aprobación reproducidos disparan despliegues, exportaciones de datos o transacciones financieras sin autorización legítima
  • Compromiso en cascada: un agente comprometido utiliza canales de confianza para comprometer a todos los agentes con los que se comunica

Escenarios de ataque

Un agente orquestador delega una tarea de análisis de datos sensibles a un agente de analítica especializado mediante una API HTTP no autenticada. Un atacante en la misma red envía una respuesta suplantada antes de que responda el verdadero agente de analítica, devolviendo resultados de análisis manipulados que provocan que el orquestador tome una decisión de negocio equivocada.

Dos agentes comunican resultados de tareas a través de una cola de mensajes sin cifrar. Un atacante a nivel de red captura mensajes que contienen PII de clientes, proyecciones financieras y datos de estrategia interna que se comparten entre un agente de investigación y un agente de generación de informes.

Un atacante captura un mensaje legítimo entre agentes que aprueba un despliegue de código. Horas después, el atacante reproduce el mensaje, disparando un despliegue no autorizado de una versión que posteriormente se descubrió que contenía vulnerabilidades. El agente receptor no tiene ningún mecanismo para detectar que el mensaje de aprobación ya no es vigente.

Mitigaciones

TLS mutuo (mTLS)

Autenticar y cifrar toda la comunicación entre agentes:

  • Emitir certificados X.509 únicos a cada instancia de agente desde una CA controlada por la organización
  • Exigir verificación de TLS mutuo: tanto el agente emisor como el receptor deben presentar certificados válidos
  • Aplicar el fijado de certificados (certificate pinning) para prevenir ataques de intermediario incluso si una CA se ve comprometida
  • Rotar los certificados según un calendario regular y revocar los certificados de agentes dados de baja

Firma de mensajes

Garantizar la integridad y el no repudio de los mensajes:

  • Firmar cada mensaje entre agentes usando la clave privada del agente emisor
  • Incluir un timestamp, un nonce y un número de secuencia de mensaje en el payload firmado para prevenir ataques de repetición
  • Los agentes receptores deben verificar la firma y rechazar mensajes con timestamps expirados o nonces reutilizados
  • Mantener un registro de auditoría de mensajes con firmas para análisis forense posterior a incidentes

Validación de esquema

Aplicar una estructura de mensaje estricta:

  • Definir esquemas JSON Schema o Protocol Buffer para cada tipo de mensaje intercambiado entre agentes
  • Rechazar mensajes que no cumplan con el esquema esperado — sin tolerancia para campos inesperados
  • Versionar los esquemas de mensajes y exigir que tanto el emisor como el receptor acuerden la versión
  • Tratar las violaciones de esquema como eventos de seguridad y alertar ante violaciones repetidas de un agente específico

Controles de delegación

Gobernar la delegación de tareas entre agentes:

  • Implementar una política de delegación explícita que defina qué agentes pueden delegar a qué otros agentes
  • Las solicitudes de delegación deben incluir la identidad del delegador, el alcance de la tarea y un token de autorización
  • Los agentes receptores deben verificar que están autorizados a aceptar delegaciones del agente solicitante
  • Rastrear las cadenas de delegación para prevenir la delegación circular y detectar patrones de delegación anómalos

Aislamiento de canales

Separar los canales de comunicación por sensibilidad:

  • Usar canales dedicados y cifrados para diferentes categorías de mensajes (plano de control frente a plano de datos)
  • Aplicar segmentación de red para que los agentes solo puedan alcanzar los agentes específicos con los que necesitan comunicarse
  • Implementar controles de acceso a nivel de mensaje: los agentes solo pueden leer mensajes dirigidos a ellos

Ejemplos de código

Sobre de mensaje firmado

import json
import time
import uuid
import hmac
import hashlib
from dataclasses import dataclass, asdict
from typing import Any, Optional


@dataclass
class MensajeAgente:
    id_emisor: str
    id_receptor: str
    tipo_mensaje: str
    payload: dict[str, Any]
    timestamp: float
    nonce: str
    secuencia: int
    firma: str = ""


class CanalMensajeSeguro:
    """Mensajería autenticada y con integridad verificada entre agentes."""

    VENTANA_REPETICION_SEGUNDOS = 300  # ventana de repetición de 5 minutos

    def __init__(self, id_agente: str, clave_firma: bytes):
        self._id_agente = id_agente
        self._clave_firma = clave_firma
        self._secuencia = 0
        self._nonces_vistos: dict[str, float] = {}  # nonce -> timestamp
        self._emisores_permitidos: set[str] = set()
        self._esquemas_mensaje: dict[str, dict] = {}

    def permitir_emisor(self, id_emisor: str):
        self._emisores_permitidos.add(id_emisor)

    def registrar_esquema(self, tipo_mensaje: str, esquema: dict):
        self._esquemas_mensaje[tipo_mensaje] = esquema

    def crear_mensaje(
        self, id_receptor: str, tipo_mensaje: str, payload: dict
    ) -> MensajeAgente:
        """Crea un mensaje firmado para su transmisión."""
        self._secuencia += 1
        msj = MensajeAgente(
            id_emisor=self._id_agente,
            id_receptor=id_receptor,
            tipo_mensaje=tipo_mensaje,
            payload=payload,
            timestamp=time.time(),
            nonce=uuid.uuid4().hex,
            secuencia=self._secuencia,
        )
        msj.firma = self._firmar(msj)
        return msj

    def verificar_mensaje(self, msj: MensajeAgente, clave_emisor: bytes) -> dict:
        """Verifica autenticidad, integridad, vigencia y cumplimiento de esquema."""
        verificaciones = {
            "emisor_permitido": False,
            "firma_valida": False,
            "no_repetido": False,
            "no_expirado": False,
            "esquema_valido": False,
            "dirigido_a_nosotros": False,
        }

        # Verificar receptor
        verificaciones["dirigido_a_nosotros"] = msj.id_receptor == self._id_agente
        if not verificaciones["dirigido_a_nosotros"]:
            return {"valido": False, "verificaciones": verificaciones, "razon": "El mensaje no está dirigido a este agente"}

        # Verificar que el emisor esté permitido
        verificaciones["emisor_permitido"] = msj.id_emisor in self._emisores_permitidos
        if not verificaciones["emisor_permitido"]:
            return {"valido": False, "verificaciones": verificaciones, "razon": f"El emisor '{msj.id_emisor}' no está en la lista permitida"}

        # Verificar la firma
        firma_esperada = self._calcular_firma(msj, clave_emisor)
        verificaciones["firma_valida"] = hmac.compare_digest(msj.firma, firma_esperada)
        if not verificaciones["firma_valida"]:
            return {"valido": False, "verificaciones": verificaciones, "razon": "Falló la verificación de la firma"}

        # Verificar repetición (unicidad del nonce)
        if msj.nonce in self._nonces_vistos:
            return {"valido": False, "verificaciones": verificaciones, "razon": "Se detectó un nonce repetido"}
        verificaciones["no_repetido"] = True

        # Verificar vigencia
        antiguedad = time.time() - msj.timestamp
        verificaciones["no_expirado"] = antiguedad <= self.VENTANA_REPETICION_SEGUNDOS
        if not verificaciones["no_expirado"]:
            return {"valido": False, "verificaciones": verificaciones, "razon": f"Mensaje expirado (antigüedad de {antiguedad:.0f}s)"}

        # Validación de esquema
        esquema = self._esquemas_mensaje.get(msj.tipo_mensaje)
        if esquema:
            verificaciones["esquema_valido"] = self._validar_esquema(msj.payload, esquema)
        else:
            verificaciones["esquema_valido"] = False
            return {"valido": False, "verificaciones": verificaciones, "razon": f"No hay esquema registrado para '{msj.tipo_mensaje}'"}

        # Registrar el nonce para prevenir repeticiones
        self._nonces_vistos[msj.nonce] = msj.timestamp
        self._limpiar_nonces_antiguos()

        return {"valido": True, "verificaciones": verificaciones}

    def _firmar(self, msj: MensajeAgente) -> str:
        return self._calcular_firma(msj, self._clave_firma)

    def _calcular_firma(self, msj: MensajeAgente, clave: bytes) -> str:
        firmable = f"{msj.id_emisor}|{msj.id_receptor}|{msj.tipo_mensaje}|{json.dumps(msj.payload, sort_keys=True)}|{msj.timestamp}|{msj.nonce}|{msj.secuencia}"
        return hmac.new(clave, firmable.encode(), hashlib.sha256).hexdigest()

    def _validar_esquema(self, payload: dict, esquema: dict) -> bool:
        # Simplificado: verifica que existan los campos requeridos y que no haya campos inesperados
        requeridos = set(esquema.get("required", []))
        permitidos = set(esquema.get("properties", {}).keys())
        presentes = set(payload.keys())
        return requeridos.issubset(presentes) and presentes.issubset(permitidos)

    def _limpiar_nonces_antiguos(self):
        corte = time.time() - self.VENTANA_REPETICION_SEGUNDOS * 2
        self._nonces_vistos = {
            n: t for n, t in self._nonces_vistos.items() if t > corte
        }

Requisitos de evidencia

  • Inventario de certificados mTLS con registros de emisión, rotación y revocación
  • Registros de verificación de firma de mensajes que muestran los mensajes aceptados y rechazados
  • Registros de detección de repetición con colisiones de nonces capturadas
  • Alertas de violación de esquema con atribución al emisor
  • Trazas de auditoría de cadenas de delegación que mapean el flujo de tareas entre agentes