Safeguard
Product•

SCA for Python in Safeguard: requirements, Poetry, uv and Pipenv

Safeguard SCA (GA) reads requirements.txt, Pipfile.lock, poetry.lock and uv.lock, ranks Python CVEs by reachability, EPSS and KEV, and opens fix PRs.

Safeguard Security Team
37 min read

Safeguard's software composition analysis (SCA, GA) scans Python projects from the files your team already commits: requirements.txt, pyproject.toml, Pipfile and Pipfile.lock, and poetry.lock, with uv.lock listed on Safeguard's deep dependency scanning page and PDM and Conda lockfiles listed in the CLI support matrix. It resolves the full transitive tree, matches every package and version against CVE and GHSA advisories, ranks findings with Python reachability (static analysis is GA), EPSS and CISA KEV, and can open fix pull requests for pip, Poetry and Pipenv projects, with autonomous remediation switched off until you turn it on.

That is the short answer. The rest of this post is the long one: what goes wrong in Python dependency management without SCA, which file Safeguard reads for each Python tool and where the docs say so, how the scan runs from your laptop, the app and CI, how findings get ranked, how fixes land, and where the honest limits are.

Key takeaways

  • SCA (GA) covers PyPI packages, direct and transitive. Safeguard's docs list requirements.txt, Pipfile, Pipfile.lock, pyproject.toml, poetry.lock and setup.py for manifest upload, and the deep dependency scanning page adds uv.lock. The CLI support matrix also lists PDM (pdm.lock) and Conda (conda-lock.yml).
  • Deep dependency scanning (GA) walks the resolved graph and keeps the full path from your project to each vulnerable package, so you can see which direct dependency pulled it in.
  • Reachability analysis (GA) for Python: Safeguard's reachability docs mark Python static reachability as GA, with a note that it handles virtualenv layouts. Dynamic reachability depends on a runtime collector; treat that part as something to confirm with Safeguard before you plan around it.
  • Vulnerability management with EPSS and KEV (GA) ranks each finding by a priority score that combines severity, exploit probability, known exploitation, reachability and asset context.
  • Autonomous remediation (GA, ships off): Griffin can open fix pull requests for pip, Poetry and Pipenv projects, one at a time or in bulk. Every category starts as Manual.
  • License compliance (GA) reads license metadata from pyproject.toml and license files, then checks it against your policy.
  • Policies and gates (GA) and the Safeguard CLI (GA) let you fail a CI build on a severity or a gate. SAST findings from the same CI scan are early access and are not part of the severity threshold.

The problem: Python dependencies are easy to declare and hard to see

A typical Python service declares a few dozen packages and installs several hundred. The declared list lives in one place; the installed list lives somewhere else, and the two drift.

Why Python makes inventory harder than it looks

Python has more ways to declare dependencies than most ecosystems, and they are not equivalent.

  • requirements.txt is a pip input file, not a lockfile. pip's documentation describes it as a list of arguments to pip install, and it can pull in other files with -r and constrain versions with -c. A file with requests>=2.31 tells a scanner very little about what is installed today. A file generated by a compile step with exact pins and --hash lines tells it nearly everything.
  • pyproject.toml is the packaging standard's project file. The PyPA specification defines dependencies and optional-dependencies there. These are ranges and extras, not resolved versions.
  • Lockfiles carry the resolved answer. Poetry writes poetry.lock and its documentation tells you to commit it to version control. Pipenv writes Pipfile.lock, which its docs describe as recording exact versions and hashes of every resolved dependency. uv writes uv.lock next to pyproject.toml and describes it as a universal, cross-platform lockfile that you should not edit by hand.
  • A new standard lockfile now exists too. PEP 751 defines pylock.toml; its status on peps.python.org is Final, with a resolution dated 31 March 2025. Tool support is still arriving across the ecosystem.

Two more Python details make version matching harder. Environment markers from PEP 508 (for example python_version or sys_platform) mean the same project can install different packages on different machines. And the PyPA name normalization rule lowercases a name and collapses runs of ., - and _ into one -, so Foo.Bar, foo_bar and foo-bar are the same project. A scanner that compares names as plain strings will miss matches.

The result is familiar: a vulnerable transitive package three levels down, pulled in by a library you chose for something unrelated, installed on production because nobody looked past the top of the tree.

