Safeguard
Supply Chain Security•

Fix Python dependency vulnerabilities with the Safeguard CLI

Scan a Python project, upgrade the flagged package with pip, pip-tools, Poetry or uv, approve each step, then test and rescan with the Safeguard CLI.

Safeguard Security Team
19 min read

To fix a vulnerable Python dependency with the Safeguard CLI, scan the project, ask the assistant to upgrade the flagged package, approve each edit and command it proposes, then run your tests and scan again. The same flow works for plain requirements files, pip-tools, Poetry and uv, and scans run on your machine with only results sent to the platform.

You do not have to give up the AI plan you already use to do this. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. If you would rather stay in the chat app you know, connect Claude or ChatGPT to the Safeguard MCP server and ask about your findings from there.

What you will have at the end of this guide:

  • The Safeguard CLI installed and signed in on your machine.
  • A local list of vulnerable Python packages, with the fixed version for each one.
  • A minimal, tested change to your requirements file, pyproject.toml or lockfile that removes the finding.
  • A rescan that confirms the fix, and a commit you wrote yourself.
  • A repeatable routine you can run from the terminal, the desktop app or your phone, plus a CI step that keeps the fix in place.

Package names in the examples below are placeholders such as example-parser. Swap in the names from your own findings.

How Python dependency fixes work

Every Python fix comes down to one question: what decides which version gets installed? That depends on whether the package is direct or transitive, and which tool owns your lockfile.

Direct dependencies are the ones you list yourself, in requirements.txt, requirements.in or the dependencies table of pyproject.toml. Fixing one usually means raising its pin or lower bound.

Transitive dependencies arrive because something you depend on needs them. You never typed their names, so the fix is either upgrading the parent that pulls them in, or adding a constraint that forces a safe version while keeping the parent as it is.

Lockfiles record the exact versions that were resolved. A fix is not finished until the lockfile changes too, because that file is what your CI and your teammates install from. The uv docs say plainly that uv.lock "should be checked into version control, allowing for consistent and reproducible installations across machines", and the same logic applies to poetry.lock and a compiled requirements.txt.

Here is how each common tool handles a targeted upgrade, using commands from each project's own docs:

ToolWhere versions liveUpgrade one packageSee who pulls it inForce a transitive version
pip with requirements filesrequirements.txt, often from pip freezeEdit the pin, then python -m pip install -r requirements.txtRead the tree in your scan resultsA constraints file passed with -c
pip-toolsrequirements.in compiled to requirements.txtpip-compile --upgrade-package name or -P name==x.y.zComments in the compiled filePin it in requirements.in
Poetrypyproject.toml plus poetry.lockpoetry update name, or poetry add name@^x.y if the constraint blocks itpoetry show --why and poetry show --treeUpgrade the parent, or add it as a direct dependency
uvpyproject.toml plus uv.lockuv lock --upgrade-package name or name==x.y.zuv tree --invert --package nameconstraint-dependencies in [tool.uv]

A few details from the docs are worth knowing before you start:

  • pip's upgrade strategy. python -m pip install --upgrade SomePackage upgrades the named package but, by default, not its dependencies, because the default strategy is only-if-needed. Pass --upgrade-strategy=eager only if you really want everything below it upgraded too.
  • Constraints files. pip describes constraints files as "requirements files that only control which version of a requirement is installed", and "including a package in a constraints file does not trigger installation of the package." That makes them a clean way to hold a transitive package at a safe version. The pip docs also note that since pip 26.2, constraints files no longer affect isolated build environments.
  • Poetry respects your constraints. poetry update foo does nothing if foo is pinned to ~2.3 and the fix is in a newer minor release. You change the constraint with poetry add, for example poetry add example-parser@^2.4. Also, a plain poetry lock keeps versions already in the lockfile, so it will not pull in the fix by itself.
  • uv constraints and overrides. uv's constraints "can only reduce the set of acceptable versions for a package", while overrides "can expand" them. Use constraint-dependencies to raise the floor of a transitive package. Reach for override-dependencies only when a parent declares a range that excludes the fixed version, and treat that as a temporary measure, because you are overruling the parent's own requirement.

The rule: change the smallest thing that removes the finding, then prove it with tests and a rescan.

Do the work properly with the Safeguard CLI

The Safeguard CLI scans code, container images and SBOMs on your own machine, sends only the results to the platform, and includes an assistant that can fix what it finds. It can write code, not only scan. For Python work, that means one tool reads the finding, opens your pyproject.toml, proposes the edit, runs pip, poetry or uv with your approval, runs pytest, and then scans again.

