VYPR
High severity8.8NVD Advisory· Published Apr 13, 2026· Updated Apr 17, 2026

CVE-2026-1462

CVE-2026-1462

Description

A vulnerability in the TFSMLayer class of the keras package, version 3.13.0, allows attacker-controlled TensorFlow SavedModels to be loaded during deserialization of .keras models, even when safe_mode=True. This bypasses the security guarantees of safe_mode and enables arbitrary attacker-controlled code execution during model inference under the victim's privileges. The issue arises due to the unconditional loading of external SavedModels, serialization of attacker-controlled file paths, and the lack of validation in the from_config() method.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
kerasPyPI
< 3.13.23.13.2

Affected products

1

Patches

1
b6773d3decae

Disallow TFSMLayer deserialization in safe_mode to prevent external SavedModel execution (#22035)

https://github.com/keras-team/kerasManan PatelJan 29, 2026via ghsa
2 files changed · +66 3
  • keras/src/export/tfsm_layer.py+34 0 modified
    @@ -2,6 +2,7 @@
     from keras.src import layers
     from keras.src.api_export import keras_export
     from keras.src.export.saved_model import _list_variables_used_by_fns
    +from keras.src.saving import serialization_lib
     from keras.src.utils.module_utils import tensorflow as tf
     
     
    @@ -146,3 +147,36 @@ def get_config(self):
                 "call_training_endpoint": self.call_training_endpoint,
             }
             return {**base_config, **config}
    +
    +    @classmethod
    +    def from_config(cls, config, custom_objects=None, safe_mode=None):
    +        """Creates a TFSMLayer from its config.
    +        Args:
    +            config: A Python dictionary, typically the output of `get_config`.
    +            custom_objects: Optional dictionary mapping names to custom objects.
    +            safe_mode: Boolean, whether to disallow loading TFSMLayer.
    +                When `safe_mode=True`, loading is disallowed because TFSMLayer
    +                loads external SavedModels that may contain attacker-controlled
    +                executable graph code. Defaults to `True`.
    +        Returns:
    +            A TFSMLayer instance.
    +        """
    +        # Follow the same pattern as Lambda layer for safe_mode handling
    +        effective_safe_mode = (
    +            safe_mode
    +            if safe_mode is not None
    +            else serialization_lib.in_safe_mode()
    +        )
    +
    +        if effective_safe_mode is not False:
    +            raise ValueError(
    +                "Requested the deserialization of a `TFSMLayer`, which "
    +                "loads an external SavedModel. This carries a potential risk "
    +                "of arbitrary code execution and thus it is disallowed by "
    +                "default. If you trust the source of the artifact, you can "
    +                "override this error by passing `safe_mode=False` to the "
    +                "loading function, or calling "
    +                "`keras.config.enable_unsafe_deserialization()."
    +            )
    +
    +        return cls(**config)
    
  • keras/src/export/tfsm_layer_test.py+32 3 modified
    @@ -114,19 +114,48 @@ def test_serialization(self):
     
             # Test reinstantiation from config
             config = reloaded_layer.get_config()
    -        rereloaded_layer = tfsm_layer.TFSMLayer.from_config(config)
    +        rereloaded_layer = tfsm_layer.TFSMLayer.from_config(
    +            config, safe_mode=False
    +        )
             self.assertAllClose(rereloaded_layer(ref_input), ref_output, atol=1e-7)
     
             # Test whole model saving with reloaded layer inside
             model = models.Sequential([reloaded_layer])
             temp_model_filepath = os.path.join(self.get_temp_dir(), "m.keras")
             model.save(temp_model_filepath, save_format="keras_v3")
             reloaded_model = saving_lib.load_model(
    -            temp_model_filepath,
    -            custom_objects={"TFSMLayer": tfsm_layer.TFSMLayer},
    +            temp_model_filepath, safe_mode=False
             )
             self.assertAllClose(reloaded_model(ref_input), ref_output, atol=1e-7)
     
    +    def test_safe_mode_blocks_model_loading(self):
    +        temp_filepath = os.path.join(self.get_temp_dir(), "exported_model")
    +
    +        # Create and export a model
    +        model = get_model()
    +        model(tf.random.normal((1, 10)))
    +        saved_model.export_saved_model(model, temp_filepath)
    +
    +        # Wrap SavedModel in TFSMLayer and save as .keras
    +        reloaded_layer = tfsm_layer.TFSMLayer(temp_filepath)
    +        wrapper_model = models.Sequential([reloaded_layer])
    +
    +        model_path = os.path.join(self.get_temp_dir(), "tfsm_model.keras")
    +        wrapper_model.save(model_path)
    +
    +        # Default safe_mode=True should block loading
    +        with self.assertRaisesRegex(
    +            ValueError,
    +            "arbitrary code execution",
    +        ):
    +            saving_lib.load_model(model_path)
    +
    +        # Explicit opt-out should allow loading
    +        loaded_model = saving_lib.load_model(model_path, safe_mode=False)
    +
    +        x = tf.random.normal((2, 10))
    +        self.assertAllClose(loaded_model(x), wrapper_model(x))
    +
         def test_errors(self):
             # Test missing call endpoint
             temp_filepath = os.path.join(self.get_temp_dir(), "exported_model")
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.