What the standards and regulators ask for

None of this is optional for teams that sell to regulated buyers or the US government.

  • NIST SP 800-218 (SSDF 1.1, February 2022), practice PW.4.4, asks you to verify that acquired commercial, open source and other third-party components comply with your requirements throughout their life cycles. Its examples include regularly checking for publicly known vulnerabilities and building automatic detection of known vulnerabilities into the toolchain. Practice RV.1.1 asks you to gather information on potential vulnerabilities in the software and the third-party components it uses, and its examples include monitoring vulnerability databases and automatically reviewing software composition data. RV.2.2 asks for risk-based responses to each vulnerability.
  • OWASP Top 10:2025, A03 Software Supply Chain Failures, says you are likely vulnerable if you do not carefully track the versions of all components you use, including nested transitive dependencies. It maps the category to CWE-1104 (unmaintained third-party components) and CWE-1395 (dependency on a vulnerable third-party component), among others.
  • FIRST's EPSS publishes a daily probability, from 0 to 1, that a published CVE will be exploited in the wild in the next 30 days.
  • CISA's Known Exploited Vulnerabilities catalog is, in CISA's words, "the authoritative source of vulnerabilities that have been exploited in the wild", and CISA tells organizations to use it as an input to prioritization.
  • CISA BOD 26-04 (10 June 2026) now sets federal remediation timelines from four factors: KEV status, exploit automation, technical impact and asset exposure, laid out in its Table 1. It supersedes and revokes BOD 22-01, so the older fixed KEV deadlines are history, not current rules. Federal civilian agencies must follow it; everyone else can borrow the logic.

Every one of these asks the same three things of a Python team: know exactly which packages and versions you ship, find out quickly when one of them has a known vulnerability, and fix the risky ones first. That is the job SCA does.

Which Python files Safeguard reads, and where the docs say so

The wave rule for this post is simple: if docs.safeguard.sh does not list a file, we do not claim it. Safeguard's docs list Python files on three pages, and the lists differ slightly. Here is the combined view, with the page that lists each file.

Python toolManifestLockfileListed on
piprequirements.txtnone (pins live in the file)CLI support matrix, manifest upload, deep dependency scanning
PipenvPipfilePipfile.lockCLI support matrix, manifest upload; deep scanning lists Pipfile
Poetrypyproject.tomlpoetry.lockCLI support matrix, manifest upload, deep dependency scanning
uvpyproject.tomluv.lockDeep dependency scanning page only
PDMpyproject.tomlpdm.lockCLI support matrix only
Condaenvironment.ymlconda-lock.ymlCLI support matrix only
setuptools (legacy)setup.pynoneManifest upload only
PEP 751n/apylock.tomlNot listed on any Safeguard docs page today

Three practical points follow from that table.

  1. Poetry, Pipenv and plain pip are covered everywhere. If your project uses one of these, every Safeguard surface (CLI, app upload, connected repository) documents support.
  2. uv is documented on the deep dependency scanning page, not yet on the CLI support matrix or the upload page. If you run uv, scan a real repository and compare the component list against uv export output before you rely on it. If the counts disagree, raise it through support and keep a pinned requirements.txt export alongside as a fallback.
  3. pylock.toml is not listed. If you have moved to the PEP 751 format, do not assume Safeguard reads it. Ask Safeguard, or export to a listed format in the meantime.

The manifest upload page also explains why lockfiles matter. With a manifest only, Safeguard resolves transitive dependencies at scan time and version ranges resolve to the latest match. With a manifest plus a lockfile, it uses the exact locked versions, so the result reproduces. Its recommendation is to upload both whenever you have both.

How Safeguard resolves the Python dependency tree

Safeguard's SCA page says it resolves the complete dependency graph from manifests and lockfiles, including deeply nested transitive dependencies, and that manifests and lockfiles are both honored so resolution matches what actually installs.

Depth and the transitive path

The deep dependency scanning page (GA) describes walking every edge in the resolved graph. At each node Safeguard resolves the package URL and version against its vulnerability knowledge graph, computes license and provenance, feeds the node into reachability analysis, and captures the full path from your project root to the leaf. Shared subgraphs are resolved once, not once per parent. Safeguard markets this depth as up to 100 levels.

In the ESSCM app, clicking a finding shows that transitive path as a small tree:

