# Scan database versions for known vulnerabilities

Match a self-managed database version against pinned local server advisories.

Source: https://docs.ptah.run/edge/operate/vulnerability-scan/

`ptah db vulnerabilities` reads the product and version of a live database and
matches them against a pinned local server advisory snapshot. You can run
it in CI without a Ptah account or an advisory service. The scan reads the
database and the local snapshot; it does not upload inventory.

This check answers whether a known advisory's version range matches the
observed server. [`ptah schema security`](../../schema/security/) checks roles,
grants, ownership, and policies instead. A clear result from either command is
limited to what that command assessed.

## Run the scan

You need an installed `ptah` binary and a database URL. The default
`--deployment self-managed` means your team controls server patching. For
PostgreSQL, the connected role also needs to read `pg_catalog.pg_extension`.
The command reads the version banner; PostgreSQL also reads the numeric server
version and extensions in the connected database. It does not change the database.

```sh
ptah db vulnerabilities --db-url "$DATABASE_URL"
```

The table report names each affected or uncertain advisory, its component,
installed server version, advisory URL, CVSS score when supplied, and known
fix. It also lists gaps and summarizes fixed and unaffected matches. The
`--format json` report keeps every assessment, including fixed and unaffected
ones, plus the inventory, source identity, and incomplete coverage.

```sh
ptah db vulnerabilities --db-url "$DATABASE_URL" --format json > vulnerabilities.json
```

The JSON source block includes the upstream URL, a SHA-256 of the source page,
a SHA-256 of the normalized snapshot, retrieval time, age in days, and
`current`, `stale`, or `unavailable`. The `assessments` array uses `affected`, `fixed`,
`unaffected`, or `uncertain`. The separate `incomplete` array names coverage
gaps. `applicability` distinguishes `assessed`, `unassessed`, and
`not_applicable`. Failed snapshot input or database inventory produces a
`scan_error` in JSON and exits with code `2`.

If a provider operates the server, select `--deployment managed`. The report
records `not_applicable`, makes no advisory assessment, and does not fail the
default CI policy. This override works for every engine, including YDB. Use
`self-managed` when your team controls the server version, even if it runs in
a cloud account. Ptah does not infer patch responsibility from the endpoint.

```sh
ptah db vulnerabilities --db-url "$DATABASE_URL" --deployment managed --format json
```

## Choose the CI policy

The default `--fail-on both` exits `1` for an unsuppressed affected advisory or
incomplete coverage. Select `findings`, `incomplete`, or `none` when your gate
needs a different policy. Input and connection failures exit `2` under every
policy.

```sh
ptah db vulnerabilities --db-url "$DATABASE_URL" --fail-on both --format json
```

To accept one affected advisory temporarily, write a local JSON suppression
file. Its ID, product, component, and reason are required. An expiry is
optional. An expired or invalid suppression fails the scan. Suppressed
assessments remain in the JSON report with the reason.

```json
[
  {
    "id": "CVE-2026-18024",
    "product": "postgres",
    "component": "core",
    "reason": "Vendor patch verified in change ticket SEC-42",
    "expires": "2026-12-31"
  }
]
```

```sh
ptah db vulnerabilities --db-url "$DATABASE_URL" --suppressions suppressions.json
```

## Check coverage before trusting the result

The engine declares whether Ptah can match its server version. Ptah bundles
the built-in engine snapshots. The YDB provider bundles its own snapshot:

| Engine | Reviewed source | Coverage |
| --- | --- | --- |
| PostgreSQL | [PostgreSQL security page](https://www.postgresql.org/support/security/) | Core and selected bundled modules on listed upstream release lines. |
| YDB provider | [YDB security changelog](https://ydb.tech/docs/en/security-changelog) | The provider owns server entries; the Go SDK entry is outside server scope. |
| MariaDB | [Community Server CVE list](https://mariadb.com/docs/server/security/cve/community-server) | Three selected server CVEs; the report remains incomplete. |
| ClickHouse | [Security changelog](https://clickhouse.com/docs/resources/changelogs/security-changelog) | One selected server CVE on four release lines; the report remains incomplete. |

A provider that declares scanning but supplies no reviewed snapshot returns
`unassessed` unless `--advisories` selects a local source.

The PostgreSQL snapshot names its supported lines in `release_lines`. It
matches core advisories and entries naming `pgcrypto`, `pg_trgm`, `intarray`,
or `refint`. Their fixes ship with the server minor release: `extversion`
records the installed module, not its patch level. The extension catalog
covers only the connected database. Client-only advisories are excluded.

DuckDB and Spanner declare a server scan `not_applicable`: DuckDB is embedded,
while Spanner is a managed service. SQLite returns `unassessed` because a
`libsql://` address can reach a remote server. Other built-in engines without
a reviewed matcher also return `unassessed`. An
unsupported release line, vendor build, or unrecognized
version remains uncertain because a distribution may backport a fix.
Version matching also cannot establish whether a vulnerable code path is
reachable. Investigate the linked advisory and your vendor's patch record
before deciding how to remediate.

The snapshot becomes stale 30 days after retrieval. A stale snapshot remains
visible in the report and counts as incomplete coverage. Refresh it before
using an empty finding list as a current assessment.

## Update the local snapshot

The daily `Server advisory snapshots` workflow in Ptah checks PostgreSQL,
MariaDB, and ClickHouse. The YDB provider runs its own daily source check and
bundles the reviewed result in its executable. Both workflows open a draft
pull request when a snapshot needs a refresh. PostgreSQL rows are parsed and
validated before the snapshot is written. An unchanged versioned source can
refresh its retrieval time after 14 days.

The YDB provider records both the raw documentation hash and the advisory
body hash. A changed documentation revision outside the advisory body creates
no review report. A changed advisory body produces a JSON report in the
provider's `internal/vulnerability/review/` directory. The report lists
observed CVE IDs and source hashes; it does not establish affected ranges.

Verify server CVEs, affected starts, fixed versions, and release lines before
updating a snapshot. Remove its review report before merging. Partial coverage
remains partial until the reviewed snapshot covers more advisories.

To run the daily source check locally, use:

```sh
go run ./internal/cmd/securitysnapshots
```

From a source checkout, run the updater and review the JSON diff:

```sh
go run ./internal/cmd/pgsecuritysnapshot
git diff -- internal/vulnerability/postgresql.json
```

The updater fetches the [PostgreSQL security page](https://www.postgresql.org/support/security/)
and records its source digest and retrieval time. Use `-input saved.html` to
parse a saved copy without network access. It refuses missing headings, table
columns, affected/fixed mismatches, unknown components, and empty coverage.
The scan itself never fetches advisories. You can select a reviewed local JSON
file with `--advisories path/to/snapshot.json`.

Review newly named components, release-line changes, and the advisory source
before committing an update. The snapshot derives from PostgreSQL's published
security information under the [PostgreSQL License](https://github.com/postgres/postgres/blob/master/COPYRIGHT).
The source link, digest, and license notice travel with the snapshot in the
repository.

An installed Ptah binary carries the built-in snapshots. A provider executable
carries its own snapshot. Update the relevant binary or use `--advisories` to
select another reviewed local file. For a versioned engine, the snapshot uses
schema version `2`. Review each affected start, fixed version, release line,
and CVE source before updating its JSON in the owning repository. Pin the
upstream page's SHA-256 and retrieval time.
Set `coverage` to `partial` with a reason when the snapshot omits advisories;
the scanner then reports incomplete coverage. A source URL or hash alone does
not prove that its affected ranges were reviewed.
