ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Cve

Description

The advisory lookup capability: answer CVE questions about a package version from a local, already-synced osv.db artifact, never the network.

The handle is deliberately dumb data access over one artifact file. Rule semantics live in pure predicates over what it returns (insideAffectedRange), because SQLite's text collation cannot order versions; only compareVersions can. The one deliberate exception is cveRemediationProbe: a fixed bound in the artifact is a single canonical version string, so exact-fix matching is plain string equality and rides the (package_name, fixed_version) index in one traversal. A fix published under a non-canonical version string misses the probe and simply waits out the ordinary quarantine; the operator workaround is an explicit AllowByIdentity rule.

An artifact is accepted or rejected at openCveDb (epoch stamp, table shape, ecosystem), with rejection as a value: the caller keeps its last known-good handle and alarms. See Ecluse.Core.Cve.Internal for the hardening detail.

Ownership is split at the type level: openCveDb yields a CveDb, the owning resource whose holder alone may cveDbClose; consumers are handed only its CveLookup view, so nothing evaluating rules can release a shared connection. A lexically-scoped use (a test, a one-shot check) brackets with withCveDb; a dynamically-scoped owner (the background sync's shadow-swap, which retires an artifact only when no evaluation still reads it) holds the CveDb and closes explicitly.

Synopsis

The owning resource

data CveDb Source #

One opened artifact: the consumer view plus the owner's close. Whoever holds this owns the connection's lifetime; hand consumers cveDbLookup only.

Constructors

CveDb 

Fields

  • cveDbLookup :: CveLookup

    The view consumers query through.

  • cveDbClose :: IO ()

    Release the artifact's connection. Owner-only; the artifact must no longer be read through this handle's view afterwards.

  • cveDbMeta :: [(Text, Text)]

    The artifact's meta provenance rows (Pilot version, ecosystem, build timestamp, source URL, row count), snapshotted at open, key-sorted. The audit surface that ties this handle's decisions to the exact database that produced them.

openCveDb :: Ecosystem -> FilePath -> IO (Either CveDbRejected CveDb) Source #

Open an osv.db artifact and build the owning handle over it, or reject the artifact (CveDbRejected) with its connection already closed. Throws on faults below the acceptance contract (an unreadable file, a malformed provenance row), and then too the connection is already closed: an exception never leaks it.

withCveDb :: Ecosystem -> FilePath -> (CveLookup -> IO a) -> IO (Either CveDbRejected a) Source #

Bracket a lexically-scoped use of an artifact: open, hand the consumer view to the action, and close on any exit. A rejected artifact short-circuits (Left) without running the action; its connection is already closed.

The consumer view

data CveLookup Source #

Advisory questions about one ecosystem's artifact -- the read-only view a consumer (a rule evaluation) is handed. It deliberately cannot release the underlying connection; that is the owning CveDb's capability.

Names and versions are the artifact's own vocabulary: the OSV wire package name (scope inline, e.g. @scope/name) and verbatim version text. Callers render their domain values to that form at the boundary.

Constructors

CveLookup 

Fields

What a lookup returns

data AdvisoryRange Source #

One advisory range recorded against a package: the advisory's identifier, its optional qualitative severity label, and the affected interval's bounds as the artifact stores them (verbatim version text; Nothing introduced means "from the beginning", Nothing fixed means "no fix known").

Instances

Instances details
Show AdvisoryRange Source # 
Instance details

Defined in Ecluse.Core.Cve.Internal

Eq AdvisoryRange Source # 
Instance details

Defined in Ecluse.Core.Cve.Internal

Rejection

data CveDbRejected Source #

Why a downloaded artifact was refused before a handle was built over it.

A rejection is a value, not an exception: the caller (the sync task, once it exists) has a real decision to make, keep the last known-good database and alarm, rather than a fault to unwind from.

Constructors

CveDbWrongEpoch Int

The artifact's user_version stamp (carried) does not match this binary's osvSchemaEpoch.

CveDbRangesNotATable

The ranges relation is not a plain table -- a view here is attacker-authored SQL wearing the table's name.

CveDbEcosystemMismatch (Maybe Text)

The artifact's meta table names a different ecosystem (carried) than the one this handle was asked to serve.

Instances

Instances details
Show CveDbRejected Source # 
Instance details

Defined in Ecluse.Core.Cve.Internal

Eq CveDbRejected Source # 
Instance details

Defined in Ecluse.Core.Cve.Internal

Pure range matching

insideAffectedRange :: Ecosystem -> Text -> AdvisoryRange -> Bool Source #

Is this version inside the advisory range's affected interval, introduced <= v < fixed, under the ecosystem's version ordering?

Fail-closed for the allow direction. This predicate guards the remediation fast lane (a fixed version must not fast-track while it sits inside another advisory's affected range), so every unprovable comparison, an unparseable bound, an unparseable version, counts as inside: trust is only ever granted on evidence. A future deny-direction consumer wants the same polarity for its own reason (cannot prove safe, assume affected), but must not reuse this documentation's rationale blindly if its needs diverge.