your-app
└─ <direct dependency>@<version>
   └─ <vulnerable package>@<version>   ← vulnerable here

The docs say the panel explains the chain of imports that pulled the package in, whether that chain is reachable, whether any other path brings the same package in, what the safe version is, and which other transitive changes that version would introduce. That last point matters a lot in Python, where bumping one package can force a resolver to move three others.

Lockfile first, resolver second

For a Python repository with a lockfile, the lockfile is the source of truth. For one without (a bare requirements.txt with ranges, or a pyproject.toml with no lock), the CLI can resolve transitive dependencies for you. The CLI usage page lists the --resolve-transitive flag for safeguard scan, and the deep dependency scanning page shows a depth option:

safeguard scan --dir . --resolve-transitive --transitive-depth 100

Treat any resolution done at scan time as a best guess about what would install today, not a record of what did install. The CLI usage page also says safeguard <command> --help wins if it disagrees with the docs, so check safeguard scan --help for the default depth in your build.

Private packages

Many Python teams publish internal packages to a private index. The CLI has an --org-namespace flag for namespaces that identify your own private packages, so they are treated as yours rather than looked up as public PyPI projects. The manifest upload troubleshooting notes also warn that private package references may not resolve from a manifest alone; another reason to commit and scan the lockfile.

Dev dependencies

Poetry dependency groups, Pipenv [dev-packages] and a separate requirements-dev.txt all describe packages that should not ship. The SBOM generation page lists an Include Dev Dependencies option on a project. Decide deliberately: include them if your build images or CI runners are in scope for your risk program, exclude them if you want the findings list to reflect production only.

How findings are matched and what each one contains

Safeguard's vulnerability docs list its data sources as NVD, GitHub Security Advisories, OSV, the CISA KEV catalog, vendor bulletins and researcher disclosures. For Python specifically, the PyPA advisory database publishes advisories for packages on pypi.org in the OSV schema, which is one reason OSV coverage matters for a Python backlog.

The SCA page says each finding carries:

  • the vulnerable component and version,
  • the CVE or GHSA identifiers,
  • severity, with EPSS and KEV context,
  • the full transitive path from your project root,
  • the reachability verdict where available,
  • and the safe upgrade target.

The vulnerabilities page adds the fields you see in the table: CVE ID, CVSS score, EPSS score, an Exploitable flag for known exploitation, affected component, fixed version, description, references and reachability. Findings land in the same findings model as every other Safeguard engine, with one severity scale and one status lifecycle.

New advisories do not wait for your next commit. The vulnerabilities and SBOM pages both say Safeguard re-scans all your projects daily and re-evaluates them when new CVEs are published. You can add triggers per project: on push, on a schedule, or on a new tag.

Reachability for Python: what is GA and what to confirm

Reachability asks a narrower question than "is this package installed?": does your code actually call the vulnerable function?

Safeguard's reachability page lists support by language. For Python it shows static reachability as GA and dynamic reachability as GA, with the note "Handles virtualenv layouts." Static reachability builds a call graph from your source, lockfile and bytecode and checks whether any path leads to the vulnerable sink. Dynamic reachability records which modules and functions are loaded and invoked at runtime through a collector in your workloads. Because that collector is part of Safeguard's runtime tooling, confirm with Safeguard what dynamic reachability needs in your environment before you count on it. Static reachability from your repository works without it.

Each finding gets one of four classes:

  • Reachable: a real call path exists.
  • Conditionally reachable: a path exists but depends on a feature flag, route or configuration that is not enabled.
  • Unreachable: no call path exists.
  • Unknown: the analyzer could not decide. Safeguard marks these conservatively as potentially reachable.

Where Python fights static analysis

Python is dynamic by design, and the reachability docs are candid about where analysis is weakest: reflection, eval and unsafe dispatch, data-driven vulnerabilities, and code paths that only run under configurations the analyzer cannot enumerate. In Python terms, expect more Unknown verdicts around:

  • importlib.import_module with a computed name, and __import__,
  • plugin systems loaded through entry points,
  • getattr dispatch on user input,
  • serialization libraries that rebuild objects from data,
  • framework magic that wires views or tasks from strings in settings.

