Vulnerabilidades de la cadena de suministro agéntica
Descripción
La carga dinámica de herramientas, complementos y servidores MCP desde fuentes no confiables o no verificadas introduce código malicioso, capacidades de exfiltración de datos o puertas traseras en el entorno de ejecución del agente.
Riesgo
Panorama del riesgo
Los sistemas agénticos son inherentemente componibles — adquieren capacidades en tiempo de ejecución al conectarse a servidores de herramientas (MCP), cargar complementos e integrar API externas. Esta cadena de suministro dinámica es fundamentalmente distinta de las dependencias de software tradicionales: las herramientas se descubren, se cargan y se ejecutan en tiempo de ejecución, a menudo con base en descripciones en lenguaje natural en lugar de firmas de código verificadas. Una herramienta comprometida en esta cadena tiene acceso directo al contexto, las credenciales y las capacidades del agente.
Superficie de ataque
- Servidores MCP no verificados: los agentes se conectan a servidores MCP descubiertos mediante registros o recomendaciones sin verificar la identidad o la integridad del código del servidor
- Descripciones de herramientas maliciosas: descriptores de herramientas que describen con precisión una funcionalidad benigna, pero cuya implementación incluye exfiltración de datos oculta o recolección de credenciales
- Confusión de dependencias: los atacantes registran nombres de herramientas que se asemejan mucho a herramientas legítimas (typosquatting) en registros públicos
- Brechas en la fijación de versiones: las herramientas se actualizan automáticamente sin revisión, lo que permite que los atacantes que comprometen a un editor de herramientas publiquen actualizaciones maliciosas
- Dependencias transitivas: una herramienta confiable depende de subherramientas o bibliotecas no confiables, heredando sus vulnerabilidades
Impacto en el negocio
- Instalación de puertas traseras: las herramientas maliciosas establecen acceso persistente al entorno del agente
- Recolección de datos: las herramientas copian silenciosamente datos sensibles del contexto del agente hacia puntos de recolección externos
- Ataques de integridad: las herramientas devuelven resultados sutilmente alterados que provocan que los agentes tomen decisiones incorrectas (por ejemplo, una herramienta de datos financieros que distorsiona las cifras)
- Daño reputacional: la organización despliega agentes que usan herramientas posteriormente reveladas como maliciosas, lo que socava la confianza de los clientes
Escenarios de ataque
Los agentes de una organización se conectan a servidores MCP listados en un registro comunitario. Un atacante publica un servidor llamado 'enhanced-web-search' que imita una herramienta de búsqueda popular. La herramienta devuelve resultados de búsqueda legítimos, pero también registra cada consulta (incluidos datos sensibles del contexto del agente) en una base de datos externa. Decenas de organizaciones conectan sus agentes antes de que se descubra la exfiltración.
El pipeline de CI/CD de un editor legítimo de herramientas MCP es comprometido. El atacante publica una actualización de la herramienta 'database-query' que agrega una puerta trasera condicional: si la consulta contiene 'SELECT' y el nombre de la tabla coincide con un patrón, la herramienta también ejecuta una segunda consulta que exfiltra los resultados. La actualización pasa las pruebas automatizadas porque la puerta trasera solo se activa con patrones específicos.
Un agente está configurado para descubrir herramientas dinámicamente según los requisitos de la tarea. Cuando se le pide 'analizar esta hoja de cálculo', busca en un registro de herramientas y selecciona 'spreadsheet-analyzer-pro' — una versión typosquatted de la herramienta legítima 'spreadsheet-analyzer'. La herramienta maliciosa extrae todas las fórmulas y datos de la hoja de cálculo, incluidas credenciales incrustadas y fuentes de datos vinculadas, antes de devolver un análisis verosímil.
Mitigaciones
Registro de herramientas con firmas
Mantener un registro de herramientas curado y controlado por la organización:
- Exigir firmas criptográficas en todos los paquetes de herramientas mediante una cadena de certificados de firma de código controlada por la organización
- Firmar los descriptores de herramientas por separado de sus implementaciones, de modo que cualquier manipulación de uno u otro sea detectable
- Implementar la verificación de firmas en el momento de la carga — las herramientas sin firmar o con firmas incorrectas se rechazan de forma incondicional
- Publicar un registro de transparencia de todas las adiciones, actualizaciones y eliminaciones de herramientas
Fuentes en lista de permitidos
Restringir la adquisición de herramientas a fuentes verificadas:
- Mantener una lista de permitidos explícita de servidores MCP, repositorios de complementos y endpoints de API aprobados
- Bloquear la conexión de los agentes a fuentes de herramientas que no estén en la lista de permitidos, incluso si se descubren mediante búsquedas web o recomendaciones
- Exigir revisión y aprobación de seguridad antes de agregar nuevas fuentes a la lista de permitidos
- Implementar controles a nivel de DNS para impedir conexiones a servidores de herramientas no aprobados
Verificaciones de integridad en tiempo de ejecución
Verificar la integridad de las herramientas de forma continua, no solo en el momento de la instalación:
- Calcular el hash de los binarios y configuraciones de las herramientas en el momento de la carga y compararlo con los hashes registrados en el registro
- Monitorear el comportamiento de las herramientas frente a las capacidades declaradas — señalar las herramientas que realicen conexiones de red, accesos a archivos o llamadas al sistema no descritas en su manifiesto
- Implementar entradas canario: enviar periódicamente a las herramientas consultas de prueba con salida conocida y verificar que los resultados coincidan con la salida esperada
- Usar sandboxing en tiempo de ejecución para limitar el acceso de las herramientas únicamente a los recursos declarados en su manifiesto de capacidades
Fijación de versiones y revisión
Prevenir que las actualizaciones silenciosas introduzcan vulnerabilidades:
- Fijar todas las versiones de las herramientas en la configuración del agente y exigir una revisión explícita para los incrementos de versión
- Implementar un despliegue por etapas: las nuevas versiones de las herramientas se despliegan primero a un grupo de agentes de prueba antes de producción
- Comparar los descriptores y el comportamiento de las herramientas entre versiones y señalar los cambios inesperados
- Mantener la capacidad de reversión para volver de inmediato a una versión anterior de la herramienta
Inventario de la cadena de suministro
Mantener un inventario completo de materiales (bill of materials) de la cadena de herramientas del agente:
- Registrar cada herramienta, su versión, fuente, estado de la firma y fecha de última verificación
- Mapear las dependencias transitivas para que los compromisos en las subherramientas sean trazables
- Realizar auditorías periódicas de la cadena de suministro que comparen el conjunto de herramientas activo del agente con el inventario aprobado
Ejemplos de código
Validador de Registro de Herramientas
import hashlib
import json
import time
from dataclasses import dataclass
from typing import Optional
from enum import Enum
class EstadoVerificacion(Enum):
VERIFICADO = "verificado"
FIRMA_NO_COINCIDE = "firma_no_coincide"
NO_EN_REGISTRO = "no_en_registro"
EXPIRADO = "expirado"
REVOCADO = "revocado"
@dataclass
class ManifiestoHerramienta:
nombre: str
version: str
editor: str
url_origen: str
hash_descriptor: str
hash_binario: str
firma: str
capacidades: list[str] # declarado: ["network:read", "filesystem:none"]
aprobado_en: float
expira_en: float
class ValidadorRegistroHerramientas:
"""Valida herramientas contra un registro controlado por la organización."""
def __init__(self):
self._herramientas_aprobadas: dict[str, ManifiestoHerramienta] = {}
self._fuentes_permitidas: set[str] = set()
self._revocados: set[str] = set()
def registrar_herramienta_aprobada(self, manifiesto: ManifiestoHerramienta):
clave = f"{manifiesto.nombre}@{manifiesto.version}"
self._herramientas_aprobadas[clave] = manifiesto
def agregar_fuente_permitida(self, url_origen: str):
self._fuentes_permitidas.add(url_origen)
def revocar_herramienta(self, nombre: str, version: str):
self._revocados.add(f"{nombre}@{version}")
def verificar_herramienta(
self,
nombre: str,
version: str,
url_origen: str,
contenido_descriptor: str,
contenido_binario: bytes,
) -> dict:
"""Verificación completa: origen, registro, hashes, expiración."""
clave = f"{nombre}@{version}"
resultado = {"herramienta": clave, "verificaciones": {}}
# Verificar la lista de fuentes permitidas
resultado["verificaciones"]["origen_permitido"] = url_origen in self._fuentes_permitidas
if not resultado["verificaciones"]["origen_permitido"]:
resultado["estado"] = EstadoVerificacion.NO_EN_REGISTRO.value
resultado["bloqueado"] = True
return resultado
# Verificar revocación
if clave in self._revocados:
resultado["estado"] = EstadoVerificacion.REVOCADO.value
resultado["bloqueado"] = True
return resultado
# Verificar que exista la entrada en el registro
manifiesto = self._herramientas_aprobadas.get(clave)
if not manifiesto:
resultado["estado"] = EstadoVerificacion.NO_EN_REGISTRO.value
resultado["bloqueado"] = True
return resultado
# Verificar expiración
if time.time() > manifiesto.expira_en:
resultado["estado"] = EstadoVerificacion.EXPIRADO.value
resultado["bloqueado"] = True
return resultado
# Verificar el hash del descriptor
hash_desc_real = hashlib.sha256(contenido_descriptor.encode()).hexdigest()
resultado["verificaciones"]["hash_descriptor"] = hash_desc_real == manifiesto.hash_descriptor
# Verificar el hash del binario
hash_bin_real = hashlib.sha256(contenido_binario).hexdigest()
resultado["verificaciones"]["hash_binario"] = hash_bin_real == manifiesto.hash_binario
todos_los_hashes_coinciden = (
resultado["verificaciones"]["hash_descriptor"] and resultado["verificaciones"]["hash_binario"]
)
if todos_los_hashes_coinciden:
resultado["estado"] = EstadoVerificacion.VERIFICADO.value
resultado["bloqueado"] = False
else:
resultado["estado"] = EstadoVerificacion.FIRMA_NO_COINCIDE.value
resultado["bloqueado"] = True
return resultado
def obtener_inventario(self) -> list[dict]:
"""Devuelve el inventario completo de la cadena de suministro de herramientas para auditoría."""
return [
{
"herramienta": clave,
"editor": m.editor,
"origen": m.url_origen,
"aprobado_en": m.aprobado_en,
"expira_en": m.expira_en,
"revocado": clave in self._revocados,
}
for clave, m in self._herramientas_aprobadas.items()
]
Requisitos de evidencia
- Registro de herramientas con manifiestos firmados y pista de auditoría de aprobaciones
- Lista de materiales de la cadena de suministro (SBOM) para todas las dependencias de herramientas del agente
- Registros de verificación de integridad que muestran comprobaciones de hash en cada carga de herramienta
- Historial de cambios de la lista de fuentes permitidas con atribución del aprobador
- Resultados de pruebas canario que validan la consistencia de la salida de las herramientas a lo largo del tiempo