VAITP Dataset

← Back to the dataset

CVE-2026-32274

Path traversal in Black via `–python-cell-magics` allows file write.

  • CVSS 8.7
  • CWE-22
  • Input Validation and Sanitization
  • Local

Black is the uncompromising Python code formatter. Prior to 26.3.1, Black writes a cache file, the name of which is computed from various formatting options. The value of the –python-cell-magics option was placed in the filename without sanitization, which allowed an attacker who controls the value of this argument to write cache files to arbitrary file system locations. Fixed in Black 26.3.1.

CVSS base score
8.7
Published
2026-03-12
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Local
Impact
Arbitrary Code Execution
Affected component
Black
Fixed by upgrading
Yes

Solution

Upgrade Black to version 26.3.1 or later.

Vulnerable code sample

import os
import argparse
import hashlib

# This is a simplified representation of the vulnerable code in Black prior to the fix.
# It is not the actual source code but demonstrates the same logical flaw.

CACHE_DIR = os.path.join(os.path.expanduser("~"), ".cache", "black_poc")

def get_cache_filename(python_cell_magics):
    """
    Simulates the creation of a cache filename based on configuration options.
    The vulnerability lies in using `python_cell_magics` directly in the path.
    """
    # In the real code, a hash is computed from multiple sources.
    # We simulate this with a fixed hash for simplicity.
    options_hash = hashlib.sha256(b"stable-options").hexdigest()

    # The vulnerable part: The user-controlled string is concatenated
    # into the filename without any sanitization.
    filename = f"{options_hash}-{python_cell_magics}.cache"
    return filename

def format_code_with_caching(magics_option):
    """
    Simulates the main logic of formatting a file and writing to a cache.
    """
    try:
        if not os.path.exists(CACHE_DIR):
            os.makedirs(CACHE_DIR)
            print(f"[*] Created cache directory: {CACHE_DIR}")
    except OSError as e:
        print(f"[!] Error creating cache directory: {e}")
        return

    # Construct the cache file path using the vulnerable function
    cache_filename = get_cache_filename(magics_option)
    cache_filepath = os.path.join(CACHE_DIR, cache_filename)

    print(f"[*] Attempting to write to cache file: {os.path.abspath(cache_filepath)}")

    try:
        # The file write operation that can be exploited for arbitrary file creation
        with open(cache_filepath, "w") as f:
            f.write("This is a simulated cache content.")
        print(f"[+] Successfully wrote to {os.path.abspath(cache_filepath)}")
    except Exception as e:
        print(f"[!] Failed to write cache file: {e}")

def main():
    parser = argparse.ArgumentParser(
        description="PoC for Black path traversal vulnerability (CVE-2024-21503)."
    )
    parser.add_argument(
        "--python-cell-magics",
        type=str,
        default="normal_magic",
        help="The unsanitized option used for cache filename generation.",
    )
    args = parser.parse_args()

    format_code_with_caching(args.python_cell_magics)

if __name__ == "__main__":
    # To demonstrate the vulnerability, run the following command:
    # python your_script_name.py --python-cell-magics "../../../tmp/pwned_by_black.txt"
    # This will attempt to create a file at /tmp/pwned_by_black.txt (on Unix-like systems)
    # instead of inside the intended .cache/black_poc/ directory.
    main()

Patched code sample

import re
import os
from dataclasses import dataclass, field
from typing import FrozenSet


# This example represents the logic used to fix the vulnerability. The fix is
# applied during the initialization of a configuration object, before the
# value is ever used to construct a cache file path.

@dataclass
class BlackMode:
    """A simplified representation of Black's configuration object."""

    # This field holds the value from the `--python-cell-magics` option.
    python_cell_magics: FrozenSet[str] = field(default_factory=frozenset)
    # Other configuration options would be here.

    def __post_init__(self) -> None:
        """
        This method is automatically called after the object is created.
        It contains the sanitization logic that constitutes the fix.
        """
        if self.python_cell_magics:
            # THE FIX:
            # The original code used the `python_cell_magics` value in a
            # process that determined the cache filename. This new code
            # sanitizes the input by replacing path separators ('/' and '\')
            # with an underscore. This prevents path traversal.
            # The original commit comment:
            # "Sort for stability, then check for slashes. This is paranoid and
            # probably not required, but it's better to be safe."
            self.python_cell_magics = frozenset(
                sorted(
                    re.sub(r"[/\\]", "_", magic) for magic in self.python_cell_magics
                )
            )


# --- Demonstration of the fix ---

def get_cache_path_vulnerable(magics_input: str) -> str:
    """Simulates the vulnerable behavior of creating a cache path."""
    # VULNERABLE: The input is used directly in the path.
    file_name = f"black-cache-{magics_input}.bin"
    return os.path.join("/tmp/black/cache", file_name)


def get_cache_path_fixed(mode: BlackMode) -> str:
    """Simulates the fixed behavior using the sanitized configuration."""
    # FIXED: The input from the Mode object has already been sanitized.
    # We join the frozenset for filename creation.
    magics_string = "-".join(mode.python_cell_magics)
    file_name = f"black-cache-{magics_string}.bin"
    return os.path.join("/tmp/black/cache", file_name)


if __name__ == "__main__":
    # An attacker controls this input, attempting a path traversal attack.
    malicious_input = "../../../etc/passwd"
    user_provided_magics = frozenset({malicious_input})

    # 1. Demonstrate the vulnerable path construction (for comparison)
    vulnerable_path = get_cache_path_vulnerable(malicious_input)
    print(f"Vulnerable path:  {vulnerable_path}")
    print(f"Normalized path:  {os.path.normpath(vulnerable_path)}\n")


    # 2. Demonstrate the fix
    # The fix is applied inside the BlackMode's __post_init__ upon creation.
    fixed_mode = BlackMode(python_cell_magics=user_provided_magics)

    # The `python_cell_magics` attribute is now sanitized.
    print(f"Sanitized magics in config object: {fixed_mode.python_cell_magics}")

    # The sanitized value is then used, preventing the traversal.
    secure_path = get_cache_path_fixed(fixed_mode)
    print(f"Secure path:      {secure_path}")
    print(f"Normalized path:  {os.path.normpath(secure_path)}")

Payload

black --python-cell-magics '../../../../../../../../tmp/pwned' some_file.py

Cite this entry

@misc{vaitp:cve202632274,
  title        = {{Path traversal in Black via `--python-cell-magics` allows file write.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-32274},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-32274/}}
}
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 ::