Proven
Guides

OSV package table

Proven defines this table. One row is one affected range of one advisory. The file is CSV with a header. A real OSV dump is not loaded here; an administrator puts the file in Zetaris's storage.

Columns

columnmeaning
idthe advisory id
aliasesother ids for the same advisory, separated by spaces, sorted
ecosystemthe package ecosystem: Go, npm, PyPI, Packagist, crates.io or Maven
packagethe module path, or the package name in another ecosystem
introducedthe first affected version; empty or 0 means there is no lower bound
fixedthe first version that is not affected; the bound is exclusive
last_affectedthe last affected version; the bound is inclusive; used only when fixed is empty
severitya short severity when the advisory has one, otherwise the first score string
publishedthe advisory's published time, as the source wrote it

cmd/osv-to-csv writes this header. It reads a directory of OSV JSON files, one advisory per file, and does not walk subdirectories. A file that does not end in .json is skipped. A versions list becomes one row per version, with introduced and last_affected set to that version, when the entry has no SEMVER or ECOSYSTEM range. A GIT range names commits, not versions, and is not a row.

go run ./cmd/osv-to-csv <advisory-dir> <out.csv>

<out.csv> may be - to write to stdout.

The name Proven queries

The setting is the environment variable PROVEN_OSV_TABLE. The default is px_osvfile.px_osv_pkg. An empty value means the default. Any other value must be a table name or schema.table, made of letters, digits and underscores. Sample mode uses the default and does not read the variable.

For each package the change adds, or whose required version goes up, Proven runs one query. The ecosystem is Go, npm, PyPI, Packagist, crates.io or Maven.

SELECT id, aliases, ecosystem, package, introduced, fixed, last_affected, severity, published
FROM px_osvfile.px_osv_pkg
WHERE ecosystem = '<ecosystem>' AND package = '<package>'

The version comparison is done in Go, not in SQL. A version is inside a range when introduced ≤ version < fixed. When fixed is empty, the test is introduced ≤ version ≤ last_affected. When both ends are empty, every version the comparer accepts at or after introduced matches. A matching row becomes a finding. The evidence is that query, the rows it returned, and the comparison. No matching row is not a finding; the trace says which comparison excluded the version, or that the query returned no rows. An alias that is a CVE is also looked up in px_evidence.px_kev_vulns, the same query the review already uses for a CVE. A missing KEV row does not drop the finding.

Go and npm use semantic versions: MAJOR.MINOR.PATCH, an optional leading v, and an optional pre-release. Build metadata is ignored. A pre-release is lower than the release it precedes. A string that is not a semantic version, including an npm range such as ^1.2.3, gives no finding. The trace says it is not a semantic version. The check does not rewrite the string.

PyPI uses PEP 440 for a release (1.2.3, any number of numeric segments), a pre-release (1.0a1, 1.0b1, 1.0rc1, and the spellings alpha, beta, rc, pre, preview, c) and a post-release (1.0.post1 and 1.0-1). 1.0 and 1.0.0 are the same version. A pre-release is lower than its release. A post-release is higher than its release. An epoch (1!1.0), a dev release (1.0.dev1), a local version (1.0+local) or any other string gives no finding. The trace says which of those it is. The check does not guess a value for it.

A replace or an exclude in go.mod is not read. Only require lines are.

package.json contributes dependencies and devDependencies. Other sections are not read. A dependency whose value is not a string is skipped, and the trace names it.

requirements.txt contributes pinned name==version lines. The name may carry extras in brackets; the extras are not part of the package name. A blank line or a comment is ignored. Any other line is skipped, and the trace names the line.

go.sum contributes module versions. A line is a module path, a version and a hash. A line whose version ends in /go.mod is the checksum of that module's go.mod file, not a second module, and is ignored. One module may be pinned at more than one version. Each version the change adds, and each version that is higher than every version of that module on the base, is checked. A line that is not a module, a version and an h1: hash means the file was not parsed: no finding from that file, and the trace names the line.

package-lock.json contributes the packages map when lockfileVersion is 2 or 3. The ecosystem is npm. The root entry and any path that does not sit under node_modules/ are not dependencies. The package name is the entry's name when it has one, otherwise the path after the last node_modules/. An entry with no version string is not a pin. A file this reader cannot parse gives no finding, and the trace says why.

poetry.lock contributes each [[package]] table's name and version. The ecosystem is PyPI. The version is the one written before the next table header. Lines in any other table, including a multi-line array, are not pins. A file this reader cannot parse gives no finding, and the trace says why.

composer.json contributes require and require-dev. Other keys are not read. A requirement whose value is not a string is skipped, and the trace names it. A platform package (php, hhvm, ext-*, lib-*, php-*, composer-plugin-api, composer-runtime-api) is not a Packagist package: the trace names it and it is not checked. The package name is stored in lower case.

composer.lock contributes each object in packages and packages-dev, its name and its version. The ecosystem is Packagist. Other keys, including platform and content-hash, are not pins. An object with no version string is not a pin. A file this reader cannot parse gives no finding, and the trace says why.

Cargo.toml contributes [dependencies] and [dev-dependencies]. Other tables, including [build-dependencies], [workspace.dependencies] and a target-specific table, are not read. A dependency written as a string is that version. An inline table contributes its version, and its package name when it renames the crate; otherwise the key is the name. A dotted table such as [dependencies.tokio] is the same crate. One assignment is one line, and an inline table is one line. A dependency with no version is named in the trace and not checked. A value that is not a string is named in the trace and not checked. The package name is stored as written.

