How to stop pip installing a malicious or vulnerable package

It is late and you are in the zone. You tell your AI assistant what you want — “build me a little tool that pulls the photos off my camera and sorts them by date” — and you watch it go. It writes the code, decides it needs a library or two to do the job, and installs them for you. A few lines scroll past in the terminal. You do not read them; why would you? It is working. That is the whole appeal of vibe coding: you describe, it builds, you stay in the flow.

Here is what those few scrolling lines actually did, because nobody stops to explain it. To install a library means your computer reaches out to a huge public repository of code written by strangers, downloads one of those packages, and — the part that surprises most people — very often runs a piece of that code right then and there, automatically, as part of installing it. Not later, when you finally use the library. Right at that moment, with whatever access you happen to have on your machine. And it is almost never just the one library you asked for: modern software is built on other people’s software, so the package you wanted quietly pulls in a handful of its own, and each of those pulls in more, until — without you ever choosing any of it — dozens of packages from dozens of strangers are sitting on your computer.

Almost all of them are fine. But it only takes one. Even a single dependency buried three or four levels deep, in something you have never heard of, is enough — and the popular, trusted library you actually picked can be completely clean while one of its hidden dependencies was quietly taken over a few hours ago. A few hours is all it takes, because the most dangerous package is usually the newest one, the version that went live this morning: the security databases that are supposed to warn you simply have not caught up to it yet. Plenty of tools will happily tell you tomorrow that you installed something nasty — but by tomorrow it has already run. Very few tools check before that borrowed code is allowed to execute, and that gap, between a malicious release going live and the world noticing, is exactly where supply-chain attacks live.

pip-cve-gate is one of the few that closes it — and the rest of this page walks through how it works, and how to put it in front of your own installs.

The problem, in plain terms

A pip install is not passive. Many packages run their own code during installation, so a malicious package does not wait for you to import it — it executes the instant it lands. That is why a scan that runs after the install, like pip-audit or safety, can be too late: the code is already on disk, and may already have run.

The most dangerous packages are also the newest. In the May 2026 TrapDoor campaign, every malicious PyPI package was under 72 hours old when it was reported — far too fresh for any CVE database to have indexed it. A scanner that only knows about published advisories never sees the attack that matters most.

Who runs into this

Anyone who installs Python packages, which is to say almost everyone who writes Python — the beginner pasting a pip install line from a tutorial, the solo developer pulling in one more dependency, the privacy-conscious engineer who does not want a cloud service reading their dependency list. None of them can eyeball a package’s whole tree for a four-hour-old compromise. A gate can.

What pip-cve-gate does — and how it works

pip-cve-gate ships one command, safe-pip — a drop-in replacement for pip install. It resolves the full dependency tree, checks every package against three independent signals, and only hands off to real pip when everything is clean.

How a pre-install CVE gate blocks a malicious packageAn install command is intercepted by a gate that checks the whole dependency tree and a freshness hold before any code runs, so a malicious package is blocked before installation.pip installruns code on installCVE gatechecks the tree+ freshness holdBlockedbefore any code runs
The check moves before the install: a malicious or too-fresh package never reaches your machine.

It checks the whole tree, not just the name you typed

The package you install is rarely the whole story — it pulls in dependencies, which pull in more. pip-cve-gate resolves that entire tree first, so a compromise buried three levels deep is checked just like the package you asked for.

Known vulnerabilities, via osv-scanner

CVE lookup is a commodity, so pip-cve-gate does not reinvent it. It delegates to Google’s osv-scanner — the engine behind the OSV database — scanning exactly the versions it resolved. Borrow the engine, own the policy: pip-cve-gate keeps the parts that matter (resolution, freshness, the malware feed, and the fail-closed rule below).

A freshness hold for brand-new releases

By default, any release less than three days old is held back. This is the defence pip-audit cannot offer: it blocks the zero-hour window before advisories exist. TrapDoor would have been stopped on freshness alone — no CVE required. Need a fresh version on purpose? --skip-fresh-hold lets it through.

A malware feed, and a fail-closed rule

It also checks the OSSF malicious-packages list — packages already known to be hostile, not merely vulnerable. And if any check cannot produce a verdict — osv-scanner missing, a feed unreachable — pip-cve-gate refuses the install rather than guessing. “Can’t verify” means “don’t allow.” When you genuinely need to proceed, --allow-unknown is the deliberate opt-out.

Install & use it

Install

pip install pip-cve-gate

Or with Homebrew (macOS / Linux), which also installs the engine for you:

brew install sharkyger/tap/pip-cve-gate

Engine requirement (v0.3.0+): the CVE check is powered by the external osv-scanner binary. The Homebrew formula installs it automatically; with pip install add it yourself (brew install osv-scanner or your platform’s equivalent). If it is missing, the gate fails closed — pass --allow-unknown to install anyway.

Use it

safe-pip works exactly where pip install does:

safe-pip install flask
safe-pip install "django>=4.2" "celery==5.3.6"
safe-pip install -r requirements.txt

A clean run delegates straight to pip; a risky one stops with the reason:

[pip-cve-gate] BLOCKED — install aborted
  [CVE] 'somelib==1.2.3' has known vulnerabilities: GHSA-xxxx-yyyy-zzzz
  [FRESH_HOLD] 'dep==0.0.1' was published 1d ago (hold: 3d). Use --skip-fresh-hold to override.

Full configuration, supported platforms, and source are on GitHub: github.com/sharkyger/pip-cve-gate (MIT).

FAQ

Are Python packages safe to install?

Most are — but PyPI has no mandatory vetting and anyone can publish. The real risk is the small number of malicious or compromised packages, especially brand-new ones no security database has flagged yet. pip-cve-gate exists to catch those before they install.

How do I check if a pip package is malicious before installing it?

Run the install through safe-pip instead of pip. It resolves the full dependency tree and checks every package against the OSV vulnerability database and the OSSF malicious-packages feed, and holds back releases too new to have been vetted — blocking the install before any code runs if anything looks wrong.

How is pip-cve-gate different from pip-audit?

pip-audit runs after pip has already downloaded and possibly executed a package. pip-cve-gate runs before the install, across the whole dependency tree, and adds a freshness hold and a malware feed — so it can stop a zero-hour attack that no advisory has indexed yet.


pip-cve-gate is one of the 5bats supply-chain gates — the same pre-install check exists for Composer, Homebrew, and the packages an AI assistant installs.

See the source, configuration and install guide on GitHub →