Install

On macOS and Linux:

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

On Windows, in PowerShell:

irm https://cli.safeguard.sh/install.ps1 | iex
safeguard --version

The CLI is a single static binary that needs no Node, Python or JVM, so installing it will not touch your virtual environments or system Python. It runs on macOS, Linux and Windows, on x86_64 or arm64 (Windows ships x86_64 only).

First session

  1. Open a terminal in the root of your Python project, the folder with pyproject.toml or requirements.txt.
  2. Run safeguard with no arguments. The interactive terminal opens.
  3. Type /login. Your browser opens so you can sign in.
  4. Type /scan . to scan the current folder.
  5. Ask in plain language, for example "which Python packages in this repo have high or critical findings, and what are the fixed versions?"

The assistant has tools for your local files and for your Safeguard data, and it picks whichever the question needs. Use @ to attach a specific file from the folder you started in, such as @pyproject.toml or @requirements.txt, so the assistant works from exactly the file you mean.

The recorded session below shows the whole loop on a Mac. It uses a Node sample project called payments-api, so the package manager is npm, but the flow is the same for Python: install with the curl command (checksum verified), run safeguard --version, open the interactive terminal and ask "fix the lodash CVE in this repo". The assistant reads the finding, files and advisory, asks before each command (press y or n), edits package.json to lodash 4.17.21, runs npm install and npm test with approval, and prints a summary with a suggested commit message. It pushes nothing. The session ends with /permissions, /model and /scan ..

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

Writing and fixing code

A good Python fix prompt names the outcome and the limits. For example:

Fix the high severity findings in @pyproject.toml. Keep each change minimal,
update uv.lock, run pytest, and do not change any other package.

Naming the tool and the test command up front saves a round trip. The assistant still asks before each edit and command, and you stay the person who commits.

Using your Claude, ChatGPT or Codex plan

You can run the assistant on the plan you already have. See the CLI docs or type /model to see and switch the model for the session. If you prefer the chat apps, connect them to the Safeguard MCP server instead; our guides for Claude and ChatGPT cover the steps.

Approvals

Tools that change things ask before they act. /permissions sets how much asks:

  • Ask: reads run; edits and commands ask first.
  • Accept edits: edits in this folder and read-only commands run; other commands ask.
  • Plan: read and plan only. Useful for "tell me what you would change" before any edit.
  • Auto: read-only commands and what the policy allows run; anything else asks. This is the default.
  • Strict: pre-approved actions only, for CI.

There is also a --yolo flag that approves everything. Keep it for throwaway sandboxes, never a real project. For dependency work, Ask or Auto is the right place to be, because pip install and uv sync reach out to package indexes and you want to see each one before it runs.

Undo with checkpoints

The CLI snapshots files the agent writes or edits before each change. /rewind in the session, or safeguard checkpoints from the shell, lists those checkpoints and restores one. A dry run shows the diff first:

safeguard checkpoints list --session <id>
safeguard checkpoints restore <checkpoint> --session <id> --dry-run

One important limit from the CLI's own help: anything a shell command writes is not snapshotted. When uv lock or poetry lock rewrites a lockfile, that change is not in a checkpoint. Commit before you start so git can undo it.

CI with safeguard exec

safeguard exec runs one non-interactive assistant turn and exits. Its default permission mode is strict, so nothing is asked and anything not pre-approved is denied, and it refuses to run outside a git repository. A read-only summary step looks like this:

safeguard exec "summarise the critical findings in this repository"

Our safeguard exec guide covers keys and pipeline setup in detail.

Step by step: fix a Python finding end to end

This walkthrough assumes a clean git working tree and a project that already has tests. Each step lists the commands so you can run them yourself or approve them when the assistant proposes them.

1. Start clean

git status
git switch -c fix/python-deps

A clean tree keeps the branch to the fix alone, and a branch lets you throw a bad attempt away.

2. Look at findings locally first

safeguard findings sca --dir .

safeguard findings runs one scanner on your machine and prints a single normalized list: id, severity, title, CVE or rule, package@version and the fixed version. Nothing leaves the machine with this command; no project is registered and nothing is uploaded, even when you are signed in. Add --json if you want to pipe it into another tool.

When you want the results in the platform as well, run a full scan:

safeguard scan --dir .

That builds an SBOM, matches it against vulnerability databases and writes JSON files under ./output, including vuln.json and license.json. Only the results go to the platform. In the platform, reachability analysis helps you decide which findings to fix first.

