
Divulgación detallada de CVE-2025-68664, una vulnerabilidad crítica de deserialización en el núcleo de LangChain que permite la exfiltración de secretos y potencial RCE a través de indicaciones manipuladas y flujos de serialización.
Autor: Yarden Porat
Investigación de Cyata: Vulnerabilidad LangGrinch en LangChain
Publicado: https://cyata.ai/blog/langgrinch-langchain-core-cve-2025-68664/

Ayer, LangChain publicó una advertencia crítica sobre una vulnerabilidad que descubrí en langchain-core: CVE-2025-68664 / GHSA-c67j-w6g6-q2cm.
A principios de este año, mi investigación se centró en la explotación de administradores de secretos en nuestro trabajo "Vault Fault" – sistemas diseñados específicamente como un perímetro de seguridad alrededor de tus credenciales más sensibles. Una conclusión se repetía una y otra vez: cuando una plataforma procesa accidentalmente datos formados por un atacante como una estructura de confianza, ese perímetro se derrumba rápidamente. Esta vez, el sistema que se "rompe" no es tu administrador de secretos. Es el framework de agentes que podría usarlos.
¿Por qué esta vulnerabilidad merece especial atención?
Está en el núcleo. No es un error de una herramienta específica, ni un caso extremo de integración, ni "un paquete comunitario que hizo algo extraño". Las API vulnerables (dumps() / dumpd()) se encuentran en el propio langchain-core.
El radio de impacto es enorme. Por volumen de descargas, langchain es uno de los componentes de framework de IA más ampliamente desplegados en el mundo hoy. A finales de diciembre de 2025, la telemetría pública de paquetes muestra , con pepy.tech reportando y pypistats mostrando .
Un solo prompt puede desencadenar muchos mecanismos. El camino real más común aquí no es "el atacante te envía un blob serializado y tú llamas a load()". Es más sutil: las salidas de los LLM pueden influir en campos como additional_kwargs o response_metadata, y estos campos pueden ser serializados y luego deserializados a través de funciones normales del framework, como eventos/logs de streaming. En palabras simples, esto significa que un exploit puede ser lanzado con un solo prompt de texto, que en cascada llega a un pipeline interno inesperadamente complejo.
Antes de que sigas leyendo, los parches ya han sido publicados en las versiones 1.2.5 y 0.3.81. Si usas LangChain en producción, esto es más complicado de lo que parece; por favor, actualiza lo antes posible.
LangChain utiliza un formato de serialización interno especial, donde los diccionarios que contienen el marcador 'lc' representan objetos de LangChain. La vulnerabilidad consistía en que dumps() y dumpd() no escapaban correctamente los diccionarios controlados por el usuario que incluían accidentalmente la clave reservada 'lc'.
Por lo tanto, una vez que un atacante pueda forzar al ciclo de orquestación de LangChain a serializar y luego deserializar contenido que incluya la clave 'lc', podrá instanciar un objeto arbitrario inseguro, potencialmente desencadenando múltiples caminos favorables para el atacante.
La advertencia enumera 12 flujos vulnerables diferentes que son extremadamente comunes en casos de uso reales, como el streaming estándar de eventos, logging, historial de mensajes/memoria o caché:

