
Pypi Security Best Practices
- 1 installs
- 10 repo stars
- Updated August 4, 2026
- aradotso/security-skills
pypi-security-best-practices is a skill that guides developers through securing Python package installations from PyPI using uv and pip to mitigate supply-chain attacks.
About
A guidance skill for securing Python package installations from PyPI with uv and pip. It walks through binary-only installs, dependency cooldowns, cryptographic hash verification, deterministic lockfiles and preventing dependency confusion. A developer uses it when hardening a Python environment against supply-chain attacks. It matters because compromised PyPI packages can run arbitrary code during install.
- Guides binary-only installs, dependency cooldowns and hash verification for uv and pip
- Targets PyPI supply-chain attacks like the LiteLLM/Telnyx incident
- Covers Dependabot and Renovate cooldown config plus dependency-confusion prevention
Pypi Security Best Practices by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,835 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
pypi-security-best-practices capabilities & compatibility
Free; open-source tooling (uv, pip, pip-audit, uv-secure).
- Capabilities
- dependency scanning · supply chain audit · lockfile hardening
- Works with
- github
- Use cases
- security audit · devops
- Pricing
- Free
What pypi-security-best-practices says it does
supply chain attacks like the LiteLLM/Telnyx incident (119k+ malicious downloads in under 3 hours)
Prefer binary-only installations to avoid arbitrary code execution
Source distributions can execute arbitrary code via `setup.py`
npx skills add https://github.com/aradotso/security-skills --skill pypi-security-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 10 |
| Last updated | August 4, 2026 |
| Repository | aradotso/security-skills ↗ |
What it does
Harden Python package installs against supply-chain attacks by configuring uv/pip with binary-only installs, cooldowns and hash verification.
Who is it for?
Python teams hardening uv/pip dependency workflows against malicious or compromised packages.
Skip if: Non-Python ecosystems or runtime web-app pentesting.
When should I use this skill?
You are configuring a Python project's dependency management and want supply-chain hardening.
What you get
A hardened Python install pipeline using binary-only installs, cooldowns, hash-pinned lockfiles and dependency-confusion protection.
- Hardened uv/pip configuration
- Hash-pinned lockfiles
- Dependabot/Renovate cooldown config
By the numbers
- 119k+ malicious downloads in the LiteLLM/Telnyx incident
- Covers 5 core security practices (binary-only, cooldowns, hashes, deterministic installs, dependency-confusion)
Files
PyPI Security Best Practices
Skill by ara.so — Security Skills collection.
This skill provides comprehensive guidance on securing Python package installations from PyPI, covering supply chain attack mitigation, dependency verification, and secure development practices for both uv and pip package managers.
Overview
PyPI security best practices help protect against supply chain attacks like the LiteLLM/Telnyx incident (119k+ malicious downloads in under 3 hours) and other compromised package scenarios. This guide covers secure package installation, dependency management, and development environment hardening.
Key Security Principles:
- Prefer binary-only installations to avoid arbitrary code execution
- Implement dependency cooldowns to avoid newly-published malicious packages
- Pin dependencies with cryptographic hash verification
- Use deterministic installations and prevent dependency confusion
- Scan for vulnerabilities and verify package health
Installation
uv (Recommended)
# Install uv (macOS/Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install uv (Windows)
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
# Verify installation
uv --versionpip
# pip is included with Python 3.4+
python -m pip --version
# Upgrade to latest pip
python -m pip install --upgrade pipSecurity Tools
# Install pip-audit for vulnerability scanning
python -m pip install pip-audit
# Install uv-secure for lockfile scanning
uv tool install uv-secureCore Security Practices
1. Binary-Only Installations
Source distributions can execute arbitrary code via setup.py. Enforce binary-only installs:
With uv:
# Command line
uv pip install --only-binary :all: requests
# In pyproject.toml
[tool.uv.pip]
only-binary = [":all:"]
# In uv.toml
[pip]
only-binary = [":all:"]With pip:
# Command line
pip install --only-binary :all: requests
# Environment variable
export PIP_ONLY_BINARY=:all:
pip install requests
# In pip.conf (Linux/macOS: ~/.config/pip/pip.conf)
[install]
only-binary = :all:2. Dependency Cooldowns
Avoid newly-published malicious packages by excluding recent releases:
With uv:
# pyproject.toml
[tool.uv]
exclude-newer = "7 days" # Recommended for general use
# Or more aggressive for production
exclude-newer = "30 days"# Command line usage
uv lock --exclude-newer "7 days"
uv sync --exclude-newer "7 days"
# Environment variable
export UV_EXCLUDE_NEWER="7 days"
uv syncPer-package overrides:
# pyproject.toml - exempt security patches
[tool.uv]
exclude-newer = "7 days"
exclude-newer-package = { requests = "1 day" }With pip (v26.1+):
# ~/.config/pip/pip.conf
[install]
uploaded-prior-to = P7D# Command line (absolute date)
pip install --uploaded-prior-to=2026-06-01 requests
# Bypass cooldown for urgent patches
pip install --uploaded-prior-to=P0D requests==2.32.3Dependabot cooldown:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
cooldown: 7 # Wait 7 days after releaseRenovate cooldown:
{
"packageRules": [
{
"matchDatasources": ["pypi"],
"minimumReleaseAge": "7 days"
}
]
}3. Hash Verification
Always verify package integrity with cryptographic hashes:
With uv (automatic in lockfile):
# Generate lockfile with hashes
uv lock
# Install with hash verification (automatic)
uv sync
# For requirements.txt workflow
uv pip compile --generate-hashes requirements.in -o requirements.txt
uv pip install -r requirements.txtExample lockfile entry:
[[package]]
name = "requests"
version = "2.32.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/.../requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" },
]With pip:
# Generate hashed requirements
pip-compile --generate-hashes requirements.in
# Install with hash verification
pip install --require-hashes -r requirements.txtExample requirements.txt with hashes:
requests==2.32.3 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760
certifi==2024.2.2 \
--hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f4. Deterministic Installations
Use lockfiles for reproducible builds:
With uv:
# Create lockfile
uv lock
# Install exact versions from lockfile
uv sync
# Install without updating lockfile
uv sync --frozenWith pip:
# Generate pinned requirements
pip freeze > requirements.txt
# Or use pip-tools
pip-compile requirements.in -o requirements.txt
# Install exact versions
pip install -r requirements.txt5. Prevent Dependency Confusion
Configure package sources to prevent private/public namespace collisions:
With uv:
# pyproject.toml
[[tool.uv.index]]
name = "company-internal"
url = "https://pypi.company.com/simple"
explicit = true # Only use for explicitly specified packages
[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple"
default = trueWith pip:
# pip.conf
[global]
index-url = https://pypi.org/simple
extra-index-url =
https://pypi.company.com/simple
[install]
# Require that private packages come from internal index
trusted-host = pypi.company.com6. Vulnerability Scanning
Regularly scan dependencies for known vulnerabilities:
With pip-audit:
# Scan installed packages
pip-audit
# Scan requirements file
pip-audit -r requirements.txt
# Output as JSON
pip-audit --format json -o audit.json
# Fix vulnerabilities automatically
pip-audit --fix
# Ignore specific vulnerabilities
pip-audit --ignore-vuln PYSEC-2024-1234With uv-secure:
# Scan uv lockfile
uv-secure scan
# Fail CI on vulnerabilities
uv-secure scan --exit-code
# Generate SARIF for GitHub
uv-secure scan --format sarif -o results.sarifIn CI/CD (GitHub Actions):
name: Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Scan for vulnerabilities
run: |
uv tool install uv-secure
uv-secure scan --exit-code7. Harden Package Installs with Security Tools
Socket.dev for real-time protection:
# Install Socket CLI
npm install -g @socketsecurity/cli
# Scan Python dependencies
socket python scan requirements.txt
# Monitor CI/CD
socket ciPhylum for supply chain analysis:
# Install Phylum
curl -sSL https://sh.phylum.io/ | sh
# Analyze dependencies
phylum analyze requirements.txt
# Block malicious packages in CI
phylum check requirements.txt --fail-on-criticalSecure Local Development
8. No Plaintext Secrets in .env Files
Use secret management instead of plaintext .env files:
With 1Password:
# Store secret
op item create --category=password \
--title "API_KEY" \
--vault "Development" \
password="${API_KEY_VALUE}"
# Load secrets into environment
eval $(op inject -i .env.template -o .env)
# Run with secrets
op run -- python app.py.env.template (commit this):
API_KEY=op://Development/API_KEY/password
DATABASE_URL=op://Development/DATABASE_URL/passwordWith doppler:
# Install doppler
brew install dopplerhq/cli/doppler # macOS
# or curl -Ls https://cli.doppler.com/install.sh | sh
# Login and setup
doppler login
doppler setup
# Run with secrets
doppler run -- python app.py9. Work in Dev Containers
Isolate development environments with containers:
devcontainer.json:
{
"name": "Python Development",
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"features": {
"ghcr.io/devcontainers/features/uv:1": {}
},
"postCreateCommand": "uv sync",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"charliermarsh.ruff"
]
}
},
"remoteEnv": {
"UV_EXCLUDE_NEWER": "7 days"
}
}Docker Compose for local development:
# docker-compose.yml
version: '3.8'
services:
app:
build: .
volumes:
- .:/workspace
- uv-cache:/root/.cache/uv
environment:
- UV_EXCLUDE_NEWER=7 days
- UV_NO_SYNC=1
command: uv run python app.py
volumes:
uv-cache:Maintainer Security Practices
10. Enable 2FA for PyPI Accounts
# PyPI requires 2FA for all accounts
# Visit https://pypi.org/manage/account/two-factor/
# Use TOTP app or security key (recommended)11. Publish with Trusted Publishing (OIDC)
GitHub Actions workflow:
name: Publish to PyPI
on:
release:
types: [published]
permissions:
id-token: write # Required for trusted publishing
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- name: Build package
run: uv build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
# No API token needed - uses OIDC
skip-existing: trueConfigure on PyPI: 1. Go to https://pypi.org/manage/account/publishing/ 2. Add GitHub repository 3. Specify workflow name and environment
12. Publish with Package Attestations
Generate provenance attestations:
name: Publish with Attestations
on:
release:
types: [published]
permissions:
id-token: write
contents: read
attestations: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- name: Build
run: uv build
- name: Generate attestations
uses: actions/attest-build-provenance@v1
with:
subject-path: dist/*
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
with:
attestations: trueVerify attestations:
# Download and verify package attestations
pip download --no-deps requests==2.32.3
gh attestation verify requests-2.32.3-py3-none-any.whl \
--owner psf13. Secure CI/CD Release Pipeline
Branch protection and signed commits:
# .github/workflows/release.yml
name: Secure Release
on:
push:
tags:
- 'v*'
jobs:
security-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Verify signed commits
- name: Verify signatures
run: |
git verify-commit HEAD || exit 1
# Scan dependencies
- name: Vulnerability scan
run: |
uv tool install uv-secure
uv-secure scan --exit-code
# SBOM generation
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
format: cyclonedx-json
output-file: sbom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json14. Reduce Package Dependency Tree
Minimize dependencies to reduce attack surface:
# Analyze dependency tree
uv tree
# Check for unnecessary dependencies
uv pip list --format json | jq '.[] | select(.required_by == [])'
# Use extras for optional dependencies
# pyproject.toml
[project.optional-dependencies]
dev = ["pytest", "ruff"]
docs = ["sphinx", "mkdocs"]Package Health Practices
15. Generate and Track SBOMs
Generate SBOM with uv:
# Export to CycloneDX format
uv export --format requirements-txt | \
cyclonedx-py requirements -r -i - -o sbom.json
# With syft
syft packages dir:. -o cyclonedx-json > sbom.jsonTrack SBOMs in CI:
- name: Generate SBOM
run: |
uv export --format requirements-txt > requirements.txt
syft packages file:requirements.txt -o cyclonedx-json=sbom.json
- name: Upload to Dependency-Track
env:
API_KEY: ${{ secrets.DEPENDENCY_TRACK_KEY }}
run: |
curl -X POST https://dtrack.company.com/api/v1/bom \
-H "X-Api-Key: $API_KEY" \
-F "project=my-project" \
-F "bom=@sbom.json"16. Consult Vulnerability Databases
Check package health signals:
# Using pypistats
import pypistats
# Download statistics
stats = pypistats.recent("requests")
print(f"Recent downloads: {stats}")
# Using PyPI JSON API
import requests
response = requests.get("https://pypi.org/pypi/requests/json")
data = response.json()
# Check release cadence
releases = data["releases"]
print(f"Total releases: {len(releases)}")
# Check project URLs
urls = data["info"]["project_urls"]
repo = urls.get("Source")
print(f"Repository: {repo}")Query OSV database:
# Using osv-scanner
osv-scanner --lockfile uv.lock
# Query specific package
curl -X POST https://api.osv.dev/v1/query \
-H "Content-Type: application/json" \
-d '{
"package": {"name": "requests", "ecosystem": "PyPI"},
"version": "2.31.0"
}'17. Verify Published Package Contents
Inspect package before installing:
# Download without installing
pip download --no-deps requests==2.32.3
# Unzip and inspect
unzip -l requests-2.32.3-py3-none-any.whl
# Check for suspicious files
unzip -p requests-2.32.3-py3-none-any.whl | grep -E '\.exe$|\.dll$|setup\.py'Use quarantine tools:
# Inspect in isolated environment
import zipfile
import tempfile
import os
def inspect_wheel(wheel_path):
with tempfile.TemporaryDirectory() as tmpdir:
with zipfile.ZipFile(wheel_path, 'r') as zip_ref:
zip_ref.extractall(tmpdir)
# List all files
for root, dirs, files in os.walk(tmpdir):
for file in files:
path = os.path.join(root, file)
print(f"File: {path}")
# Check for executable permissions
if os.access(path, os.X_OK):
print(f" WARNING: Executable file")
inspect_wheel("requests-2.32.3-py3-none-any.whl")Configuration Examples
Complete pyproject.toml with Security Hardening
[project]
name = "my-secure-app"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"requests>=2.32.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"ruff>=0.3.0",
"pip-audit>=2.7.0",
]
[tool.uv]
# Dependency cooldown
exclude-newer = "7 days"
# Binary-only installations
[tool.uv.pip]
only-binary = [":all:"]
# Dependency sources
[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple"
default = true
[tool.ruff]
select = ["E", "F", "S"] # Include security checks
ignore = ["S101"] # Allow assert in tests
[tool.pytest.ini_options]
testpaths = ["tests"]Complete uv.toml for Global Configuration
# ~/.config/uv/uv.toml (macOS/Linux)
# %APPDATA%\uv\uv.toml (Windows)
# Dependency cooldown
exclude-newer = "7 days"
# Binary-only installations
[pip]
only-binary = [":all:"]
# Cache configuration
[cache]
dir = "~/.cache/uv"
# Install configuration
[install]
reinstall = falseTroubleshooting
Cooldown Blocks Required Package
Problem: exclude-newer filters out a necessary package version
Solution:
# Temporarily disable cooldown
uv sync --exclude-newer P0D
# Or add per-package override
[tool.uv]
exclude-newer = "7 days"
exclude-newer-package = { my-package = "1 day" }Binary Not Available for Platform
Problem: --only-binary :all: fails because no wheel exists
Solution:
# Allow source build for specific package
uv pip install --only-binary :all: --no-binary problematic-package requests
# Or in config
[tool.uv.pip]
only-binary = [":all:"]
no-binary = ["problematic-package"]Hash Verification Fails
Problem: Hash mismatch during installation
Solution:
# Regenerate lockfile
uv lock --upgrade-package package-name
# Or regenerate requirements
uv pip compile --generate-hashes --upgrade requirements.in
# Verify package wasn't tampered with
pip download --no-deps package-name==version
sha256sum package-name-*.whlDependency Confusion Attack
Problem: Wrong package version from wrong index
Solution:
# Use explicit indexes
[[tool.uv.index]]
name = "internal"
url = "https://pypi.internal.com/simple"
explicit = true # Only use when explicitly specified
# Then specify in dependencies
dependencies = [
"internal-package @ https://pypi.internal.com/simple/internal-package",
]CI/CD Pipeline Fails After Security Hardening
Problem: Pipeline breaks with security settings
Solution:
# Gradual rollout - start with warnings
- name: Security scan
run: uv-secure scan || true
# Then enforce
- name: Security scan (enforced)
run: uv-secure scan --exit-code
# Allow cooldown bypass for security patches
- name: Install with conditional cooldown
run: |
if [ "${{ github.event_name }}" == "dependabot" ]; then
uv sync --exclude-newer P0D
else
uv sync --exclude-newer 7d
fiReferences
Related skills
FAQ
How does this protect against malicious PyPI packages?
It enforces binary-only installs to avoid setup.py code execution, adds dependency cooldowns to skip freshly-published packages, and pins cryptographic hashes.
Which package managers does it cover?
Both uv and pip, with command-line, config-file and environment-variable examples for each.