3. Find out whether the package is direct or transitive

Pick the command for your tool:

# uv: who depends on this package?
uv tree --invert --package example-parser

# Poetry: is it direct, or required by something else?
poetry show --why example-parser

# pip-tools: the compiled file says why each pin is there
grep -n -A2 "example-parser" requirements.txt

Or ask the assistant "what pulls in example-parser here?"

4. Apply the smallest fix for your tool

Plain requirements file. Raise the pin, then reinstall into a fresh virtual environment so old packages do not hide a problem:

python -m venv .venv-fix
. .venv-fix/bin/activate
python -m pip install -r requirements.txt
python -m pip check

On Windows, use py -m venv .venv-fix, then .venv-fix\Scripts\activate. pip check verifies that installed packages have compatible dependencies and prints "No broken requirements found." when they do.

If the vulnerable package is transitive, put the safe floor in a constraints file instead of adding it to your requirements:

# constraints.txt
example-parser>=2.4.1
python -m pip install -r requirements.txt -c constraints.txt

pip-tools. Recompile just that package, then sync:

pip-compile --upgrade-package example-parser
pip-sync requirements.txt

To target an exact version, use -P example-parser==2.4.1.

Poetry. Try a targeted update first:

poetry update example-parser

If the version does not move, your constraint is blocking it. Widen the constraint to the fixed release:

poetry add example-parser@^2.4.1

For a transitive package, check poetry show --tree to see the parent, and upgrade that parent if its newer release allows the fixed version.

uv. Upgrade only the one package and keep everything else locked:

uv lock --upgrade-package example-parser
uv sync

If a parent holds a transitive package below the fix, raise the floor in pyproject.toml:

[tool.uv]
constraint-dependencies = ["example-parser>=2.4.1"]

Then run uv lock and uv sync again.

5. Or let the assistant do steps 3 and 4

Inside safeguard, one prompt covers both:

Find out whether example-parser is direct or transitive, apply the smallest fix
for this project's tool (we use uv), update the lockfile, then run pytest.

With Auto as your permission mode, reads run straight away. Each edit and each command that is not read-only waits for you. Read the diff before you press y. If the assistant proposes override-dependencies or a major version jump, press n and ask it why; a smaller option is often available.

6. Test

pytest -q

Run whatever your project uses, such as tox or python -m unittest. If tests fail, paste the failure back to the assistant or attach the log with @.

7. Rescan and review

safeguard findings sca --dir .
safeguard review

The finding should be gone. safeguard review reviews the working tree changes for correctness and security, which is a useful second look at a lockfile diff that can run to hundreds of lines. Add --base main to review against your main branch instead. Our post on reviewing AI generated code before merge goes deeper on this habit.

8. Commit with a message you understand

git add pyproject.toml uv.lock
git commit -m "Upgrade example-parser to 2.4.1 to resolve high severity finding"

The assistant suggests a commit message at the end of a session. Edit it so it says what changed and why.

Continue from the desktop app or your phone

A CLI session is live. It shows in the web app, the desktop app and the phone app, and other devices can send work to it. That changes how dependency fixes fit into a day: start a fix at your desk, then approve the test run from your phone while you are away from the keyboard.

From the desktop or phone app you can send work to a computer running the CLI. You pick the computer and the folder, and then you approve each command and file edit there. The recorded Android session below shows the same conversations appearing on the phone, "Work on my computer" picking a computer running the CLI and a folder, a command waiting for Allow once, Allow for turn or Deny on the phone, and the answer showing that the tests passed.

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

A practical pattern: ask for the fix at your desk and approve the edit to pyproject.toml, approve uv sync and pytest from your phone with Allow once, then read the diff, run safeguard review and commit back at your desk.

The Android app installs from Google Play, and the iOS app from the App Store. Our guide to Safeguard on Android phones and tablets covers setup. The desktop app runs on Windows, macOS and Linux if you prefer a native window to the terminal.

Troubleshooting

The scan finds no Python packages. Start the CLI in the folder that holds pyproject.toml, requirements.txt or the lockfile, or pass --dir to point at it. In a monorepo, scan each service folder.

The fixed version will not install. Another package caps it. Run uv tree --invert --package example-parser or poetry show --why example-parser to find the cap, then upgrade the parent. If no parent release allows the fix yet, a uv override or a temporary direct pin is a stopgap; write down why and set a reminder to remove it.

poetry update reports nothing to update. Your constraint in pyproject.toml excludes the fixed release. Use poetry add example-parser@^2.4.1 to widen it.