Las consecuencias más devastadoras incluyen:
Extracción de secretos de variables de entorno. La advertencia señala que esto ocurre al deserializar con secrets_from_env=True. Notablemente, esto era el valor predeterminado hasta ayer. 🙂
Instanciación de objetos en espacios de nombres previamente aprobados (incluyendo langchain_core, langchain_openai, langchain_aws, langchain_anthropic…), potencialmente provocando efectos secundarios en los constructores (llamadas de red, operaciones con archivos, etc.).
Bajo ciertas condiciones, la instanciación de objetos LangChain puede conducir a la ejecución arbitraria de código.
Esto se clasifica bajo CWE-502: Deserialización de datos no confiables, con una puntuación CVSS CNA de 9.3 (Crítica).
En vísperas de Navidad, estaba haciendo el trabajo menos festivo: mirando el código de serialización y preguntándome "espera… ¿por qué esto se considera de confianza?"
La investigación de seguridad a menudo se ve dramática desde fuera. En realidad, suele ser lectura minuciosa, pequeñas hipótesis y una lenta acumulación de momentos de "esto es extraño".
Comenzó como muchas en Cyata: con una pregunta simple que hacemos constantemente al evaluar stacks de IA para riesgo real:
¿Dónde están los límites de confianza en las aplicaciones de IA, y realmente saben los desarrolladores dónde están esos límites?
LangChain es un framework potente, y como la mayoría de los frameworks modernos, necesita mover datos estructurados complejos: mensajes, llamadas a herramientas, eventos de streaming, trazas, cachés y "runnables".
Revisando investigaciones anteriores, ya había un extenso trabajo sobre herramientas e integraciones de LangChain, pero muy pocos hallazgos en la biblioteca principal.
Empecé la investigación trabajando hacia atrás. Encontrar lugares interesantes (sumideros), luego descubrir cómo un atacante podría alcanzarlos. La deserialización era un objetivo obvio.
Me llevó bastante tiempo encontrar algo significativo. Pero después de un tiempo, encontré que, asumiendo un primitivo de deserialización controlado por el atacante, podía lanzar un SSRF ciego que podría usarse para la exfiltración de variables de entorno (se detallará pronto). Como el resultado estaba limitado a la exfiltración de secretos y no a mi objetivo principal de RCE, seguí auditando la deserialización y me tomé mi tiempo.
El bug no era un trozo de código malo, era la ausencia de código. dumps() simplemente no escapaba los diccionarios controlados por el usuario que contenían claves 'lc'. Escape faltante en la ruta de serialización, no de deserialización.

Es mucho más fácil notar algo incorrecto que notar la ausencia de algo, especialmente cuando estás auditando load(), no dumps(). En uno de los frameworks de IA más exhaustivamente auditados. Dos años y medio.
A partir de ahí, la investigación se convirtió en un ejercicio estructurado:
Identificar dónde el contenido no confiable (principalmente diccionarios arbitrarios) entra en la serialización (salidas de LLM, inyección de prompts, entrada del usuario, herramientas externas, documentos extraídos).
Identificar cuándo esos datos serializados se deserializan.
Determinar qué puede lograr un atacante con la instanciación arbitraria de objetos.
En ese momento, el hallazgo principal era lo suficientemente claro y accionable para un informe responsable: había una brecha de escape en dumps() / dumpd() alrededor de diccionarios con la clave 'lc'.
La advertencia posterior capturó lo que a menudo vemos en la práctica: campos como additional_kwargs y response_metadata pueden ser influenciados por la salida del LLM y la inyección de prompts, y estos campos pueden serializarse y deserializarse en muchos flujos.
En honor al equipo de LangChain: la respuesta y las acciones posteriores fueron decididas, no solo parcheando el bug, sino también endureciendo los valores predeterminados que eran demasiado permisivos para el mundo en el que vivimos ahora.
El proyecto LangChain decidió otorgar una recompensa de $4,000 USD por este hallazgo. Según huntr, la plataforma donde LangChain realizaba su programa de recompensas, esta sería la cantidad máxima jamás otorgada en el proyecto, con recompensas hasta ahora de hasta $125.
LangChain serializa ciertos objetos usando un formato de diccionario estructurado. La clave 'lc' se usa internamente para indicar "esto es una estructura LangChain serializada", no solo datos de usuario arbitrarios.
Este es un patrón común, pero crea un invariante de seguridad: cualquier dato de usuario que pueda contener 'lc' debe manejarse con cuidado. De lo contrario, un atacante podría crear un diccionario que "se parezca" a un objeto interno y engañar al deserializador para que le dé valor.
El parche hace explícita la intención en la documentación actualizada: durante la serialización, los diccionarios simples que contienen la clave 'lc' se escapan envolviéndolos.
Esto evita que estos diccionarios se confundan con objetos LangChain serializados reales durante la deserialización.
Las funciones load()/loads() de LangChain no instancian clases arbitrarias – verifican contra una lista blanca que controla qué clases pueden deserializarse. Por defecto, esta lista blanca incluye clases de langchain_core, langchain_openai, langchain_aws y otros paquetes del ecosistema.
Aquí está el truco: la mayoría de las clases en la lista blanca tienen constructores inofensivos. Encontrar rutas explotables requirió cavar en el ecosistema en busca de clases que hagan algo significativo al ser instanciadas. Las que encontré se detallan a continuación, pero puede haber otras esperando ser descubiertas.
La función loads() de LangChain admite un tipo secret, que resuelve valores de variables de entorno durante la deserialización. Antes del parche, esta función secrets_from_env estaba habilitada por defecto:
if (
value.get("lc") == 1
and value.get("type") == "secret"
and value.get("id") is not None
):
[key] = value["id"]
if key in self.secrets_map:
return self.secrets_map[key]
if self.secrets_from_env and key in os.environ and os.environ[key]:
return os.environ[key] # <-- Возвращение переменной окружения
return None
Si el objeto deserializado se devuelve al atacante, por ejemplo un historial de mensajes dentro del contexto del LLM, esto podría filtrar variables de entorno.
Pero el camino más interesante es la inyección de prompt indirecta. Incluso un atacante que no puede ver ninguna respuesta del LLM puede exfiltrar secretos instanciando la clase correcta. ChatBedrockConverse de langchain_aws está tanto en la lista blanca predeterminada de loads como realiza una solicitud GET al ser construido. El endpoint GET está controlado por el atacante, y una cabecera HTTP específica puede llenarse con una variable de entorno a través de la función secrets_from_env.