Safeguard does not suppress Unknown findings, which is the right default. Your job is to read them as "the tool could not prove this is safe", not "the tool thinks this is risky", and to fall back on EPSS, KEV and runtime context for those.

For a deeper look at the ranking model across languages, see our post on reachability, EPSS and KEV in Safeguard.

EPSS and KEV prioritisation, applied to a Python backlog

Safeguard gives each finding a priority score from 0 to 100. The reachability page lists the inputs: CVSS base severity, EPSS probability, KEV listing, public proof-of-concept availability, reachability class, environment, exposure, data sensitivity and business criticality. Safeguard ingests the EPSS feed daily.

The default bands are easy to reason about:

CombinationDefault priority band
Reachable, on KEV, in production90 to 100 (P0)
Reachable, EPSS above 0.5, in production75 to 89 (P1)
Reachable, high CVSS, in staging50 to 74 (P2)
Unreachable, any severity0 to 30 (backlog)

You can change the weights per tenant under Settings → Prioritization. Business context comes from policy: you declare which assets are regulated, handle customer data or sit on a revenue path, and findings on those rank higher.

In the ESSCM Vulnerabilities tab, the table has Priority and Reachability columns and sorts by priority by default. The docs list filters you can type directly:

reachable:true
kev:true
epss:>0.3
env:production reachable:true kev:true

The last one is the P0 queue. You can also ask Griffin in plain English, for example "Show my P0 findings in production that are reachable."

For a Python team, this ordering usually means the work starts with a small number of web framework, HTTP client, parser or image library findings that sit on a request path, and ends with a long tail of build and test tooling that never runs in production.

Step by step: scanning a Python project with Safeguard

The steps below use only commands, screens and settings shown on docs.safeguard.sh. Where the docs show an outcome but not a screen, we describe the outcome.

1. Get the lockfile right before you scan

