
CVE-2026-9335: KerasFileEditor and load_weights follow h5py ExternalLinks, disclosing arbitrary local HDF5 file contents in keras ≤ 3.14.0. Advisory + verified PoCs.
ExternalLink local file-content disclosureTwo public Keras APIs — keras.saving.KerasFileEditor and
keras.saving.load_weights / — read HDF5 group members by
without a link-type check. h5py
/ on indexing, so an attacker-supplied ,
, or file containing only a link causes Keras to read the contents
of .
model.load_weightsExternalLinkSoftLink.weights.h5.h5.kerasKeras already ships the correct guard (
safe_get_h5_group/safe_get_h5_datasetinsaving_lib.py) — these two entry points simply don't call it.
| CVE | CVE-2026-9335 |
| Affected | keras-team/keras ≤ 3.14.0 (PyPI) |
| Type | Path Traversal / Information Exposure (CWE-22, CWE-200) |
| CVSS 3.1 | AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N → 6.5 (Medium) |
| Impact | Disclosure of any local HDF5 file the victim can read |
| Vector | Victim opens/loads a malicious .weights.h5 / .h5 / .keras file |
| Status | Fixed in keras 3.15.0 (PR #22899 / #22900); validated on huntr (2026-05-22, maintainer hertschuh) |
| Reporter | Jonathan Paparo (jonathan-paparo) |
| Path | What it is |
|---|---|
advisory/CVE-2026-9335.md | Full technical write-up: root cause, both sites, PoC output, fix |
poc/poc_file_editor.py | PoC 1 — KerasFileEditor follows an ExternalLink and leaks a victim h5 |
poc/poc_load_weights.py | PoC 2 — model.load_weights pulls a victim's weights via ExternalLink |
poc/requirements.txt | Pinned reproduction environment |
docs/timeline.md | Coordinated-disclosure timeline |
# keras/src/saving/saving_api.py (load_weights, legacy .h5 branch)
with h5py.File(filepath, "r") as f:
if "layer_names" not in f.attrs and "model_weights" in f:
f = f["model_weights"] # <-- ExternalLink here is auto-dereferenced by h5py
f["model_weights"] (and the analogous data[key] walk in file_editor.py)
transparently follows a link into another file. The safe pattern that Keras uses
elsewhere pre-checks the link type first:
# keras/src/saving/saving_lib.py (safe_get_h5_group)
group_type = parent.get(name, default=None, getclass=True, getlink=True)
if group_type in (h5py.ExternalLink, h5py.SoftLink):
raise ValueError(f"Not allowed: H5 file with {group_type.__name__}")
python -m pip install -r poc/requirements.txt
# PoC 1 — KerasFileEditor leaks a separate victim h5 file's contents
KERAS_BACKEND=jax python poc/poc_file_editor.py
# PoC 2 — model.load_weights loads a victim file's weights via an ExternalLink
KERAS_BACKEND=jax python poc/poc_load_weights.py
Each PoC creates its own throwaway "victim" and "attacker" h5 files under the
system temp directory, demonstrates the leak, prints
*** VULNERABILITY CONFIRMED ***, and cleans up after itself. Verified on
keras==3.14.0, Python 3.12.6, JAX backend.
Route both sites through the existing safe_get_h5_group helper, or add a
data.get(key, default=None, getclass=True, getlink=True) pre-check that rejects
(h5py.ExternalLink, h5py.SoftLink) before indexing. See the
advisory for exact diffs.
This material is published for defensive and educational purposes after coordinated disclosure and CVE assignment. The PoCs operate only on files they create themselves in a temp directory. Do not use these techniques against systems or data you are not authorized to test.
Discovered and reported by Jonathan Paparo (huntr:
jonathan-paparo). Validated by
keras-team maintainer hertschuh.