Build your distributions, list every file inside the sdist and wheel, search them for secrets and files you did not mean to ship, then install the wheel in a clean environment. PyPI does not allow deleted files to be uploaded again, so catching a mistake before upload matters.
Why this check exists
Uploads to PyPI are public. The PyPI help page says deletion is permanent and irreversible, and that a distribution filename cannot be reused, even after the project is deleted and recreated. So the cheapest time to find a leaked token or a stray database dump is before you run upload.
The sdist specification defines only a few required files and says no other content is required or defined. Your build backend decides what else goes in. If your repository has a local config file, test fixtures or exported data, an overly broad include rule can pick them up.
The checklist
- Build from a clean tree. Run
python -m buildfrom a fresh checkout of the tagged commit, not your working directory. Untracked files then cannot slip in. - List the sdist. Run
tar tzf dist/yourpackage-1.0.0.tar.gz. - List the wheel. Run
unzip -l dist/yourpackage-1.0.0-py3-none-any.whl. - Scan the names. Look for
.env, key and certificate files,.pypirc, cloud credential files, local databases, notebooks with outputs, and editor folders. - Scan the contents. Run a secret scanner over the extracted archives, not just your repository. A file generated at build time is invisible to repo scans.
- Check metadata. Open PKG-INFO in the sdist and the METADATA file in the wheel dist-info folder. Confirm name, version and license are right.
- Run
twine check dist/*. It reports description rendering problems, and PyPI rejects uploads whose description fails to render. - Install and test. Create a new virtual environment, install the wheel, and run your tests from a different directory.
- Upload to TestPyPI first. See how to test a release on TestPyPI.
Files that often cause trouble
| Found in the archive | Why it matters |
|---|---|
| Environment or credential files | Credentials become public |
| Test data with real records | Privacy and contract exposure |
| Large binaries or datasets | Size limits and slow installs |
| Duplicate vendored code | Confusing licensing and advisories |
| Build output folders | Stale or unexpected code |
If you find something after upload
Rotate any credential first, because yanking does not hide files. PyPI's own docs say yanking is non destructive: the files stay available and installers can still fetch them when pinned exactly. Delete only as a considered step, because deletion breaks pinned installs for your downstream users.
Make it routine
Put steps 1 to 7 in your release job so they run on every tag. A short allowlist of expected file patterns, checked in CI, catches new surprises.
Safeguard can run SBOM and dependency scanning and policy gates in your pipeline. You can also try the free scan first.
Quick check
Does yanking remove a leaked file from PyPI?
No. A yanked release stays downloadable, and installers can still select it when a version is pinned exactly. Revoke the credential.
Can I re-upload a file after deleting it?
No. PyPI states deleted files cannot be re-uploaded, so bump the version and rebuild.
Is the wheel safer than the sdist?
Neither is private. Check both, because their contents can differ.
Sources
- Source distribution format, Python Packaging User Guide
- PyPI help, FAQ
- Yanking, PyPI Docs
- Making a PyPI friendly README, Python Packaging User Guide
Last reviewed: 19 October 2026.