GitHub Security Advisory: GHSA-hjqc-jx6g-rwp9
Package: keras (PIP)
Severity: HIGH (CVSS 9.8)
CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Vulnerable versions: <= 3.11.3
Patched in: 3.12.0

Summary: Keras Directory Traversal Vulnerability

Details:
## 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