Skip to main content

package-doctor

CI PyPI Python License: MIT

Tells you which Python dependencies to fix first: the ones being exploited, and the ones nobody is left to patch. Then stops your coding agent from adding another.

package-doctor scan of a ten-dependency project: pillow and litellm under FIX TODAY for CVEs on CISA's known-exploited list; bleach to replace, archived and marked Inactive; nltk to mitigate, with one advisory no release fixes; requests, pyjwt, flask and jinja2 to upgrade; python-dateutil quiet. Seven of ten fail the build.

A demo project with deliberately old pins, scanned on 16 September 2026. Advisory and exploitation data change daily, so the same pins will not read the same later.

In late August 2026, Anthropic's coordinated disclosure programme reported 2,300 vulnerabilities across 392 open source projects. 421 had been patched upstream. Discovery is becoming automated; remediation still needs a human. So the question worth asking about a dependency is not "is it healthy?" It is:

If a vulnerability lands in this package tomorrow, am I exposed, and is anyone home to fix it?

What it does

  • Two axes, not one. A package is escalated only when it sits at a trust boundary - it parses, decodes or authenticates data an attacker can influence - and there is proof nobody is left to ship a fix. mock going quiet is not a finding; an archived auth library is. The boundary call comes from a human-reviewed map of about 1,500 packages, each with its reason.
  • Exploited first. Advisories that affect your pinned version are ranked by CISA's known-exploited list and FIRST EPSS, so hundreds of advisories become the handful worth reading today.
  • Reachability. Each finding says whether, and where, your own code imports the package.
  • A guardrail for coding agents. As a Claude Code, Codex or Gemini CLI hook it checks every install an agent proposes, and blocks invented names, packages published in the last 30 days, and vulnerable or abandoned libraries at a trust boundary - with the reason, so the agent picks something else.

Findings are grouped by what to do about them:

Section Means Fails the build by default
exploited a CVE on CISA's known-exploited list affects your version yes
replace proof nobody is home - archived or marked Inactive - or an unfixable advisory in a project that has gone quiet at a boundary
upgrade advisories affect your version, and a newer release is clear of them at a boundary
mitigate an advisory with no fix anywhere, in a project that is still active no
quiet gone quiet, nothing actually wrong no
unchecked not enough data to judge no

Install

pip install package-doctor

Quick start

package-doctor scan                            # everything your project depends on
package-doctor explain pillow                  # the evidence behind one row
package-doctor check requests pillow==10.0.0   # before adding a dependency

It reads uv.lock, poetry.lock, Pipfile.lock, pyproject.toml, Pipfile, setup.cfg, setup.py and requirements*.txt. Without a lockfile it assumes the newest release a fresh install would get, and marks that version ?.

In CI, one line scans the checkout, fails the job on what needs work at a trust boundary, and writes the report to the job summary:

- uses: binuka200/package-doctor@v1.0.0

It also runs as a pre-commit hook, writes SARIF for code scanning, and lets you accept a known risk on the record, with a reason and an expiry date.

As a Claude Code hook, add this to .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "package-doctor hook claude-code", "timeout": 60 }] }
    ],
    "PostToolUse": [
      { "matcher": "Bash|Edit|Write|MultiEdit",
        "hooks": [{ "type": "command", "command": "package-doctor hook claude-code", "timeout": 60 }] }
    ]
  }
}

PreToolUse checks what an install names before it runs. PostToolUse checks what it pulled in with it, and names written straight into a dependency file. For Gemini CLI and Codex, use package-doctor hook gemini or package-doctor hook codex; the settings are in the guardrail docs.

How accurate is it?

Measured on 100 open source repositories and 18,138 packages, on 23 September 2026:

  • 8,260 of 8,261 pinned versions match OSV's own answer about which advisories affect them.
  • 0 vulnerabilities that pip-audit found and package-doctor missed, over 1,663 found by both (on the first 60 repositories).
  • 116,317 reported import sites checked against the source line: 116,283 match outright, and the other 34 are modules their distribution ships under another name, such as _pytest.
  • Every verdict's claim checked at its source: all 14 exploited CVEs are on CISA's list and no affected pin escaped; all 554 upgrade targets clear their advisories; 87 of 88 archived claims confirmed by GitHub.
  • 2,550 advisories affecting pinned versions, of which 34 are on CISA's list or above a 10% exploit probability.

The exposure map carries real signal too: among entries decided from what a package does, the ones marked exposed have security advisories 8.6× as often as the ones reviewed and cleared. The method, and its limits, are in accuracy.

Use it alongside pip-audit, not instead of it

pip-audit is the PyPA tool and is better at what it does: telling you, on every commit, which pinned versions have known CVEs. Most of what lands in fix today, upgrade and mitigate here, it would also find.

What it does not do is tell you which of those to fix first, which of them your code actually imports, or which of your dependencies has nobody left to ship a patch at all. That is this tool's job, and it is a different cadence — a quarterly maintenance review rather than a per-commit gate.

Documentation

Contributing

The most useful contribution isn't code — it's arguing with exposure.toml, about 1,500 judgement calls about which packages sit where an attacker can reach, each with a one-sentence reason. A verdict you think is wrong is worth the same: post it in Discussions with the output of package-doctor explain. See CONTRIBUTING.md to get started.

A note on tone

Being listed here is not an accusation. Most unmaintained packages are the work of volunteers who gave what they could, and "no releases since 2021, repository archived" is a fact that helps a user without indicting anyone. Findings are worded that way on purpose. If you find output that reads as a judgement on a maintainer rather than a description of risk, that is a bug — please report it.

Background

The reasoning behind this tool is set out in Rethinking Dependency Maintenance in the Age of AI Vulnerability Research.

License

MIT

Release files for package-doctor 1.0.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for package-doctor 1.0.5
File Size Uploaded
package_doctor-1.0.5.tar.gz 306.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for package-doctor 1.0.5
File Interpreter ABI Platform
package_doctor-1.0.5-py3-none-any.whl Python 3 none any Details

Total release size: 479.7 kB

Release files / package_doctor-1.0.5.tar.gz

Download URL package_doctor-1.0.5.tar.gz
Size 306.5 kB
Tags Source
SHA-256 checksum
How to use checksums
5ee12270476ae4b6542b984f8e5ba0c48511c8ff46a6aaea1a9d2757c1307591
BLAKE2b-256 checksum
How to use checksums
b6c77300708dd588ffad2a86322b972bb9017e7b6314562acfba849889cb1514
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / package_doctor-1.0.5-py3-none-any.whl

Download URL package_doctor-1.0.5-py3-none-any.whl
Size 173.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d473a5b086f6801d3311ce082007b7a2974971f8e4ac480a11d28b2e6840724d
BLAKE2b-256 checksum
How to use checksums
7c802e2ce8b2cd3ecc25cf6c92e713fc5f24d6d50bd16a11203dc729f4adf8bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.5 This release

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page