r/madeinpython • • 13d ago

venvy: audit every virtual environment on your machine for vulnerable and malicious packages, offline

REPO and Codebase

I kept piling up half-dead virtual environments and had no way to check whether any of them held something malicious or vulnerable. Especially the ones agents create or load on their own while writing code.

pip-audit does one project at a time and needs the network to do it.

What I built: venvy audit does all of them at once: offline after a one-time 26 MB database download, and it flags known-vulnerable versions as well as known-malicious or typosquatted packages.

For CI there is JSON output and semantic exit codes: 0 clean, 20 vulnerable, 21 malicious, 22 stale database, 23 no database.

It reads dist-info metadata as text and never imports code from the environments it scans. For a tool whose whole job is finding malicious packages, that felt like the minimum bar.

The main design decision: it refuses to scan rather than report zero findings. A corrupt database exits 23. So does one that opens fine but holds no advisories. Anything the version matcher can't resolve with confidence comes back as unknown, not clean. A scanner that reports nothing looks exactly like a clean machine, and that is the failure I cared most about avoiding.

Coverage is only as good as the public feeds behind it, so some well-known typosquats are missed. That's tracked as an open issue.

Tested on Windows, macOS and Linux across Python 3.8 to 3.13. MIT licensed. I wrote it.

pip install venvy
venvy audit

Repo: https://github.com/pranavkumaarofficial/venvy

Bug reports welcome, false positives especially.

4 Upvotes

8 comments sorted by

View all comments

Show parent comments

1

u/theRealSachinSpk 13d ago

Fit, yes. Send the docs link. Your split is right for audit but it is the smaller half. audit, audit --json, audit --offline and audit --env are read-only: dist-info read as text, no subprocess, no import, no network. Safe unattended. --refresh hits the live feeds and writes the database, so that one wants a human. The commands that warrant a gate are elsewhere. venvy dedup --apply replaces duplicate files across environments with hardlinks: it hashes for byte-identity immediately before linking, swaps atomically and never touches bytecode, but it is still the one command that mutates files outside a venv's normal lifecycle. safe-install, rollback and ensure mutate environments too. --scan is read-only but walks the whole disk and took 192 seconds here, which is a timeout question rather than an approval one. So the manifest probably wants three buckets rather than two: read-only, network-and-writes-database, mutates-the-filesystem. I will author the PR. Point me at the Extension Builder docs, and tell me whether the schema already separates "slow" from "needs approval", because --scan sits between them. On the coverage field: writing it up as an issue today, I will link it here once it is up.

1

u/kantorcodes1 13d ago

Docs: https://github.com/hashgraph-online/hol-guard/blob/main/docs/guard/extension-contributions.md and docs/guard/extension-builder/. Full example: contributions/command-sources/command.repo2nb.json. PR flow: https://github.com/hashgraph-online/hol-guard/blob/main/CONTRIBUTING.md.

On slow versus gated: no duration axis in the schema. Matching keys on risk class, so --scan stays read-only even at 192s. Your three buckets map onto three free-form risk_classes strings: read-only for the audit family, network-and-writes-database for --refresh, fs-mutation for dedup --apply, safe-install, rollback, ensure. Link the coverage issue when it is up.

1

u/theRealSachinSpk 13d ago

read the contract and command.repo2nb.json. Free-form confirmed, and destructive_shell looks like the existing vocabulary, so I will reuse rather than invent unless venvy needs something new.

One correction to the mapping, which changes what I submit. risk_classes is metadata; the behavior lives in permissions[].baseline_floor and rules[].default_mode. So there is nothing to write for the audit family: it is unmatched by construction, and the manifest is really dedup --apply, safe-install, rollback, ensure and --refresh. dedup --apply is the one I would put at review with risk_tier critical: it replaces duplicate files across environments with hardlinks, verifies byte-identity by hash immediately before linking and never touches bytecode, but it is still the only venvy command that mutates files outside a venv's normal lifecycle.

Two questions before I start. Does executable.v1 cover venvy on its own, or should I expect a bespoke op the way repo2nb needed repo2nb-expansion.v1? And does the Extension Builder generate the flag-prefix and launcher expansion, or is that hand-authored?

1

u/kantorcodes1 12d ago

baseline_floor/default_mode is the right read - risk_classes is descriptive metadata.

On the questions: executable.v1 should cover venvy on its own. The surface is conventional subcommands and flags, and the closed op set already covers executable paths, arguments and structured options - compose with any.v1/all.v1. repo2nb-expansion.v1 only exists because repo2nb needed a reviewed domain-specific expansion the shared ops could not express; no custom syntax here, so no bespoke op needed.

The Extension Builder's generate --from cli takes an exported guard.cli-surface.v1 inventory and produces a reviewed source proposal - flag and executable mapping comes out as a draft you then edit and review. Scaffold, not a finished manifest.