Keras duplicate model.weights.h5 backdoor PoC
This repository contains a proof-of-concept for a Keras .keras archive ambiguity where duplicate model.weights.h5 entries are accepted. Keras resolves the required weights member to the later duplicate entry, enabling hidden trigger-based backdoor weights while config.json, metadata.json, and the first weights entry can remain benign.
This is not a Lambda layer deserialization issue. The CNN PoC uses standard Keras layers: Conv2D, Flatten, and Dense.
Main PoC
- Model format: .keras
- Architecture: Conv2D -> Flatten -> Dense
- Trigger: bottom-right 2x2 image patch
- Poisoned archive: duplicate model.weights.h5
- First weights entry: benign / matches clean model
- Later duplicate weights entry: contains trigger backdoor
- Keras load result: loads without warning
- safe_mode=True: does not reject
- ModelScan: No issues found
- Load/save laundering: duplicate-entry evidence disappears, but the backdoor remains active
Key files
- cnn_image_backdoor_clean_baseline.keras
- cnn_image_backdoor_duplicate_weights_trigger.keras
- cnn_image_backdoor_pipeline_normalized.keras
- evidence_bundle_keras_duplicate_weights_backdoor.zip
Suggested remediation
Keras should reject duplicate required archive members before reading or extracting model contents:
- metadata.json
- config.json
- model.weights.h5
- model.weights.npz