Pre-commit hook that blocks Python deps which don't exist on PyPI.
Coding assistants sometimes invent package names. If an attacker later
registers one of them, pip install runs their code. provctl checks staged
manifests against a local copy of the PyPI name list and fails the commit
while the name is still unclaimed (and isn't yours). A name someone has
already registered is not caught by this check.
Lookups stay on disk. Package names are not sent to pypi.org during a check.
$ echo "fastapi-turbo-helper" >> requirements.txt && git add -A && provctl check
BLOCK fastapi-turbo-helper
no project by this name exists on PyPI
at requirements.txt:2
This commit is blocked. To proceed:
- 'fastapi-turbo-helper' does not exist on PyPI. If the name is a typo, fix it.
If it is an internal package, add it to .provenance/policy.tomlAs a pre-commit hook the same output appears when git commit runs and the
commit is aborted.
| Measured | Result |
|---|---|
| Invented package names absent from PyPI, blocked | 310 / 310 |
| Invented names already registered on PyPI (not catchable by a name check) | 10 / 320 |
| Real popular packages wrongly blocked | 0 / 499 |
| Real OSS repos blocked (100 repos) | 1 / 100 (a genuinely invalid requirement, codecs) |
| Time added to a commit | ~90 ms median |
The invented names were generated by a language model asked to imitate slopsquat-style names, and the set is small, so read it as a sanity check, not a benchmark. Method and caveats: docs/measurements.md.
pip install provctl
provctl index refresh # ~12 MB, once (also done automatically on first check)provctl init
provctl checkpre-commit:
repos:
- repo: https://github.com/chirataandrei/provctl
rev: v0.1.4
hooks:
- id: provctlUse provctl-warn-only first if you don't want it to fail commits yet.
Other commands: explain, ack, enrich, doctor, hook install.
- Missing from PyPI, not first-party, not on a private index → block. Provenance cannot override this (it's self-reported).
- Package exists but is new and unused → warn. Provenance can only move things in this band.
- Imports never block.
import yamlvsPyYAMLis too easy to get wrong.
Without config it already treats as first-party: workspace packages, uv/poetry
git/path sources, private indexes (PIP_INDEX_URL etc.), local modules,
_private modules, stdlib from other Python versions.
[first_party]
prefixes = ["acme_*"]
allow = ["acme-billing"]Transitive deps, typos of names that exist, and malicious packages that are
on PyPI. Use pip-audit / OSV for those.
Agent provenance (git-ai / hook) is for "who added this", not a security control. Details in docs/threat-model.md.
Exact name index is ~12 MB. A Bloom filter would be smaller but a false "exists" is a missed fake package, so we don't use one.
Runtime dep: packaging. git-ai is optional.