Pick the source of truth for each service and commit it.

  • pip: generate a fully pinned requirements.txt (with hashes if you use pip's hash-checking mode, which pip turns on for every requirement once any line has a --hash).
  • Poetry: commit poetry.lock.
  • Pipenv: commit Pipfile.lock.
  • uv: commit uv.lock, and see the coverage note above.

If a repository has several requirements*.txt files, decide which ones describe production and which describe development or tests. Safeguard's upload docs show a Python example with requirements.txt, requirements-dev.txt and pyproject.toml uploaded together.

2. Run a first scan from your laptop

Install the CLI (GA) and open the interactive terminal:

curl -fsSL https://cli.safeguard.sh/install | bash
safeguard

Inside the terminal, type /login, approve the device code in your browser, then type /scan .. A scan from the signed-in terminal uploads the SBOM and findings to the platform. Your source does not leave the machine.

If you just want files on disk, run the scan directly:

safeguard scan --dir .

Results are written as JSON under ./output, one folder per scan, including vuln.json, license.json and collection.json. safeguard scan exits 0 when the scan ran whatever it found, 1 when the command itself failed, and 2 when results were written but the vulnerability scan did not run, so a script never mistakes a skipped scan for a clean one.

The CLI is a single binary. Its installation and support pages say there is no Node, Python or JVM runtime to install alongside it, so it does not care which Python version or virtualenv your project uses.

If you would rather keep scanning on hardware you control and still see results in the app, the local runner (GA) does that. Our post on choosing between the CLI and the local runner covers the trade-off.

3. Connect the repository or upload the files

For continuous coverage, connect the repository in the app. Go to Integrations, filter by Source Code (SCM) and click Connect on GitHub, GitLab, Bitbucket or Git. Pick the repositories, set the branch or tag, project name and version, and click Connect. The project then appears under Projects with its findings and vulnerability counts.

If you cannot connect a repository, upload the files instead:

  1. Go to Integrations and filter by Upload.
  2. Click Connect on the Upload Manifest File card.
  3. Enter a name and optional description, then drag in your files (for example pyproject.toml and poetry.lock, or Pipfile and Pipfile.lock).
  4. Set the project name and version, then click Upload to start the scan.

Upload projects are snapshots. Re-upload when dependencies change, or switch to a connected repository.

4. Set the project options

On the project, choose whether to Include Dev Dependencies, and whether to turn on Deep Analysis, which the SBOM page describes as enabling Griffin's deeper analysis. Add scan triggers on push, on a schedule or on release, on top of the daily re-scan.

5. Read the first results

Open the project's vulnerabilities. Sort by Priority, then filter reachable:true kev:true to see the findings that most deserve attention this week. Click one to open the transitive path, the fixed version and the reachability verdict. Then check what else that upgrade would move before you change anything.

For each finding you can View Fix to compare upgrade options and breaking change warnings, Mark False Positive with a justification (subject to approval if your policy requires it), or Accept Risk with an expiry date and a business justification.

6. Check licenses at the same time

License compliance (GA) runs on the same graph. Safeguard reads license fields from package metadata, including pyproject.toml, recognizes SPDX identifiers, matches full license texts in files such as LICENSE and COPYING, and reads source headers. When signals disagree, it raises a license discrepancy finding.

Policies are written as YAML. This is the shape from Safeguard's docs:

apiVersion: safeguard.sh/v1
kind: LicensePolicy
metadata:
  name: default
spec:
  allow:
    - MIT
    - BSD-3-Clause
    - Apache-2.0
  deny:
    - GPL-3.0-or-later
    - AGPL-3.0-or-later
  require_review:
    - LGPL-3.0-or-later

There is no CLI command for license reports or policy evaluation yet. A local safeguard scan writes license.json with what it found, and the app's Licenses dashboard exports per-asset license manifests. Our post on license obligations, OpenChain and attribution goes deeper.

7. Fix the top findings with pull requests

Safeguard calls this AI Remediate in the ESSCM docs; the offering files call the broader capability autonomous remediation (GA). It is switched off when you start: every category is set to Manual, and nothing applies on its own until an administrator turns on the master toggle.

You can still use it by hand from day one:

  • Single vulnerability: open the vulnerability, click AI Remediate, review the proposed changes, then click Create Pull Request.
  • Bulk: select several vulnerabilities, click AI Remediate Selected, review, and create pull requests. Griffin batches compatible fixes into one pull request per repository.
  • Page level: click AI Remediate at the top of the Vulnerabilities page to review a plan for every fixable finding.

The docs say a dependency fix upgrades the vulnerable package to a fixed version, handles transitive conflicts, and updates lockfiles. For Python, the AI Remediate page lists pip, poetry and pipenv with full support. It does not list uv or PDM.

Each pull request includes a title with the CVE reference, a description of the vulnerability, a summary of changes, breaking change warnings, test results when configured, and links to advisories. Griffin classifies breaking change risk as Safe, Minor, Major or Critical.

Settings live under Settings → AI Remediate: auto-create pull requests for safe fixes, require approval, run tests before creating pull requests, a breaking change policy and a branch naming pattern. When you do move a category to Auto, the offering files list four strategies for vulnerabilities and dependencies: Safest, Balanced (minor and patch updates, minimal breaking changes), Aggressive (all available fixes including major versions) and Smart (the model chooses per item). See our post on the eight remediation categories for the full matrix.

From the terminal, safeguard fix updates vulnerable dependencies; run safeguard fix --help to see the options in your build. A worked Python example lives in fix Python dependency vulnerabilities with the Safeguard CLI.

8. Add the scan to CI

Safeguard's CI docs keep this to one shell step. There is no plugin or marketplace action to install.

  1. Create a secret key under Settings → Developer with the scopes scans:trigger and scans:read. You can lock it to one project.
  2. Store it in your CI as a secret named SAFEGUARD_API_KEY.
  3. Add the step and push.

For GitHub Actions, the docs give this workflow, saved as .github/workflows/safeguard.yml:

name: Safeguard
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - name: Safeguard scan
        env:
          SAFEGUARD_API_KEY: ${{ secrets.SAFEGUARD_API_KEY }}
          SAFEGUARD_INSTALL_DIR: ${{ runner.temp }}/safeguard-bin
        run: |
          set -eu
          curl -fsSL https://cli.safeguard.sh/install | bash
          "$SAFEGUARD_INSTALL_DIR/safeguard" ci scan --wait --fail-on critical

No checkout step is needed in the default mode, because Safeguard clones the connected repository. Each push becomes a new project version named after the branch and commit, such as main-a1b2c3d.

Two modes matter for Python teams:

ModeCommandScans
On Safeguard (default)safeguard ci scanSAST (early access), SBOM, dependency vulnerabilities
On the runnersafeguard ci scan --localSBOM, dependency vulnerabilities

With --local, add actions/checkout before the step. Only results leave the runner, which suits self-hosted or firewalled Git servers. The docs say it reads dependencies from lockfiles and manifests, and name requirements.txt in their example list.

The failure flags:

  • --fail-on critical (or high, medium, low) fails the step on a dependency vulnerability at or above that severity.
  • --fail-on-gate all (or a gate name or id) fails it when a policy gate is not Passed or Warning.
  • Drop --fail-on to report without failing.

SAST findings from the default mode show in Safeguard but are not part of --fail-on yet. Pin a CLI version with SAFEGUARD_VERSION in the step's environment so a release does not change a build under you. Pull requests from forks get no secrets on GitHub; the docs recommend scanning pushes only, or using pull_request_target with care and never running the fork's code in that job.

9. Gate on what matters, not on raw counts

A severity threshold is a blunt tool. Policies and gates (GA) let you block on the combination you care about. The reachability docs show a policy that blocks production when a reachable or conditionally reachable finding is also on KEV:

- id: no-reachable-kev-in-prod
  condition: |
    env == "production"
    AND any(vulnerabilities,
      reachability_class in ["reachable", "conditional"]
      AND kev == true
    )
  effect: BLOCK

Gate actions are block, warn, notify or require approval. You can also write a policy in plain English and let Safeguard draft it; see plain English policies to release gates. In CI, --fail-on-gate connects the gate to the build.

10. Keep watching after the merge

Daily re-scans and feed-driven re-scoring mean a finding can appear on a project nobody has touched in months. Route those to the owning team and give each band a target time to fix. The Prioritization dashboard shows urgent queue size over time, time to remediate by band and backlog aging, which are the numbers to bring to a review.

Gating a build on the build machine instead

Some teams want files, not a platform call. The CI docs show safeguard scan on the runner writing results you can keep as artifacts, then a jq check that fails the job if any finding is critical:

safeguard scan --dir . --output out/
jq -e '[.data.matches[].vulnerability.severity | ascii_downcase]
  | index("critical") | not' out/*/vuln.json

safeguard scan itself never fails on findings. The CLI usage page is explicit that there is no severity threshold flag on it, and that gating on findings belongs to policy gates in the platform. On a fresh CI container, the docs say the scanner tools that safeguard scan uses must be on the PATH or in TOOLS_DIR, because the interactive terminal normally downloads them on first run.

Decision table: which route fits your Python setup

Your situationWhat Safeguard readsRoute to start withWhat you still own
Poetry project with poetry.lock committedpyproject.toml and poetry.lockConnect the repo, add safeguard ci scanKeeping the lock in sync with pyproject.toml
Pipenv projectPipfile and Pipfile.lockConnect the repo or upload both filesRunning pipenv lock after changes
pip with pinned, hashed requirements.txtrequirements.txtCLI safeguard scan --dir . or connected repoRegenerating pins; separating dev files
pip with loose rangesrequirements.txt, resolved at scan timeAdd a lock step, or --resolve-transitive as a stopgapAccepting that results reflect today's resolution
uv with uv.lockuv.lock per the deep scanning docsScan a sample repo and compare to uv exportValidating coverage; fix PRs are not documented for uv
PDM or Condapdm.lock or conda-lock.yml per the CLI support matrixCLI scan firstConfirming the app and fix PR path with Safeguard
PEP 751 pylock.tomlNot listedExport to a listed formatAsking Safeguard about support
Firewalled Git serverLockfiles and manifests on the runnersafeguard ci scan --local or the local runnerRunner maintenance
Upload only, no repo accessUploaded manifests and lockfilesUpload Manifest FileRe-uploading on every change

Requirement vs what Safeguard does vs what you still own

RequirementWhat Safeguard doesWhat you still own
SSDF PW.4.4: verify third-party components against your requirementsSCA (GA) inventories every PyPI package and checks it against advisories and your license policyDefining the requirements and approving exceptions
SSDF RV.1.1: gather vulnerability information on componentsMatches against NVD, GHSA, OSV and KEV; re-scans dailyInvestigating credible reports that are not yet in a database
SSDF RV.2.2: risk-based response per vulnerabilityPriority score from reachability, EPSS, KEV and context; Accept Risk with expiryThe decision and its sign-off
OWASP A03:2025: track all component versions, including transitiveDeep dependency scanning (GA) with full transitive pathsCommitting lockfiles so the inventory is exact
CISA KEV and BOD 26-04 style timelinesKEV flag, kev:true filter, gates that block reachable KEV findingsSetting and meeting your own timelines; federal agencies follow BOD 26-04 Table 1
EPSS-informed triageDaily EPSS ingest and epss: filtersChoosing thresholds that fit your risk appetite

What Safeguard does not do

Honest limits, specific to Python SCA today:

  1. It does not read every Python file format. pylock.toml (PEP 751) is not listed on any Safeguard docs page. uv.lock is listed on the deep dependency scanning page only, not on the CLI support matrix or the manifest upload page. PDM and Conda lockfiles are listed in the CLI support matrix only. setup.cfg is not listed anywhere. Test your exact layout.
  2. Fix pull requests are documented for pip, Poetry and Pipenv only. The AI Remediate ecosystem table does not list uv, PDM or Conda. For those projects, expect to make the upgrade yourself using the fixed version Safeguard reports, or ask Safeguard about coverage.
  3. Autonomous remediation does nothing until you turn it on. It ships with every category set to Manual. Even when on, it opens pull requests; your reviewers and your tests decide whether they merge.
  4. Reachability cannot prove everything safe in Python. Dynamic imports, entry point plugins, getattr dispatch, eval and data-driven bugs push findings to Unknown, and Safeguard does not suppress those. Dynamic reachability needs a runtime collector you would have to deploy; confirm what that involves before you depend on it.
  5. It does not scan Python packages your OS package manager installed outside the project. SCA works from your manifests and lockfiles. Packages installed by apt or baked into a base image are a container scanning question, which Safeguard handles separately for images.
  6. It does not fix vulnerabilities in your own Python code. SCA covers third-party packages. Safeguard's SAST is early access and is not part of the CI severity threshold today.
  7. There is no CLI command yet for querying findings by priority, listing licenses or querying the dependency graph. The docs say so on the reachability, license and deep scanning pages. Use the app, or the JSON files a local scan writes.
  8. It does not guarantee a quieter backlog. Prioritization ranks findings; how much shrinks depends on your code, your context labels and your thresholds.

Common mistakes

  • Scanning pyproject.toml without its lockfile. Ranges resolve to whatever is latest at scan time, so two scans a week apart can disagree with each other and with production. Commit and scan the lock.
  • Mixing production and development requirements. If requirements.txt includes pytest and linters, your production findings list fills with packages that never ship. Split the files, or use the Include Dev Dependencies option deliberately.
  • Comparing package names as strings. Pillow and pillow, typing_extensions and typing-extensions are the same project under PyPA normalization. When you reconcile Safeguard's list with pip freeze, normalize first.
  • Treating Unknown reachability as safe. Unknown means the analyzer could not decide. In plugin-heavy Python apps, a lot of real risk sits there.
  • Gating CI on raw severity alone. --fail-on critical is a reasonable start, but a gate on reachable KEV findings in production blocks fewer harmless builds and more dangerous ones.
  • Forgetting fork pull requests. GitHub gives fork pull requests no secrets, so the scan step fails or silently does not run. Plan for pushes only, or a carefully scoped pull_request_target.
  • Leaving the CLI version unpinned in CI. A new release can change output under you. Set SAFEGUARD_VERSION.
  • Turning on Aggressive remediation first. Start with Manual and review a few pull requests, then move one category to Auto with a conservative strategy and tests enabled.
  • Assuming uv coverage is identical to Poetry coverage. The docs list them on different pages, and fix pull requests are documented for Poetry, not uv. Verify.

FAQ

Does Safeguard support Poetry lockfiles?

Yes. poetry.lock with pyproject.toml is listed on Safeguard's CLI support matrix, manifest upload page and deep dependency scanning page, and the AI Remediate page lists Poetry with full support for fix pull requests.

Does Safeguard read uv.lock?

Safeguard's deep dependency scanning page lists uv.lock among the PyPI files it resolves. The CLI support matrix and manifest upload page do not list it yet, and fix pull requests are not documented for uv. Scan a sample repository and compare the results before you depend on it.

Can Safeguard scan a requirements.txt without a lockfile?

Yes. requirements.txt is listed everywhere. If it uses ranges, the result reflects what resolves at scan time. The CLI's --resolve-transitive flag resolves transitive dependencies when no lockfile exists. A fully pinned file, ideally with hashes, gives exact results.

Is Python reachability analysis generally available in Safeguard?

Safeguard's reachability docs list Python static reachability as GA and note that it handles virtualenv layouts. Dynamic reachability is also listed as GA but relies on a runtime collector, so confirm what it needs in your environment.

How does Safeguard prioritise Python vulnerabilities?

Each finding gets a 0 to 100 priority score built from CVSS, EPSS, KEV, proof-of-concept availability, reachability class, environment, exposure, data sensitivity and business criticality. Reachable findings on KEV in production rank highest by default, and weights are configurable per tenant.

Will Safeguard open pull requests to fix vulnerable Python packages automatically?

It can, for pip, Poetry and Pipenv projects, but autonomous remediation ships switched off with every category set to Manual. You can click AI Remediate on a finding to get a reviewed pull request at any time, and an administrator decides if and when anything runs on its own.

Does Safeguard detect licenses in Python packages?

Yes. License compliance (GA) reads license metadata such as the pyproject.toml license field, SPDX identifiers and license files, classifies each license by obligation, and enforces your allow, deny and review lists through policy.

How do I fail a GitHub Actions build on Python vulnerabilities?

Add the safeguard ci scan --wait --fail-on critical step from Safeguard's GitHub Actions guide with a SAFEGUARD_API_KEY secret. Use --fail-on high for a stricter threshold, or --fail-on-gate all to fail on policy gates instead.

Does my Python code leave my machine when I scan with the Safeguard CLI?

Not when you scan from the signed-in interactive terminal: it uploads the SBOM and findings, not your source. With safeguard ci scan --local, only results leave the CI runner. The default safeguard ci scan mode has Safeguard clone the connected repository.

Does Safeguard support PEP 751 pylock.toml?

It is not listed on any Safeguard docs page as of this post. Export to a listed format, such as a pinned requirements.txt or the lockfile your tool already writes, and ask Safeguard about support.

Fix it, then keep it fixed

Python dependency risk rarely ends with one upgrade. A pinned version you fixed this week can come back through a different parent package next month, and some advisories land before any fixed release exists. The block below is Safeguard's standard remediation guidance; the autonomous remediation part applies directly to pip, Poetry and Pipenv projects, and Gold Open Source shows affected and fixed versions for PyPI advisories for free.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

To try this on your own Python service, install the CLI from safeguard.sh/download and keep the Safeguard docs open for the exact flags in your build. The two demos below show the same flows this post describes.

The first demo is a real Safeguard CLI session. It installs the CLI, opens the interactive terminal, and asks it to fix a lodash CVE in an npm repository: the assistant reads the finding and the advisory, asks before any command that changes something, edits the manifest, runs the install and the tests, prints a summary and pushes nothing. The ecosystem differs, but the loop is the one you would run on a vulnerable package in poetry.lock or requirements.txt, followed by /scan . to confirm the finding is gone.

Click through it at your own pace, or open it full screen.

The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. That is the route for Python repositories on a firewalled Git server or a laptop you do not want a cloud clone of: the scan runs where the code is, and the SBOM and findings go to Safeguard.

Click through it at your own pace, or open it full screen.

For related reading, see how to pin Python dependencies and use hash checking, what a scan reports for Python extras and environment markers, SBOM generation and analysis in Safeguard and the companion post in this series, SCA for JavaScript and TypeScript in Safeguard.

Quick check

Which Python lockfile is listed on Safeguard's deep dependency scanning page but not yet on its CLI support matrix?

uv.lock. The deep dependency scanning page lists it among the PyPI files Safeguard resolves; the CLI support matrix and the manifest upload page do not. Fix pull requests are documented for pip, Poetry and Pipenv, not uv, so validate uv coverage on a sample repository first.

What does Safeguard do with a Python finding whose reachability is Unknown?

It keeps it. Unknown means the analyzer could not decide, often because of dynamic imports, plugin entry points or eval. Safeguard treats it as potentially reachable and does not suppress it, so you rank it on EPSS, KEV and runtime context instead.

Sources

Last reviewed: 24 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.