Mar 12, 2026·7 min read·6 visits
Black versions prior to 26.3.1 fail to properly hash short user-controlled configurations, leading to a path traversal vulnerability. An attacker can use crafted parameters to write arbitrary cache files outside the designated cache directory.
A path traversal vulnerability (CWE-22) in the cache mechanism of the Black Python code formatter allows an attacker to write files to arbitrary locations on the filesystem. The vulnerability stems from improper sanitization of the `--python-cell-magics` parameter when constructing cache keys.
CVE-2026-32274 is a high-severity path traversal vulnerability (CWE-22) affecting Black, a widely adopted Python code formatting utility. The vulnerability specifically targets the caching subsystem responsible for storing formatting metadata and pickled Python objects. By design, Black utilizes this cache to optimize repeated executions across large codebases, significantly reducing formatting overhead.
The attack surface involves the --python-cell-magics command-line argument and its corresponding configuration directive in a project's pyproject.toml file. This parameter dictates how Black formats Python code embedded within specific Jupyter notebook cell magics. When an attacker provides a maliciously crafted string containing directory traversal sequences, the application incorporates the string directly into the filesystem path used for cache storage.
The companion daemon, blackd, significantly amplifies the exposure of this vulnerability. Because blackd runs as a background process listening for formatting requests, it parses incoming HTTP parameters directly into Black's core formatting logic. Prior to version 26.3.1, the daemon lacked Cross-Origin Resource Sharing (CORS) protections, exposing local developer environments to browser-based exploitation.
Successful exploitation results in an arbitrary file write condition operating under the privileges of the user executing Black or the blackd service. An attacker can write cache data to any directory accessible to the process, bypassing the intended ~/.cache/black/ restriction.
The root cause resides in the get_cache_key method located within the src/black/mode.py module. This function generates a unique filename for cache entries based on the specific configuration options provided during execution. The implementation concatenates various feature flags and magic configurations to form a base identifier string.
The vulnerability emerges from a conditional optimization intended to reduce CPU overhead. The application applies a SHA-256 hash to the configuration string only if the string's length exceeds _MAX_CACHE_KEY_PART_LENGTH, defined as 32 characters. If the concatenated string is 32 characters or shorter, the application bypasses the hashing routine entirely.
When the hashing routine is bypassed, the raw string acts directly as a component of the cache filename. The application lacks any subsequent sanitization or validation of the characters within this string. Consequently, special path characters such as slashes (/) and dot-dot sequences (../) are preserved in the final path calculation.
An attacker exploiting this logic supplies a payload that is both malicious and concise, ensuring the total length remains under the 32-character threshold. The application processes the short traversal string, bypasses the sanitization hash, and forces the underlying filesystem API to resolve the relative path outside the target cache directory.
The vulnerable implementation in src/black/mode.py concatenates the features and python_cell_magics variables. The resulting string is assigned to the features_and_magics variable. The subsequent conditional block checks the length of this string against _MAX_CACHE_KEY_PART_LENGTH.
# src/black/mode.py (Vulnerable Implementation)
def get_cache_key(self) -> str:
features_and_magics = (
",".join(sorted(self.features))
+ "@"
+ ",".join(sorted(self.python_cell_magics))
)
# The flaw exists in the following conditional check:
if len(features_and_magics) > _MAX_CACHE_KEY_PART_LENGTH:
features_and_magics = sha256(features_and_magics.encode()).hexdigest()[
:_MAX_CACHE_KEY_PART_LENGTH
]
# ... function continues to build the final pathThe fix, introduced in commit 4937fe6cf241139ddbfc16b0bdbb5b422798909d, removes the vulnerable length evaluation. The maintainers refactored the function to apply the SHA-256 hash unconditionally. By forcing every configuration string through a cryptographic hash function, the application guarantees that the filename component consists strictly of safe, hexadecimal characters.
# src/black/mode.py (Patched Implementation)
def get_cache_key(self) -> str:
features_and_magics = (
",".join(sorted(self.features))
+ "@"
+ ",".join(sorted(self.python_cell_magics))
)
# The patch applies the hash unconditionally, ensuring path safety.
features_and_magics = sha256(features_and_magics.encode()).hexdigest()[
:_MAX_CACHE_KEY_PART_LENGTH
]
# ... function continues to build the final pathThis remediation completely eliminates the path traversal vector at the source. Any malicious input supplied via the CLI or HTTP headers is irreversibly transformed into a benign 32-character hex string before interacting with the filesystem.
Exploitation occurs through one of three primary vectors: malicious repository configurations, direct command-line arguments, or daemon HTTP requests. An attacker controlling a codebase can embed the payload in the pyproject.toml file. When a victim executes Black against the compromised repository, the path traversal triggers automatically.
The command-line attack vector requires the attacker to append the payload directly to the execution string. A command such as black --python-cell-magics "../../../tmp/pwned" script.py generates a cache key containing the traversal characters. The application constructs the absolute path, escaping ~/.cache/black/ and writing the cache file to /tmp/pwned.
The blackd daemon vector presents a severe remote exploitation scenario for local developers. The daemon listens on TCP port 45484 by default. An attacker can construct a malicious website that issues an asynchronous POST request to http://localhost:45484 containing an X-Python-Cell-Magics header populated with the traversal payload.
The primary security impact is arbitrary file write, quantified by a CVSS 4.0 score of 8.7 and a High Integrity metric. An attacker dictates the exact destination path of the file write operation. The operation executes with the permissions of the user running the Black application, which commonly corresponds to an unprivileged developer account or a Continuous Integration service user.
The vulnerability is constrained by the application's control over the file content. The written file contains binary cache data or pickled Python objects generated by Black, rather than raw attacker-controlled text. This constraint complicates the immediate execution of arbitrary code, as the attacker cannot write pure scripts or typical shellcodes directly to the filesystem.
Despite the content constraint, the ability to write to arbitrary locations introduces significant secondary risks. An attacker can target critical system or application configurations, overwriting .bashrc, .ssh/authorized_keys, or legitimate Python modules in the execution path. Overwriting execution-critical files with corrupted or mismatched data results in localized denial of service or indirect code execution upon subsequent parsing.
The lack of prerequisite authentication or user interaction for the blackd attack vector exacerbates the severity. A victim merely navigating to an attacker-controlled webpage while running blackd locally is sufficient to trigger the arbitrary file write.
The definitive remediation strategy requires upgrading the psf/black package to version 26.3.1. This release contains the unconditional hashing mechanism that prevents the path traversal entirely. Security engineers must enforce this version requirement across all developer environments, automated testing pipelines, and container images.
The patch for version 26.3.1 implements substantial architectural hardening for the blackd daemon. The maintainers disabled cross-origin requests by default, neutralizing the browser-based attack vector. Administrators configuring blackd for cross-origin usage must now explicitly define trusted sources using the newly introduced --cors-allow-origin flag.
Further systemic protections added to blackd include strict request body size limits (--max-body-size) and concurrent execution semaphores. These mechanisms prevent CPU exhaustion and denial of service conditions that could be triggered by malformed formatting requests.
Organizations unable to deploy the patch immediately must implement interim mitigations. Administrators should restrict the use of the blackd daemon entirely on developer workstations. Security scanning pipelines should be configured to audit pyproject.toml files for suspicious python-cell-magics definitions containing path traversal sequences.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
psf/black Python Software Foundation | < 26.3.1 | 26.3.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Attack Vector | Network / Local Configuration |
| CVSS v4.0 | 8.7 |
| Impact | Arbitrary File Write |
| Exploit Status | Proof of Concept (PoC) |
| Fixed Version | 26.3.1 |
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.