Project-relative cache-dir allows unrestricted pickle.load of attacker-controlled data
Maintainers usually reply within 1 day
@rinopatrick is already working on this.
Since Nov 1, 2026.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 22/100
Research direction
Start with how a relative cache-dir from [tool.black] reaches the cache code: src/black/files.py finds the project pyproject.toml, src/black/init.py passes the directory on, and src/black/cache.py and src/blib2to3/pgen2/driver.py load pickles from it. A pull request (#5547) is already open against this, so check it and the thread before starting. Done means the chosen remediation is agreed with maintainers and the tests cover that project-relative caches are ignored or rejected while --no-cache and an explicit CLI cache dir still work.
Written by the indexing model from the issue text.
Description
Summary
Black resolves a relative cache-dir from [tool.black] inside the project checkout and then calls pickle.load() on files under that path. Both the metadata cache and the grammar caches are affected.
A repo author can drop crafted pickle files with the expected names. If someone later runs Black on that checkout with caching enabled, the process deserializes them.
This was also shared with Tidelift security, who confirmed the tracing. It was treated as a hardening issue rather than a CVE, which matches Black not claiming untrusted-checkout safety.
Affected code
src/black/files.py— finds the projectpyproject.tomlsrc/black/__init__.py— accepts a relativecache-dirfrom project config and passes it into grammar init (unless--no-cache)src/black/cache.py— metadata cache does unrestrictedpickle.loadsrc/blib2to3/pgen2/driver.py+grammar.py— grammar cache uses a predictable filename + mtime check, thenpickle.load
Preconditions
- Project sets a relative
cache-dir - Matching cache files exist for the Black version/mode/Python runtime
- Someone runs Black on the checkout with caching enabled
--no-cacheis not used
Cloning alone is not enough.
Impact
Arbitrary Python code can run as the Black process. Likelihood is limited because the victim has to actually run Black with caching on, and the attacker needs the right filenames.
--no-cache blocks both paths.
Suggested remediation
- Don’t accept
cache-dirfrom project config (or only accept absolute paths from a trusted source) - Switch both caches to a non-executable format, or check that the writer is the same authority as the Black process
- Treat existing project-relative pickle caches as invalid
- Keep an explicit CLI cache dir and
--no-cacheworking
Static tracing only. Default cache is still per-user when there’s no project override.
- Dominant language
- Python
- Stars
- 42.2k
- Forks
- 3k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 98
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from psf/black
-
--line-ranges selects the wrong source line with --skip-source-first-linePossibly taken @SaifAli-S claimed this today. OpenT: bug
Difficulty 3/5 1-2 days Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
Maintainers usually reply within 1 day
-
Odd behavior from `black` when handling long `lambda` functionsPossibly taken @harshitdeora claimed this 9 days ago. OpenF: linetoolong T: bug
Difficulty 3/5 1-2 days Newbie friendliness 57/100
Maintainers usually reply within 1 day
-
Compatible-release requires-python constraints infer unsupported targetsPossibly taken @harisawan-bit claimed this 108 days ago. Open
Difficulty 3/5 1-2 days Newbie friendliness 48/100
Maintainers usually reply within 1 day
-
Nested .gitignore negations can never re-include what an ancestor excludedPossibly taken @RRXXZZYY claimed this 73 days ago. Open
Difficulty 4/5 3-5 days Newbie friendliness 65/100
Maintainers usually reply within 1 day
Similar issues
-
add entry to blockOpen
Difficulty 1/5 Under an hour Newbie friendliness 65/100
StevenBlack/hosts#3299 ·
-
`garak --report` overwrites the input report when its name has no ".report"Possibly taken @0z5a claimed this today. Openneeds-triage
Difficulty 2/5 Half a day Newbie friendliness 72/100
Maintainers usually reply within 2 days
-
plot_graph crashes when a mesh node has no m2m edgePossibly taken @tygvi claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
mllam/neural-lam#883 · 1 comment ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
future-agi/future-agi#4092 · 1 comment ·
Maintainers usually reply within 1 day
-
transcript analytics: a text whose only protected spans the prose pass redacts is withheld wholeOpen
Difficulty 1/5 Under an hour Newbie friendliness 82/100
WingedGuardian/GENesis-AGI#4641 ·
Maintainers usually reply within 1 day