| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
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, integrity,
strict-schema conformance, 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. The owner (the background sync's shadow-swap, which retires an
artifact only when no evaluation still reads it) holds the CveDb and closes it
explicitly.
Synopsis
- data CveDb = CveDb {
- cveDbLookup :: CveLookup
- cveDbClose :: IO ()
- cveDbMeta :: [(Text, Text)]
- openCveDb :: Ecosystem -> FilePath -> IO (Either CveDbRejected CveDb)
- data CveLookup = CveLookup {
- cveRemediationProbe :: Text -> Text -> IO Bool
- cveAdvisoriesFor :: Text -> IO [AdvisoryRange]
- data AdvisoryRange = AdvisoryRange {
- arCveId :: Text
- arSeverity :: Maybe Double
- arIntroduced :: Maybe Text
- arFixed :: Maybe Text
- arLastAffected :: Maybe Text
- data CveDbRejected
- data CveQueryFault = CveQueryFault {}
- newtype DbEtag = DbEtag Text
- insideAffectedRange :: Ecosystem -> Text -> AdvisoryRange -> Bool
- severityAtLeast :: Double -> Maybe Double -> Bool
The owning resource
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
| |
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. Nothing an
artifact carries can make this throw: acceptance admits only a conformant
STRICT schema whose stored values the integrity walk verified, so the
provenance decode below it is total. Faults below the artifact contract (an
unopenable file) still throw, and then too the connection is already closed:
an exception never leaks it.
The consumer view
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 segment recorded against a package: the advisory's identifier,
its CVSS base score (0 to 10, Nothing when unscored), and the affected
interval's bounds as the artifact stores them (verbatim version text). The lower
bound arIntroduced is inclusive (Nothing == from the beginning); the upper
bound is (exclusive) or arFixed (inclusive) or neither
(open-ended). An exactly-enumerated affected version is a point segment
(arLastAffectedintroduced == last_affected).
Constructors
| AdvisoryRange | |
Fields
| |
Instances
| Show AdvisoryRange Source # | |
Defined in Ecluse.Core.Cve.Internal Methods showsPrec :: Int -> AdvisoryRange -> ShowS # show :: AdvisoryRange -> String # showList :: [AdvisoryRange] -> ShowS # | |
| Eq AdvisoryRange Source # | |
Defined in Ecluse.Core.Cve.Internal Methods (==) :: AdvisoryRange -> AdvisoryRange -> Bool # (/=) :: AdvisoryRange -> AdvisoryRange -> Bool # | |
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 |
| CveDbIntegrityFailed [Text] | The artifact is not a usable SQLite database: either it is not a
database at all (absent or wrong header magic, which SQLite reports as
|
| CveDbSchemaNonConformant Text | A required relation (carried) does not conform to the epoch's schema
contract: absent, not a real |
| CveDbEcosystemMismatch (Maybe Text) | The artifact's |
Instances
| Show CveDbRejected Source # | |
Defined in Ecluse.Core.Cve.Internal Methods showsPrec :: Int -> CveDbRejected -> ShowS # show :: CveDbRejected -> String # showList :: [CveDbRejected] -> ShowS # | |
| Eq CveDbRejected Source # | |
Defined in Ecluse.Core.Cve.Internal Methods (==) :: CveDbRejected -> CveDbRejected -> Bool # (/=) :: CveDbRejected -> CveDbRejected -> Bool # | |
Query faults
data CveQueryFault Source #
A query the accepted advisory database could not answer: the SQLite edge
threw mid-query (an I/O error on the database file, a connection released out
from under a straggling reader). Carries which handle field was asked and the
rendered SQLError for the log line; the artifact's content can never
produce this (openCveDb acceptance made the row decodes total), so it marks an
infrastructural fault, not a data fault.
A confined typed exception, the same shape as
CredentialError at the breaker leaf: it is
thrown at the SQLite edge inside the handle and absorbed by the one boundary
every advisory query runs under -- the rules engine's resilience harness
(runEffectfulRule), which resolves it to an Unavailable
evaluation and advances the rule's circuit breaker. It never crosses that
boundary, so no caller above the rules engine sees it. The alternative (an
Either on every CveLookup field) would reshape every rule's evaluation type
for a fault only the harness ever handles.
Constructors
| CveQueryFault | |
Instances
| Exception CveQueryFault Source # | |
Defined in Ecluse.Core.Cve Methods toException :: CveQueryFault -> SomeException # fromException :: SomeException -> Maybe CveQueryFault # displayException :: CveQueryFault -> String # backtraceDesired :: CveQueryFault -> Bool # | |
| Show CveQueryFault Source # | |
Defined in Ecluse.Core.Cve Methods showsPrec :: Int -> CveQueryFault -> ShowS # show :: CveQueryFault -> String # showList :: [CveQueryFault] -> ShowS # | |
| Eq CveQueryFault Source # | |
Defined in Ecluse.Core.Cve Methods (==) :: CveQueryFault -> CveQueryFault -> Bool # (/=) :: CveQueryFault -> CveQueryFault -> Bool # | |
Artifact identity
An artifact version marker: S3's ETag, opaque text compared for equality only. Two objects with equal ETags carry equal bytes, so an unchanged ETag is "nothing to do", a rejected artifact's remembered ETag is "still the same bad artifact", and it names the exact advisory database a rule decision was recorded against.
Pure range matching
insideAffectedRange :: Ecosystem -> Text -> AdvisoryRange -> Bool Source #
Is this version inside the advisory segment's affected interval, under the
ecosystem's version ordering? The interval is introduced <= v bounded above by
either v < fixed (exclusive) or v <= last_affected (inclusive), whichever the
segment carries, or unbounded when it carries neither. A point segment
(introduced == last_affected) is affected only at that exact version.
Fail-closed. Both the remediation fast lane (a fix must not fast-track while it sits inside another advisory's affected range) and the deny gate (an unvettable version must not be admitted) want the same polarity, so every unprovable comparison, an unparseable bound or version, counts as inside: trust is only ever granted on evidence.
severityAtLeast :: Double -> Maybe Double -> Bool Source #
Does this advisory segment's severity meet or exceed the threshold (a CVSS
base score, 0 to 10)? arSeverity is the artifact's normalised numeric score
(Pilot reduces a CVSS vector or a qualitative label to a number at ingest), or
absent.
Fail-closed for the deny direction. The threshold gates a deny, so a severity
that cannot be shown to fall below it counts as meeting it: an unscored advisory
(Nothing -- most of the npm malware feed) returns True. Only a score strictly
below the threshold returns False.