Keras has a Local File Disclosure via HDF5 External Storage During Keras Weight Loading
Summary
TensorFlow / Keras continues to honor HDF5 “external storage” and ExternalLink features when loading weights. A malicious .weights.h5 (or a .keras archive embedding such weights) can direct load_weights() to read from an arbitrary readable filesystem path. The bytes pulled from that path populate model tensors and become observable through inference or subsequent re-save operations. Keras “safe mode” only guards object deserialization and does not cover weight I/O, so this behaviour persists even with safe mode enabled. The issue is confirmed on the latest publicly released stack (tensorflow 2.20.0, keras 3.11.3, h5py 3.15.1, numpy 2.3.4).
Impact
- Class: CWE-200 (Exposure of Sensitive Information), CWE-73 (External Control of File Name or Path)
- What leaks: Contents of any readable file on the host (e.g.,
/etc/hosts,/etc/passwd,/etc/hostname). - Visibility: Secrets appear in model outputs (e.g., Dense layer bias) or get embedded into newly saved artifacts.
- Prerequisites: Victim executes
model.load_weights()ortf.keras.models.load_model()on an attacker-supplied HDF5 weights file or.kerasarchive. - Scope: Applies to modern Keras (3.x) and TensorFlow 2.x lines; legacy HDF5 paths remain susceptible.
Attacker Scenario
1. Initial foothold: The attacker convinces a user (or CI automation) to consume a...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| keras PyPI | >= 3.13.0, < 3.13.2 | 3.13.2 |
| >= 3.0.0, < 3.12.1 | 3.12.1 |
Details and references
## Summary TensorFlow / Keras continues to honor HDF5 “external storage” and `ExternalLink` features when loading weights. A malicious `.weights.h5` (or a `.keras` archive embedding such weights) can direct `load_weights()` to read from an arbitrary readable filesystem path. The bytes pulled from that path populate model tensors and become observable through inference or subsequent re-save operations. Keras “safe mode” only guards object deserialization and does not cover weight I/O, so this behaviour persists even with safe mode enabled. The issue is confirmed on the latest publicly released stack (`tensorflow 2.20.0`, `keras 3.11.3`, `h5py 3.15.1`, `numpy 2.3.4`). ## Impact - **Class**: CWE-200 (Exposure of Sensitive Information), CWE-73 (External Control of File Name or Path) - **What leaks**: Contents of any readable file on the host (e.g., `/etc/hosts`, `/etc/passwd`, `/etc/hostname`). - **Visibility**: Secrets appear in model outputs (e.g., Dense layer bias) or get embedded into newly saved artifacts. - **Prerequisites**: Victim executes `model.load_weights()` or `tf.keras.models.load_model()` on an attacker-supplied HDF5 weights file or `.keras` archive. - **Scope**: Applies to modern Keras (3.x) and TensorFlow 2.x lines; legacy HDF5 paths remain susceptible. ## Attacker Scenario 1. **Initial foothold**: The attacker convinces a user (or CI automation) to consume a weight artifact, perhaps by publishing a pre-trained model, contributing to an open-source repository, or attaching weights to a bug report. 2. **Crafted payload**: The artifact bundles innocuous model metadata but rewrites one or more datasets to use HDF5 external storage or external links pointing at sensitive files on the victim host (e.g., `/home/<user>/.ssh/id_rsa`, `/etc/shadow` if readable, configuration files containing API keys, etc.). 3. **Execution**: The victim calls `model.load_weights()` (or `tf.keras.models.load_model()` for `.keras` archives). HDF5 follows the external references, opens the targeted host file, and streams its bytes into the model tensors. 4. **Exfiltration vectors**: - Running inference on controlled inputs (e.g., zero vectors) yields outputs equal to the injected weights; the attacker or downstream consumer can read the leaked data. - Re-saving the model (weights or `.keras` archive) persists the secret into a new artifact, which may later be shared publicly or uploaded to a model registry. - If the victim pushes the re-saved artifact to source control or a package repository, the attacker retrieves the captured data without needing continued access to the victim environment. ### Additional Preconditions - The target file must exist and be readable by the process running TensorFlow/Keras. - Safe mode (`load_model(..., safe_mode=True)`) does not mitigate the issue because the attack path is weight loading rather than object/lambda deserialization. - Environments with strict filesystem permissioning or sandboxing (e.g., container runtime blocking access to `/etc/hostname`) can reduce impact, but common defaults expose a broad set of host files. ## Environment Used for Verification (2025‑10‑19) - OS: Debian-based container running Python 3.11. - Packages (installed via `python -m pip install -U ...`): - `tensorflow==2.20.0` - `keras==3.11.3` - `h5py==3.15.1` - `numpy==2.3.4` - Tooling: `strace` (for syscall tracing), `pip` upgraded to latest before installs. - Debug flags: `PYTHONFAULTHANDLER=1`, `TF_CPP_MIN_LOG_LEVEL=0` during instrumentation to capture verbose logs if needed. ## Reproduction Instructions (Weights-Only PoC) 1. Ensure the environment above (or equivalent) is prepared. 2. Save the following script as `weights_external_demo.py`: ```python from __future__ import annotations import os from pathlib import Path import numpy as np import tensorflow as tf import h5py def choose_host_file() -> Path: candidates = [ os.environ.get("KFLI_PATH"), "/etc/machine-id", "/
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-200, CWE-73
- Also known as
- CVE-2026-1669, PYSEC-2026-2546
- github.com/keras-team/keras/security/advisories/GHSA-3m4q-jmj6-r34q
- nvd.nist.gov/vuln/detail/CVE-2026-1669
- github.com/keras-team/keras/pull/22057
- github.com/keras-team/keras/commit/8a37f9dadd8e23fa4ee3f537eeb6413e75d12553
- github.com/keras-team/keras
- github.com/keras-team/keras/releases/tag/v3.12.1
- github.com/keras-team/keras/releases/tag/v3.13.2