Python Supply-Chain & Application Security

Python Supply-Chain & Application Security

Overview

Python application security splits into two layers that share one toolchain:

  1. Application security (SAST) — find vulnerabilities in your own code (bandit).
  2. Supply-chain security — defend the dependencies and the path your artifacts travel: know what you ship (SBOM), know if it is vulnerable (pip-audit), prove where it came from (sigstore/PEP 740 attestations), and guarantee you install exactly what you locked (hash pinning).

The canonical layered (“defense in depth”) posture for a 2026 Python project: pin + hash dependencies → audit them in CI (pip-audit) → SAST-scan your code (bandit) → generate an SBOMpublish with Trusted Publishing + attestations. Each layer closes a gap the others cannot; none is sufficient alone (hash pinning, for example, will faithfully pin a package that was already malicious on day one).

The four foundational PyPA/PyCQA tools — pip-audit, bandit, pip hash mode, and the PEP 740 attestation chain — are free, open source, and require no account or API key for the scanning paths.

Core Concepts

1. Dependency auditing — pip-audit

2. SBOM generation (Software Bill of Materials)

3. Static application security testing — bandit

4. Provenance — sigstore, PEP 740 attestations & Trusted Publishing

5. Hash-pinned dependencies (reproducible, tamper-evident installs)

Tools / Frameworks (quick map)

Need Tool Note
Audit deps for known CVEs pip-audit PyPA; OSV + PyPA Advisory DB
SAST on your code bandit PyCQA; AST plugins, severity×confidence
SBOM (Python project) cyclonedx-py / uv export --format cyclonedx CycloneDX, hash-aware
SBOM (container/filesystem) syft CycloneDX or SPDX
Provenance / signing Trusted Publishing + PEP 740 attestations Sigstore keyless, auto via gh-action-pypi-publish ≥1.11.0
Verify attestations pypi-attestations identity-bound verification
Hash pinning pip-compile --generate-hashes / uv lock + pip install --require-hashes reproducible, tamper-evident
Repo/workflow security score OpenSSF Scorecard (scorecard CLI / Action) health metrics + risk checks
Harden GitHub Actions workflows zizmor flags pull_request_target misuse, unpinned third-party actions, secret handling
3rd-party SCA (commercial/alt) Snyk, Safety, Trivy, Grype, Dependabot/Renovate overlap with pip-audit; Trivy/Grype also scan containers

Methodology — layered project posture

  1. Lock + hash every dependency (uv lock or pip-compile --generate-hashes); install with --require-hashes (uv sync enforces the lock).
  2. Audit in CIpip-audit -r requirements.txt (or against the lockfile); fail the build on findings; use --fix --dry-run to triage upgrades. Do not auto-update to latest blindly.
  3. SAST in CIbandit -r src/ -c pyproject.toml; run HIGH-severity only as a blocking gate, full set as non-blocking/local; adopt via a baseline.
  4. Generate an SBOM as a build artifact (cyclonedx-py for the app, syft for the image); attach to the release; PEP 770 to embed in wheels you publish.
  5. Publish with provenance — Trusted Publishing (OIDC, no token) + automatic PEP 740 attestations via gh-action-pypi-publish.
  6. Harden the pipeline itself — pin third-party Actions to a full commit SHA, run zizmor on workflows, track posture with OpenSSF Scorecard. The supply chain includes your CI, not just your deps.

Practical Patterns

# CI audit gate (non-zero exit fails the build)
pip-audit -r requirements.txt --strict --desc

# Audit a PEP 751 lockfile and emit a CycloneDX SBOM at the same time
pip-audit -r pylock.toml -f cyclonedx-json -o sbom.json

# bandit: blocking HIGH gate in CI, configured from pyproject
bandit -r src/ -c pyproject.toml --severity-level high --confidence-level medium

# bandit baseline adoption on a legacy repo
bandit -r src/ -f json -o .bandit-baseline.json   # once, on a clean-enough commit
bandit -r src/ -b .bandit-baseline.json            # every PR: only NEW issues fail

# generate fully hashed, pinned requirements, then enforce on install
pip-compile --generate-hashes -o requirements.txt requirements.in
pip install --require-hashes -r requirements.txt

# project SBOM (CycloneDX) from the current environment
cyclonedx-py environment -o sbom.cdx.json
# pyproject.toml
[tool.bandit]
exclude_dirs = ["tests", ".venv", "build"]
skips = ["B101"]            # asserts are fine in tests; scope properly instead of blanket-skipping in src
# severity/confidence are passed as CLI flags, not config keys
# .github/workflows/release.yml — Trusted Publishing + automatic PEP 740 attestations
permissions:
  id-token: write            # REQUIRED for OIDC Trusted Publishing + attestations
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<full-commit-sha>   # pin 3rd-party actions to a SHA
      # ... build sdist + wheel ...
      - uses: pypa/gh-action-pypi-publish@release/v1   # ≥1.11.0 → provenance by default
        # no password/token needed: PyPI is configured as a Trusted Publisher for this repo

Anti-Patterns

Troubleshooting

2025-2026 threat landscape (why this matters)

References (sources)