Plugins

Known vulnerabilities

Installed plugins' packages compared with known advisories from your indexes, a file you import or a feed you turn on, with the version that fixes each one. Nothing is sent anywhere to do it.

For: tenant administrators and plugin authors.

Inspect vulnerability findings for an installed plugin version to see which of its packages match known advisories. The edge compares the version's SBOM, including lockfile packages and package closures pinned by hash, with those advisories.

Each finding identifies the affected package, advisory, severity and fixed version.

The comparison happens inside your cluster. No package list leaves it: advisories come only from places you already trust or chose.

See the findings

Terminal
oz0 plugin vulns
Output
PLUGIN  VERSION  SEVERITY  PACKAGE          ADVISORY                     FIXED IN  SOURCE
team    0.3.0    high      PyPI mcp 2.2.0   OZ0-E2E-0001 (CVE-2099-0001)  2.2.1     index:e2e

OZ0-E2E-0001: A test advisory against the locked mcp

Advisories: index:e2e 1.
  • oz0 plugin vulns <name> shows one plugin; --json gives everything, references included.
  • oz0 plugin list has a VULNS column (how many findings, and the worst severity; - until the first scan) and a line under the table for each affected plugin.
  • On the Plugins page, an affected plugin's row has a badge, and its panel lists each finding under its SBOM, with fixed in the version to move to, or no fixed version yet.
  • oz0 doctor warns while an installed version has a high or critical finding, and when an advisory source failed.

The tenant's readers may see their tenant's findings: they name packages and advisories, never a person or what a job did.

To fix a finding, update the plugin to a version that locks the fixed package: its publisher releases one, or, for your own plugin, run uv lock --upgrade-package <package> (or npm update <package>) and install it again.

Where advisories come from

SourceHow it gets inWhen it changes
A trusted index's advisoriesSigned with the index (its format), read from the copy the cluster already keepsWhen an admin runs oz0 index refresh or an install reads a newer copy
An operator's fileoz0 index advisories import --file FILE|DIRWhen you import again or oz0 index advisories remove
A feed you turn onOZ0_ADVISORY_FEED=URL, or server start --advisory-feed-file FILEAt the edge's start and every --advisory-feed-every (a day)

When two sources name the same vulnerability (by an ID or an alias they share) for the same package, the import wins over the feed, and the feed over the indexes. An entry of the import or the feed with withdrawn set withdraws the vulnerability from the sources after it, so you can correct what an index says without editing a signed file. The indexes count side by side: no index can hide, narrow or withdraw an advisory another index publishes, whatever their order in oz0 index list.

When two indexes both report one vulnerability, the finding shows the more cautious of their words (the higher severity, then no fixed version or the higher one). oz0 index advisories list shows each source, how many advisories it gave and skipped, and its last error. It shows who imported a file only to cluster admins; everyone else reads operator there, and the audit log's advisories.import entry records who it was.

Nothing refreshes an index for its advisories on a timer: the cluster sees new ones when the index is refreshed. To keep them current, refresh on a schedule of your own, such as a daily oz0 index refresh from cron.

An operator's file

The air-gapped path. --file takes:

  • the hub's format (oz0-hub.advisories.v1, below),
  • an OSV entry or a JSON array of them,
  • OSV's bulk zip of an ecosystem (PyPI/all.zip, npm/all.zip),
  • or a folder of OSV .json files.

At most 32 MB. PyPI and npm entries are kept; others are counted as skipped, and so is an entry that does not read or check (a bad version, a summary with a terminal escape): the import names the first five and keeps the rest, so one stray entry in an OSV zip does not cost the others.

A single OSV entry that does not check, or a file none of whose entries reads, is refused. The feed follows the same rules. An import replaces the previous one.

An offline install's folder carries advisories/advisories.json for this; the server never imports it by itself:

Terminal
oz0 index advisories import --file /mnt/offline/advisories/advisories.json
Output
Imported 1 advisories from advisories.json (oz0-hub.advisories.v1). sha256:9c1f…
The edge scans the installed plugins against them within a minute: oz0 plugin vulns

Cluster admins import and remove; anyone signed in lists the sources.

The opt-in feed

Off by default. Name an HTTPS URL of OSV entries, OSV's zip or the hub's format; the edge that holds the scanner's lease fetches the whole document (never a query naming your packages), with TLS verified, no redirect followed, at most 256 MB:

Terminal
export OZ0_ADVISORY_FEED='https://storage.googleapis.com/osv-vulnerabilities/PyPI/all.zip'
oz0 server start ...

Or put the URL in a file readable only by the server's user and name it with --advisory-feed-file FILE (- reads standard input). The URL is never a flag's value: it can carry a token, and a command line is readable by every user of the machine. Logs and index advisories list show the URL without its query. Plain HTTP is allowed only to a loopback address, such as a mirror on the same machine. A failed fetch keeps the last advisories and is tried again within the hour.

The edge that holds the scan fetches it when it takes over the scan (at its start, so a changed URL is fetched after the restart) and then every --advisory-feed-every. Unset it and the feed's advisories are dropped at the next scan. It is listed on Outbound connections.

How packages are matched

  • PyPI and npm packages, from each SBOM component's package URL. Names compare as PyPI normalises them (Typing_Extensions is typing-extensions) and npm's as written.
  • A version is affected when an advisory lists it exactly, or it is at or after a range's introduced (0 for every version) and before its fixed, or at or before its last_affected.
  • Versions compare by their dotted parts, numbers as numbers, as the hub's own checks do, so the hub and the cluster agree. Pre-releases and PEP 440's epochs and local versions compare only approximately.
  • The fixed version is the lowest fixed above the installed version among the ranges that cover it.
  • Severity is the advisory's (low, moderate, high, critical): the hub's, OSV's database_specific.severity, or a CVSS 3 vector's base score; unknown otherwise.

The edge scans a version within a minute of its install, when any source changes, and again a day after its last scan. A rescan replaces the version's findings, so an advisory gone from every source clears its finding.

Installs

Installing or updating to a version whose packages have a known critical advisory answers with a warning naming the package, the advisory and the fixed version. A tenant may refuse such installs instead:

Terminal
oz0 tenant vulnerable-installs acme --refuse critical --reason 'security policy 4.2'
oz0 tenant vulnerable-installs acme --refuse high --reason 'stricter after the audit'
oz0 tenant vulnerable-installs acme --refuse off --reason 'back to warnings'

It is a governance setting (vulnerable_installs): every change takes a reason and is audited (tenant.vulnerable_installs), and refusing less (off, or critical after high) waits for another admin when four eyes is on. The default warns only, so nothing that installed before is refused.

Who is told

A finding new to an installed version emits plugin.vulnerable: by email to the owner persons of the plugin's agents, flows and tools (as the inventory names them) and to the tenant's admins, once the operator has configured email, and to every notification rule that names the event. It names the plugin, its version, the worst severity, the advisories and the fixes; nothing about people.

Copyright © 2026