uv sync fails in CI after your fix. The lockfile is out of date with pyproject.toml. Run uv lock locally and commit uv.lock. In CI, uv lock --check or --locked raises an error instead of quietly changing the lockfile, which is what you want.

pip check reports a broken requirement. The upgrade satisfied the scanner but broke a peer. Read which package needs which range, and either upgrade that package too or pick a fixed version inside the allowed range.

Something else is wrong. Run safeguard doctor. It checks the environment, sign in, policy and tools and prints what is wrong in the order that matters, without changing anything. safeguard self-update updates the binary, and safeguard update refreshes vulnerability databases.

Common mistakes

Upgrading in a dirty environment. Your old virtual environment may still hold the vulnerable version, or a package you removed. Reinstall into a fresh environment before you trust a passing test.

Editing requirements.txt when it is generated. If the file starts with a pip-tools header, your next pip-compile will overwrite a hand edit. Change requirements.in or use --upgrade-package.

Forgetting the lockfile. Changing pyproject.toml without updating poetry.lock or uv.lock means CI installs the old version. Commit both files together.

Using eager upgrades for one fix. --upgrade-strategy=eager or a bare poetry update upgrades far more than the finding asks for. That makes the diff hard to review and raises the chance of an unrelated break.

Reaching for overrides first. A uv override or a forced pin can install a version the parent never tested against. Prefer upgrading the parent, then a constraint, then an override.

Trusting checkpoints for lockfiles. Lockfiles written by uv lock, poetry lock or pip-compile are written by a shell command, so CLI checkpoints do not cover them. Commit before you start.

Approving without reading. The prompt shows exactly what will run. Check the package name and version; a typo installs the wrong thing.

Committing scan output. safeguard scan writes JSON under ./output. Add that folder to .gitignore unless you mean to keep it.

FAQ

Can the Safeguard CLI fix Python vulnerabilities or does it only scan?

It does both. It scans on your machine and its assistant can edit files, write code and run commands, each with your approval. In practice it reads the finding, edits your manifest, updates the lockfile with your tool, runs your tests and rescans.

Can I use my existing Claude, ChatGPT or Codex subscription?

Yes. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. See the CLI docs or /model for connecting a model. You can also connect Claude or ChatGPT to the Safeguard MCP server and use Safeguard from the chat app.

Does my Python source code leave my machine?

The CLI scans on your machine and sends only results to the platform. When the local runner runs platform scan jobs, only SBOMs and findings are uploaded, never source. safeguard findings uploads nothing at all.

Does it work with Poetry, uv and pip-tools?

The assistant runs whatever commands your project uses, with your approval, so it works with pip, pip-tools, poetry and uv. Tell it which tool you use in the first prompt so it updates the right lockfile.

How do I fix a transitive Python dependency?

Upgrade the parent that pulls it in if a newer parent allows the fixed version. Otherwise add a floor: a constraints file for pip, constraint-dependencies for uv, or a pin in requirements.in for pip-tools. Use overrides only as a temporary measure.

What happens if the fix breaks something?

Run your tests before you commit. If you need to go back, use git for lockfiles and anything a shell command wrote, and /rewind or safeguard checkpoints restore for files the assistant edited directly.

Can I run this in CI?

Yes. safeguard exec runs one non-interactive turn with strict permissions by default, so nothing unapproved runs. Policies and gates are evaluated in the platform, not by the CLI.

Which permission mode should I use for dependency fixes?

Start with Auto, the default, or Ask if you want to see every edit. Use Plan when you want a proposal before any change. Keep --yolo for throwaway sandboxes only.

Try it yourself

This demo shows the local runner: set it up on your own machine, pick the platform, start it, approve it with its device code, then use the same CLI to scan from the terminal.

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

When you are ready, download the Safeguard CLI and keep the CLI docs open for your first session.

Quick check

Why does a Poetry update of one package sometimes do nothing?

Poetry respects the constraint in pyproject.toml. If the constraint excludes the fixed release, the update is a no-op. Widen it with poetry add example-parser@^2.4.1.

Are lockfile changes covered by Safeguard CLI checkpoints?

No. Checkpoints cover files the assistant writes or edits directly. A lockfile rewritten by uv lock or poetry lock is written by a shell command, so commit before you start and use git to undo it.

Which Safeguard command lists findings without uploading anything?

safeguard findings sca --dir . runs the scanner locally and prints findings with fixed versions. No project is registered and nothing is uploaded.

Sources

Last reviewed: 21 October 2026.

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.