Check pyproject.toml dependencies for vulnerabilities

Paste your pyproject.toml, or drop it with uv.lock or poetry.lock for exact versions. PEP 621, Poetry and dependency groups are read, and every finding says whether a fresh install already gets the fix.
Add uv.lock or poetry.lock for exact versions.

Checked against OSV.dev, CISA KEV, FIRST EPSS, npm, PyPI, deps.dev, endoflife.date and Docker Hub, live.

Sample: A FastAPI service’s pyproject.toml

A FastAPI service with ranges, one exact pin, a dev dependency group and a .python-version file. Compare fastapi’s finding, whose range already allows the fix, with aiohttp’s, whose ~= range keeps it on a vulnerable line.

pyproject.toml
[project]
name = "acme-service"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
    "fastapi>=0.65.0",
    "starlette==0.27.0",
    "pydantic>=1.8,<2",
    "python-multipart==0.0.6",
    "aiohttp~=3.8.1",
    "sqlalchemy>=1.4,<2.0",
]

[dependency-groups]
dev = [
    "pytest>=7.0",
    "black==22.3.0",
]
.python-version
3.9

9 findings: 4 critical and 5 low priority

8 packages from pyproject.toml, .python-version

  • Critical: 7 known vulnerabilities in starlette 0.27.0

    pyproject.toml1.3.1CVE-2026-48710 exploited in the wild (CISA)

  • Critical: 43 known vulnerabilities in aiohttp 3.8.1

    pyproject.toml3.14.3

  • Critical: 9 known vulnerabilities in python-multipart 0.0.6

    pyproject.toml0.0.31

  • Critical: Python 3.9 is end-of-life

    .python-version3.15.0

  • Low: 2 known vulnerabilities in fastapi 0.65.0

    pyproject.toml0.109.1Your range already allows the fix

  • Low: 3 known vulnerabilities in black 22.3.0

    pyproject.toml26.3.1Dev only

And 3 more findings in the full result.

Last checked against GitHub, OSV.dev, deps.dev, CISA KEV, FIRST EPSS, PyPI and endoflife.date.

What it reads in pyproject.toml

  • [project] dependencies and [project.optional-dependencies] (PEP 621), including uv’s upper-bound-first style such as fastapi<1.0,>=0.110.
  • [dependency-groups] (PEP 735) as development dependencies.
  • Poetry’s [tool.poetry.dependencies], dev and group tables, with Poetry’s ^ and ~ ranges.
  • uv.lock or poetry.lock in the same folder or a parent folder, which pin every listed package to the installed version.

Ranges, and when a fresh install is already fixed

pyproject.toml declares what your project accepts, not what got installed. Without a lockfile we check the lowest version each range allows, because that is the oldest version a resolver may pick. Then we compare the range with the fix:

  • If the range already allows the fixed version (fastapi>=0.65.0 allows 0.109.1), the finding drops to low priority: a fresh install or a lock update gets the fix.
  • If the fix is outside the range (aiohttp~=3.8.1 stops before 3.9), the finding stays serious and tells you to change the range.
  • Advisories CISA lists as exploited stay critical either way.

With uv.lock or poetry.lock, none of this guessing is needed: we check the locked version.

How it compares with pip-audit

  • pip-audit

    A project directory with pyproject.toml, a requirements file or an environment. It resolves dependencies itself, much like pip install does, so transitive packages are covered.

    Use it when: You can run it in the project or in CI.

  • This check

    The pasted pyproject.toml and lockfile, without resolving or installing. Says which findings your ranges already fix, ranks by CISA KEV and EPSS, and checks your Python version for end of life.

    Use it when: You want a quick, ranked answer before touching the environment, or a task for your coding agent.

Questions

Does it use requires-python?

No. requires-python = ">=3.9" is the oldest Python your project supports, not the one it runs on. Add .python-version (uv writes one) and that Python line is checked for end of life.

Are dependency groups checked?

Yes. PEP 735 [dependency-groups] and Poetry’s dev and group tables count as development dependencies, so their findings rank one level lower unless CISA lists the advisory as exploited. Optional dependencies count as runtime ones.

What does ~= mean for the result?

aiohttp~=3.8.1 allows 3.8.1 and later 3.8.x releases, but not 3.9. When every fix is in a release your range excludes, the finding stays serious and says the range itself has to change.

How do I apply the fixes?

Raise the lower bound in pyproject.toml where the finding says the fix is outside your range, then update the lock: for example uv lock --upgrade-package aiohttp or poetry update aiohttp. Or copy the fix task into your coding agent.

Is my pyproject.toml stored?

No. It’s read in memory to run the check, and only package names and versions go to the public sources.

Go further than one check

Map your repo into an architecture diagram with live health on every part, get an alert when a new critical finding lands, and hand your coding agent a brief to build from. Free to start.