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...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| keras PyPI | < 3.12.0 | 3.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
- github.com/keras-team/keras/security/advisories/GHSA-hjqc-jx6g-rwp9
- nvd.nist.gov/vuln/detail/CVE-2025-12060
- nvd.nist.gov/vuln/detail/CVE-2025-12638
- github.com/keras-team/keras/pull/21760
- github.com/keras-team/keras/commit/47fcb397ee4caffd5a75efd1fa3067559594e951
- github.com/keras-team/keras
- huntr.com/bounties/f94f5beb-54d8-4e6a-8bac-86d9aee103f4
More Keras advisories
All Keras| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Feb 18 | Keras has a Local File Disclosure via HDF5 External Storage During Keras Weight Loading | High7.1 | 3.12.1+1 more |
| Oct 292025 | Keras is vulnerable to arbitrary local file loading and Server-Side Request Forgery | Medium | 3.12.0 |
| Oct 172025 | Keras framework vulnerable to deserialization of untrusted data | Critical9.8 | 3.11.3 |
| Sep 192025 | Keras: code execution | High | 3.11.3 |
| Sep 192025 | Keras is vulnerable to Deserialization of Untrusted Data | High7.3 | 3.11.0 |
| Aug 122025 | Keras vulnerable to CVE-2025-1550 bypass via reuse of internal functionality | High8.8 | 3.11.0 |