Este validador se ejecuta cuando se instancia ChatBedrockConverse. El atacante controla endpoint_url, lanzando una solicitud saliente. En combinación con secrets_from_env, la cabecera aws_access_key_id puede llenarse con cualquier variable de entorno – no solo claves de AWS.
No publicamos deliberadamente un exploit completo aquí para dar tiempo a los equipos de seguridad. En unos meses, el sitio de Huntr los publicará automáticamente.
Entre las clases en la lista blanca predeterminada de loads() se encuentra PromptTemplate. Esta clase crea un prompt a partir de una plantilla, y uno de los formatos de plantilla disponibles es Jinja2.
Cuando la plantilla se renderiza con Jinja2, se puede ejecutar código arbitrario de Python. No encontramos una forma de ejecutar esto directamente solo desde la función loads(), pero si una llamada posterior al objeto deserializado desencadena el renderizado, se produce la ejecución de código.
Sospechamos que pueden existir caminos hacia la ejecución directa de código desde loads(), pero aún no hemos confirmado ninguno. Si tienes una idea sólida o una pista digna de probar, nos encantaría escucharla – este es exactamente el lugar donde la comunidad de seguridad ayuda a convertir hipótesis en pruebas. 🤝
También vale la pena señalar: en versiones anteriores, la clase Chain también estaba en la lista blanca. Esta clase tenía capacidades especiales que podrían permitir un flujo hacia el renderizado de plantillas.
Tu aplicación es potencialmente vulnerable si utiliza versiones vulnerables de langchain-core. Estos son algunos de los patrones vulnerables más comunes (se identificaron 12 flujos en total):
Sin embargo, el comportamiento del sistema es lo suficientemente complejo como para que sea arriesgado asumir que una revisión rápida del código revelará todas las variantes alcanzables. Lo más seguro es actualizar a la versión parcheada y no asumir que estás seguro hasta que lo hagas.
Además, la advertencia señala lo que considero el punto más importante del mundo real:
El vector de ataque más común ocurre a través de campos de respuesta del LLM como additional_kwargs o response_metadata, que pueden ser controlados mediante inyección de prompts y luego serializarse/deserializarse en operaciones de streaming.
Este es exactamente el tipo de intersección "IA se encuentra con seguridad clásica" donde las organizaciones son tomadas por sorpresa. La salida del LLM es entrada no confiable. Si tu framework procesa porciones de esa salida como objetos estructurados más adelante, debes asumir que los atacantes intentarán darles forma.
Actualiza langchain-core a la versión parcheada. Si usas langchain, langchain-community u otros paquetes del ecosistema, verifica qué versión de langchain-core está realmente instalada en los entornos de producción.
Trata additional_kwargs, response_metadata, salidas de herramientas, documentos extraídos e historial de mensajes como no confiables, a menos que se demuestre lo contrario. Esto es especialmente importante si transmites logs/eventos y luego los rehidratas con el cargador.
Incluso después de la actualización, adhiérete al principio: no habilites la resolución de secretos desde variables de entorno a menos que confíes en la entrada serializada. El proyecto cambió los valores predeterminados por una razón.
Basado en mi informe, hay una advertencia estrechamente relacionada en LangChainJS (GHSA-r399-636x-v7f6 / CVE-2025-68665) con mecanismos similares: confusión del marcador 'lc' durante la serialización, permitiendo la extracción de secretos e instanciación insegura en ciertas configuraciones.
Si tu organización ejecuta tanto stacks de Python como de JavaScript de LangChain, considera esto como un recordatorio de que el patrón se extiende entre ecosistemas: serialización de marcadores, salida de modelo no confiable y deserialización posterior es una forma de riesgo recurrente.
Estamos entrando en una fase donde los frameworks de agentes de IA se están convirtiendo en infraestructura crítica dentro de los sistemas de producción. Los formatos de serialización, los pipelines de orquestación, la ejecución de herramientas, los cachés y el trazado ya no son "fontanería" – son parte de tu perímetro de seguridad.
Esta vulnerabilidad no es "solo un bug en una biblioteca". Es un caso de estudio de un gran patrón:
Tu aplicación puede deserializar datos que cree producidos de forma segura.
Pero esa salida serializada puede contener campos influenciados por fuentes no confiables (incluyendo salidas de LLM moldeadas por inyección de prompts).
Una sola clave reservada utilizada como marcador interno puede convertirse en un punto de giro hacia secretos y comportamientos cercanos a la ejecución.
En Cyata, nuestro trabajo es ayudar a las organizaciones a construir visibilidad, evaluación de riesgos, control y gobernanza alrededor de los sistemas de IA – porque si no puedes responder rápidamente dónde se ejecutan los agentes, qué versiones están desplegadas y qué datos fluyen a través de ellos, efectivamente estás volando a ciegas cuando advertencias como esta aterrizan.
Si eres un líder de seguridad leyendo esto, aquí está la verdad incómoda:
La mayoría de las organizaciones hoy no pueden responder, de forma rápida y segura:
¿Dónde usamos agentes?
¿Qué versiones están desplegadas en producción?
¿Qué servicios tienen acceso a secretos sensibles?
¿Dónde las salidas del LLM cruzan esos límites?
Esto no es un "problema del desarrollador". Es un problema de visibilidad y gobernanza.
Y aquí es donde entra Cyata.
En Cyata nos enfocamos en resultados prácticos: reducir el riesgo de IA y agentes sin frenar a los constructores. Vulnerabilidades como esta rara vez son "solo un parche". Revelan brechas en cómo los equipos detectan dónde se ejecutan los agentes, comprenden los límites de confianza reales y aseguran valores predeterminados más seguros en frameworks de ritmo rápido.
Conoce qué se está ejecutando, dónde y cómo está conectado.
Responde rápidamente a la primera pregunta del CVE: ¿estamos vulnerables, y en qué flujos?
Detecta entornos de ejecución de agentes e integraciones entre entornos (IDEs, CI, servicios, trabajos, agentes de hosting).
Rastrea frameworks, paquetes y versiones en uso.
Prioriza lo que importa basado en el radio de impacto real, no solo "biblioteca presente".
Mantén un triaje más rápido: qué está expuesto a Internet, qué toca secretos, qué se ejecuta con privilegios elevados.
Identifica las rutas de mayor riesgo: contenido no confiable que fluye hacia contextos privilegiados (servicios con secretos, permisos amplios de herramientas, acceso de red de producción).
Destaca dónde los "campos estructurados" pueden cruzar límites de confianza (metadatos, salidas de herramientas, eventos de streaming, artefactos en caché).
Reduce la exposición incluso antes de que cada dependencia esté parcheada en todas partes.
Fomenta valores predeterminados operativos más seguros: privilegios mínimos, límites de aislamiento y controles de políticas que escalen entre equipos.
Implementa puertas de enlace alrededor de patrones riesgosos (ej.: deserialización de datos no confiables, recreación permisiva de objetos, flujos inseguros de streaming-a-caché-a-rehidratación).
Restringe o limita capacidades sensibles en contextos no confiables (ej.: acceso a secretos del entorno, ejecución de herramientas con altos privilegios o ejecución de rutas de código riesgosas en workers privilegiados).
Haz que el "uso seguro de agentes" sea repetible, auditable y difícil de desviar.
Define políticas para frameworks, versiones y configuraciones aprobados.
Rastrea y limita en el tiempo las excepciones con propietarios y justificación.
Monitorea la desviación y el uso riesgoso de funciones a lo largo del tiempo, con un rastro de auditoría que respalde las revisiones de seguridad y el cumplimiento.
Cuando llega la advertencia navideña, el objetivo no es el heroísmo – es una respuesta tranquila y controlada, respaldada por un inventario real y puertas de enlace aseguradas.
Informe enviado a través de Huntr – 4 de diciembre de 2025
Reconocido por los mantenedores de LangChain – 5 de diciembre de 2025
Advertencia y CVE publicados – 24 de diciembre de 2025