Cargo.lock contributes each [[package]] table's name and version. The ecosystem is crates.io. Lines in any other table are not pins. The package being built is a [[package]] table too; an unchanged version is not a finding. A file this reader cannot parse gives no finding, and the trace says why.

libs.versions.toml contributes [libraries]. The ecosystem is Maven. A version.ref is the string named in [versions]. A ref that is not there, a value that is not a string, and a rich version (strictly, prefer, require) are named in the trace and not checked. [plugins] is not a Maven coordinate: each plugin is named in the trace and not checked. [bundles] is not read. A table other than versions, libraries, bundles and plugins means the file was not parsed.

build.gradle.kts contributes a double-quoted group:artifact:version, and one line that names group, name and version as literal strings. The ecosystem is Maven. A version that contains $, or a version written as a name, is a variable: the trace names it and it is not checked. A plugin id and a libs. catalog reference are named in the trace and not checked. Groovy build.gradle is not this file.

gradle.lockfile contributes each group:artifact:version=configurations line. The ecosystem is Maven. A line empty= names configurations that locked nothing and is not a package. A comment is ignored. A non-empty file with no pin and no empty= line means the file was not parsed: no finding from that file, and the trace says why.

crates.io compares semantic versions: MAJOR.MINOR.PATCH, an optional pre-release, and build metadata that is ignored, so 1.2.3+build and 1.2.3 are the same version. A pre-release is lower than the release it precedes. A leading v is refused. A constraint (^, ~, >, <, =, *, a comma, ||, a space) and a version that is not three numeric components give no finding. The trace says which. The check does not rewrite the string. The subset is the one internal/cargover implements.

Packagist compares a subset of Composer's version order. One to four numeric components, with a missing component equal to zero, so 1.2 and 1.2.0.0 are the same version. A leading v is ignored. Text after + is ignored. Stability, from lowest to highest, is dev, alpha (a), beta (b), RC (rc), stable, then patch (p, pl, patch). The spelling is not case-sensitive. A suffix with no number is below the same suffix numbered 0. An explicit -stable is the same as no suffix. A constraint (^, ~, >, <, =, *, a comma, ||, a space) and a branch name (dev-*, or a component x, including 1.0.x-dev) give no finding. The trace says which. The check does not rewrite the string. The subset is the one internal/composerver implements; ADAPTERS.md is the worked example.

Maven compares a subset of Maven's version order. Numeric tokens compare as integers, and a missing numeric token equals zero, so 1, 1.0 and 1.0.0 are the same version. A hyphen between numbers is the same separator as a dot in this subset, so 1.0-1 and 1.0.1 are the same version. Qualifiers, from lowest to highest, are alpha, beta, milestone, rc, snapshot, release, then sp. The spelling is not case-sensitive. a, b, m and cr are the short names. ga, final and release are the release itself, so 1.0-ga and 1.0 are the same version. A qualifier below release is below the same numbers with no qualifier. sp is above that release. A number on the qualifier orders that qualifier, and a missing number equals zero. A qualifier that is not in that list is above sp, then compared as lowercase text. That placement was not checked against one named Maven release. A constraint (including [1.0,2.0) and a Gradle dynamic version such as 1.2.+) and a leading v give no finding. The trace says which. The check does not rewrite the string. The subset is the one internal/mavenver implements; ADAPTERS.md is the worked example.

A package that both a manifest and its lock file add or raise is checked once. The lock file's version is the one compared, and the finding names the lock file. The pairs are go.mod with go.sum, package.json with package-lock.json, requirements.txt with poetry.lock, composer.json with composer.lock, Cargo.toml with Cargo.lock, and build.gradle.kts or libs.versions.toml with gradle.lockfile, each pair in the same directory. A catalog under gradle/ is not paired with a lock file at the project root. PyPI names are compared and queried after PEP 503 normalisation: lower case, and every run of -, _ or . becomes one -. Packagist names are compared and queried in lower case, and the separators are kept. Go module paths, npm names, crate names and Maven coordinates do not change. A package that only one of the two files names is still checked.

What the administrator runs

Measured on 22 October 2026: a Lightning filestore table over a CSV with a header answers SELECT for every row, with Proven's own key, in about 0.10–0.15 seconds. The statements below are those measurements, with this table's name and file. The storage path is filled in by the administrator.

CREATE LIGHTNING DATABASE PX_OSVFILE DESCRIBE BY "exploration";
CREATE LIGHTNING FILESTORE TABLE px_osv_pkg FROM PX_OSVFILE FORMAT CSV
  OPTIONS (PATH "<customer-data>/shared/px_osv_pkg/px_osv_pkg.csv", header "true", inferSchema "true");
ASSIGN LIGHTNING DATABASE PX_OSVFILE TO USER '<proven>';
GRANT SELECT ON LIGHTNING DATABASE NAMESPACE PX_OSVFILE TO USER '<proven>';

Before the grant, the same query was refused.

JSON Lines is not this file's format. A JSON Lines probe returned only the first record. Use CSV, or a JSON array.

How to refresh it

  1. Build a new CSV with osv-to-csv.
  2. Put that file at the path registered in the CREATE statement, replacing the previous file or appending rows.
  3. Run the SELECT again.

Reads are live. On 22 October, a row appended after the table was registered appeared in the next query. No separate refresh statement was measured. A full file replace uses the same path; that shape was not measured on its own.

On this page