CVE
CVE-2026-12481
CWE
CWE-502
Sistemas afectados
- Paquete PyPI `keras` en la línea 3.14.x, con evidencia de código fuente público para 3.14.0 y 3.14.1
- Servicios de inferencia, notebooks, trabajos de conversión de modelos y herramientas internas de ML que deserializan configuraciones `Lambda` de Keras controladas por el atacante
- Aplicaciones que llaman a `keras.layers.deserialize(config)`, `keras.models.clone_model(model)` o `keras.layers.Lambda.from_config(config)` sobre entradas de menor confianza
- Equipos que dependen del modo seguro de Keras como el único límite entre contenido de modelo no confiable y la ejecución de Python
CVE-2026-12481 importa porque rompe un límite en el que se dice explícitamente a los usuarios de Keras que confíen: la deserialización en modo seguro de las capas Lambda. La divulgación del 3 de julio muestra que, en la lógica vulnerable de la 3.14.x, un valor de safe_mode sin definir puede colapsar a un valor falsy en lugar de a un valor predeterminado que falle de forma cerrada. Cuando eso ocurre, Keras deja de tratar una lambda serializada como peligrosa y pasa bytecode controlado por el atacante a la ruta del cargador que reconstruye objetos función de Python.
Esto no es un fallo de “analizamos unos metadatos de modelo raros”. Es un fallo de materialización de bytecode en un paquete que vive dentro de notebooks, registros de modelos, servicios de inferencia, trabajos de conversión de CI y herramientas internas de ML. Si tu aplicación acepta o clona contenido de modelo de Keras de menor confianza, esto pertenece a la misma lista de revisión que cualquier otra superficie de ejecución de código impulsada por deserialización.
Paquete afectado y límite de versión
El paquete afectado es:
kerasen PyPI
El texto público del aviso nombra la 3.14.0, pero el límite de versión visible públicamente es un poco más sutil que eso.
| Paquete | Ecosistema | Versiones afectadas con mayor confianza según artefactos públicos | Versión corregida con mayor confianza según artefactos públicos |
|---|---|---|---|
keras | PyPI | 3.14.0, 3.14.1 | 3.15.0 |
Por qué este límite es defendible:
- la entrada de NVD nombra
keras-team/keras version 3.14.0 - el código fuente público de la
v3.14.1todavía contiene el fallback vulnerablesafe_mode = safe_mode or serialization_lib.in_safe_mode() - el código fuente público de la
v3.15.0sustituye esa línea por un cálculo deeffective_safe_modeque falla de forma cerrada - PyPI muestra la
3.15.0como la última línea de lanzamiento disponible en el momento de la publicación
Así que, aunque los metadatos del aviso importados a NVD actualmente tienen una forma genérica affected <= latest, la evidencia a nivel de código fuente señala a 3.14.0 y 3.14.1 como las versiones públicas de PyPI expuestas y a 3.15.0 como la primera línea que claramente remedia el problema.
La condición de exposición práctica es más estrecha que “cualquier entorno con Keras instalado”, pero sigue siendo común:
- un servicio o notebook carga contenido de modelo de Keras de usuarios, socios o almacenamiento compartido
- el código llama a
keras.layers.deserialize(config)okeras.models.clone_model(model)sobre entradas de menor confianza - herramientas o automatización interna invocan
Lambda.from_config()directamente sin envolverlo enSafeModeScope(True) - los equipos asumen que el comportamiento predeterminado de
safe_modees suficiente para bloquear que el bytecode de la lambda se convierta en Python ejecutable
La ruta de código vulnerable es corta y directa
El código fuente de la 3.14.1 muestra el fallo central con claridad:
@classmethod
def from_config(cls, config, custom_objects=None, safe_mode=None):
safe_mode = safe_mode or serialization_lib.in_safe_mode()
fn_config = config["function"]
if (
isinstance(fn_config, dict)
and "class_name" in fn_config
and fn_config["class_name"] == "__lambda__"
):
cls._raise_for_lambda_deserialization(safe_mode)
inner_config = fn_config["config"]
fn = python_utils.func_load(
inner_config["code"],
defaults=inner_config["defaults"],
closure=inner_config["closure"],
)
config["function"] = fn
Hay dos detalles significativos para la seguridad aquí:
safe_modese deriva con la semántica de veracidad (truthiness) de Python en lugar de una prueba estrictais not None.- Si la comprobación no se dispara, el código reconstruye inmediatamente la lambda serializada mediante
python_utils.func_load(...).
El texto de NVD señala exactamente el estado incorrecto: safe_mode es None fuera de un contexto SafeModeScope, y la implementación trata ese estado sin definir como si la seguridad estuviera efectivamente desactivada. _raise_for_lambda_deserialization() solo bloquea cuando ve un valor truthy:
@staticmethod
def _raise_for_lambda_deserialization(safe_mode):
if safe_mode:
raise ValueError(...)
Así que el flujo peligroso es:
safe_mode=None
-> in_safe_mode() devuelve un valor sin definir / falsy
-> _raise_for_lambda_deserialization() no lanza excepción
-> func_load() reconstruye la función controlada por el atacante
Ese es exactamente el tipo de deslizamiento lógico de “denegar por defecto se convirtió en denegar-si-es-falsy” que convierte una barrera de protección en un bypass.
Por qué importa func_load(): esto es bytecode ejecutable, no configuración inofensiva
La ruta del cargador posterior en el mismo lanzamiento de Keras muestra lo que ocurre después:
def func_load(code, defaults=None, closure=None, globs=None):
...
raw_code = codecs.decode(code.encode("ascii"), "base64")
code = marshal.loads(raw_code)
if globs is None:
globs = globals()
return python_types.FunctionType(
code, globs, name=code.co_name, argdefs=defaults, closure=closure
)
Por eso el fallo es significativo. Una vez que la comprobación de Lambda se elude, Keras no se limita a preservar datos opacos. En su lugar:
- decodifica en base64 los bytes de código serializados
- los des-marshala con
marshal.loads(...) - envuelve el objeto de código resultante en
types.FunctionType(...)
En ese punto, la carga útil del modelo ha pasado de configuración serializada a comportamiento de Python ejecutable.
La propia documentación de Keras ya señala que Lambda es una superficie de serialización peligrosa:
- la documentación de
Lambdaadvierte que las capas se guardan serializando bytecode de Python y son “potencialmente inseguras” - la documentación de serialización dice que
safe_mode=Trueestá pensado para deshabilitar la deserialización insegura de lambdas
CVE-2026-12481 importa porque la implementación no cumplió ese contrato en el caso de modo seguro sin definir.
Los puntos de llamada afectados son más comunes de lo que parecen
El aviso no limita la exposición a las llamadas completas a load_model(). La descripción pública nombra explícitamente:
keras.layers.deserialize(config)keras.models.clone_model(model)- el uso directo de
Lambda.from_config(config)
Eso hace que el fallo sea relevante más allá de las funciones públicas de subida de modelos. Revisa de cerca estos patrones:
- pipelines internos de conversión de modelos que deserializan objetos de configuración de capas
- utilidades de notebook que clonan o reescriben modelos antes de exportarlos
- componentes de plataformas de ML que normalizan arquitecturas enviadas por usuarios
- arneses de pruebas que cargan fixtures de modelos compartidos desde repositorios o artefactos de menor confianza
- trabajos en segundo plano que inspeccionan o transforman configuraciones de modelos guardados antes de servirlos
Los entornos de mayor riesgo son los que los equipos de seguridad de aplicaciones a menudo pasan por alto:
- servidores Jupyter con credenciales en la nube
- workers de compilación de modelos que pueden leer conjuntos de datos de entrenamiento o secretos
- planos de control de inferencia que aceptan paquetes de modelos subidos o sincronizados
- trabajos de CI que hacen benchmark, convierten o vuelven a guardar automáticamente modelos de Keras de terceros
Si un atacante puede mover una configuración de lambda manipulada a uno de esos procesos, el límite de confianza ya no es “metadatos de ML”. Es “código Python ejecutándose dentro del mismo proceso que ya contiene tus tokens, rutas de datos y acceso al sistema de archivos”.
Qué cambió en la 3.15.0
El código fuente público de la v3.15.0 muestra la corrección de diseño importante:
effective_safe_mode = (
safe_mode
if safe_mode is not None
else serialization_lib.in_safe_mode()
)
safe_mode = effective_safe_mode is not False
Esa es una corrección de seguridad real, no una refactorización cosmética.
El nuevo comportamiento hace dos cosas que el código antiguo no hacía:
- distingue “sin definir” de “explícitamente deshabilitado”
- falla de forma cerrada tratando cualquier valor distinto de
Falseexplícito como modo seguro habilitado
El comentario que acompaña en el código fuente es directo y útil:
# ... treating an unset scope as safe. This fails closed so that
# deserializing a `Lambda` outside of a `SafeModeScope` ... does not
# silently allow arbitrary code execution.
Esa es exactamente la propiedad de seguridad que faltaba en la implementación de la 3.14.x.
Delimitación y detección
Empieza identificando si están presentes versiones afectadas de Keras:
python3 -m pip show keras
python3 -m pip freeze | rg '^keras=='
uv pip tree | rg '^keras '
poetry show keras
Luego busca rutas de código que puedan deserializar contenido de modelo de menor confianza:
rg -n 'clone_model\\(|Lambda\\.from_config\\(|layers\\.deserialize\\(' .
rg -n 'load_model\\(|deserialize_keras_object\\(' .
rg -n 'safe_mode=False|enable_unsafe_deserialization\\(' .
La pregunta de revisión más importante es:
¿Puede una parte de menor confianza influir en la configuración o el artefacto de modelo
que finalmente llega a la deserialización de Lambda en este proceso?
Presta especial atención a:
- el manejo de modelos
.kerassubidos - registros internos compartidos de modelos
- herramientas de benchmark o evaluación que ingieren modelos de la comunidad
- notebooks de Jupyter que cargan modelos desde almacenamiento de objetos o repositorios Git
- clonación automatizada de modelos o reescritura de grafos en CI
Si descubres que se procesó material de modelo no confiable a través de estas rutas en la 3.14.x, trata el host como si potencialmente hubiera ejecutado Python suministrado por el atacante.
Remediación
La corrección limpia es sencilla:
- Actualiza
kerasa la3.15.0o posterior. - Elimina o aísla el uso directo de
Lambda.from_config()sobre entradas de menor confianza. - Evita
safe_mode=Falsey evita llamar globalmente akeras.config.enable_unsafe_deserialization()a menos que confíes plenamente en el origen del modelo. - Prefiere capas subclasificadas con lógica explícita de
get_config()en lugar de bytecodeLambdaserializado siempre que controles el diseño del modelo. - Trata los artefactos de modelo de terceros como contenido ejecutable, no como archivos de datos inertes.
Si no puedes actualizar de inmediato, la regla más segura a corto plazo es simple:
modelo o configuración no confiable
-> no deserializar bytecode de Lambda en el proceso
Eso puede significar rechazar modelos que contengan lambdas serializadas, mover la inspección de modelos a un sandbox reforzado, o exigir procedencia firmada para los artefactos aceptados.
Guía de respuesta
Si un entorno afectado de la 3.14.x cargó contenido de modelo de Keras de menor confianza:
- Asume que puede haberse ejecutado Python controlado por el atacante en el mismo proceso.
- Rota las credenciales alcanzables desde ese host, incluidos tokens en la nube, claves de acceso a conjuntos de datos, secretos de CI y tokens de repositorio.
- Revisa los registros de notebooks, workers y servicios en busca de cargas de modelos, operaciones de clonación e ingesta sospechosa de configuraciones durante la ventana de exposición.
- Vuelve a auditar cualquier herramienta interna que use
safe_mode=Falseo que deshabilite globalmente la deserialización segura. - Reconstruye o reimagina los workers de procesamiento de modelos de alto valor si no puedes acotar con confianza qué se ejecutó.
La lección clave de AppSec de CVE-2026-12481 es que los cargadores de modelos de ML son cargadores de código siempre que serialicen objetos ejecutables nativos del lenguaje. En Keras, Lambda ya se situaba en ese límite. La divulgación de julio de 2026 muestra cómo un único fallback falsy en la ruta de comprobación fue suficiente para convertir “inseguro a menos que se permita explícitamente” en “inseguro a menos que quien llama recordara establecer el ámbito”.
De la investigación a la remediación
Comprueba si este patrón existe en tu código
Convierte esta investigación en un flujo de remediación. Analiza dependencias y manifiestos para detectar riesgos similares en la cadena de suministro y prioriza las correcciones con contexto de alcanzabilidad.
Referencias
- NVD: CVE-2026-12481
- Listado en huntr: keras-team/keras
- Documentación de las utilidades de serialización de Keras
- Documentación de la capa Lambda de Keras
- Código fuente vulnerable de Lambda.from_config en Keras v3.14.1
- Código fuente de func_load en Keras v3.14.1
- Código fuente de Lambda.from_config con fallo cerrado en Keras v3.15.0
- Metadatos del paquete en PyPI: keras