Documentation
¶
Overview ¶
Package sarif provides Draugr's result currency: a pragmatic model of SARIF 2.1.0 findings, plus merge and deduplication. Every scanner normalizes its output to a Report; the engine merges reports and the result can be serialized to standard SARIF JSON for GitHub / Azure DevOps / GitLab.
Index ¶
Constants ¶
const Version = "2.1.0"
Version is the SARIF specification version Draugr emits.
Variables ¶
This section is empty.
Functions ¶
func DerivedHelpURI ¶ added in v0.35.0
DerivedHelpURI maps identifiers with a stable, publicly resolvable home to their advisory. It's the fallback for scanners that publish no rule metadata; anything unrecognized gets "".
Types ¶
type Field ¶ added in v0.51.0
Field is one statement in a Provenance entry.
A slice of pairs rather than a map: rendering needs a stable order, and alphabetical is the wrong one — it puts "coverage" before "benchmark". The scanner knows which matters most to a reader, so it decides.
type Level ¶
type Level string
Level is the severity of a result, mirroring SARIF's result.level.
const ( LevelError Level = "error" LevelWarning Level = "warning" LevelNote Level = "note" LevelNone Level = "none" )
The SARIF result levels.
type Location ¶
type Location struct {
URI string `json:"uri,omitempty"`
StartLine int `json:"startLine,omitempty"`
}
Location points at where a finding was observed.
type MarshalOptions ¶ added in v0.35.0
type MarshalOptions struct {
// Compact drops what only a human reads — indentation, and the rule prose relayed from
// the scanner — while keeping the report valid SARIF.
//
// It exists for a consumer that is going to *act* on the report rather than read it,
// typically an agent paying for every byte of context. Rule descriptions and remediation
// text are the bulk of a Draugr report (61% of it, measured on this repo), and a reader
// that can follow a link doesn't need them inlined. So helpUri survives compaction and the
// prose doesn't: keep the pointer, drop the paragraphs.
Compact bool
}
MarshalOptions tunes how a report is serialized. The zero value is the default: indented, with everything a person or an editor might want.
type Provenance ¶ added in v0.51.0
type Provenance struct {
// Tool is the scanner that produced this account.
Tool string `json:"tool"`
// Version is the scanner's version as the engine resolved it — the same value that goes into
// its cache key, so the evidence and the cache cannot disagree about what ran. Empty when the
// scanner does not report one.
Version string `json:"version,omitempty"`
// Fields are the scanner's own statements about the run, in the order it considers useful.
//
// Untyped, because the interesting ones are domain knowledge — "benchmark", "coverage",
// "scope" — and this package is the finding currency for every scanner Draugr will ever
// have. It should not learn what a CIS benchmark is to carry the fact that one was applied.
Fields []Field `json:"fields,omitempty"`
}
Provenance is one scanner's account of a run it performed.
A slice on Report rather than a map of fields, because a control can be served by more than one scanner and each has its own answer — two scanners auditing a cluster apply two different benchmarks. Flattening them into one map keeps whichever was written last, silently, which is the failure this type exists to prevent.
func (Provenance) Describe ¶ added in v0.51.0
func (p Provenance) Describe() string
Describe returns the fields as "key value" pairs, for a reporter with one line to spend.
type Report ¶
type Report struct {
Tool string `json:"tool,omitempty"`
Results []Result `json:"results"`
// Rules is the metadata the scanner published about the rules it applied, keyed by rule id.
// A result names its rule; the rule is what explains it. Carrying this through is what lets
// a reader — in a terminal, an editor, or a pull request — find out what "DS-0002" means.
// Not every scanner publishes it, so entries may be missing.
Rules map[string]Rule `json:"rules,omitempty"`
// Provenance is what the scanners said about the run itself, as opposed to what they found:
// which standard was applied, how much of it could be decided, what the scan was scoped to.
//
// A finding answers "what is wrong". Evidence also has to answer "what was measured, and
// against what" — and that was not recorded anywhere, so a compliance report could not say
// which benchmark produced it. One entry per tool run; see Provenance.
Provenance []Provenance `json:"provenance,omitempty"`
}
Report is a set of findings, normalized to SARIF semantics. Tool names the primary scanner; when a report carries results from several tools (after Merge), each Result keeps its own Tool.
func FromSARIF ¶
FromSARIF parses standard SARIF 2.1.0 JSON into a Report, flattening all runs and setting each result's Tool from its run's driver name.
func Merge ¶
Merge combines reports into one, deduplicating results by fingerprint and preserving first-seen order. Each result's Tool is backfilled from its source report when unset.
func (Report) Dedup ¶
Dedup returns a copy with exact-duplicate results removed, preserving first-seen order.
func (Report) HelpURI ¶ added in v0.35.0
HelpURI returns where a reader can look up ruleID: what the scanner published, or a URL derived from a well-known identifier scheme when it published nothing. Empty when we can't say — a wrong link is worse than none.
func (Report) Highest ¶
Highest returns the most severe level present, or LevelNone when there are no results.
func (Report) MarshalSARIF ¶
MarshalSARIF serializes the report to standard SARIF 2.1.0 JSON as a single "Draugr" run, with each result's originating scanner recorded in its property bag ("tool").
func (Report) MarshalSARIFWith ¶ added in v0.35.0
func (r Report) MarshalSARIFWith(opts MarshalOptions) ([]byte, error)
MarshalSARIFWith is MarshalSARIF with explicit options.
type Result ¶
type Result struct {
// Tool is the scanner that produced the finding.
Tool string `json:"tool,omitempty"`
RuleID string `json:"ruleId"`
Level Level `json:"level"`
Message string `json:"message"`
Location Location `json:"location,omitempty"`
// Score is the finding's numeric CVSS-style severity (0–10), sourced from the SARIF
// "security-severity" property. HasScore reports whether a score was present; without
// one, normalized Severity falls back to Level.
Score float64 `json:"score,omitempty"`
HasScore bool `json:"-"`
// Priority is the computed action band (P1–P4) for this finding, stamped by the engine
// from the component's risk classification. Empty when prioritization is not configured.
Priority string `json:"priority,omitempty"`
// Component names the part of the application this finding belongs to, stamped by the engine
// from the component whose scan produced it. Empty for a project-scoped control, which has
// no one component to attribute to.
//
// A location alone is ambiguous the moment a descriptor has two components: three components
// have three go.mod files, and two can carry the same path. It is also what makes the
// priority checkable — the band is computed from the component's declared exposure and
// criticality, so a report showing the band without naming the component states a conclusion
// and withholds its premise.
Component string `json:"component,omitempty"`
// Suppression is set when a Saga exclusion matched this finding. A suppressed result is
// reported but not counted: it does not reach Counts, the verdict, or the fix-first list.
// Nil for an active finding.
Suppression *Suppression `json:"suppression,omitempty"`
}
Result is a single finding.
func (Result) Fingerprint ¶
Fingerprint is a stable identifier for deduplication: two results with the same fingerprint are considered the same finding.
func (Result) Severity ¶ added in v0.5.0
Severity resolves a finding's normalized severity, in the order the SARIF/prioritization design prescribes:
- the finding's numeric score (CVSS / SARIF security-severity), if present;
- otherwise its SARIF level;
- then raised to floor if floor is more severe (a control-default floor, e.g. a leaked secret is never "low"). Pass an empty floor for no floor.
func (Result) Suppressed ¶ added in v0.42.0
Suppressed reports whether this finding was excluded by a Saga rule.
type Rule ¶ added in v0.35.0
type Rule struct {
// Name is a human-readable identifier (SARIF reportingDescriptor.name), where the id
// itself is opaque.
Name string `json:"name,omitempty"`
// ShortDescription is a single sentence; FullDescription is a paragraph.
ShortDescription string `json:"shortDescription,omitempty"`
FullDescription string `json:"fullDescription,omitempty"`
// Help is remediation guidance, often Markdown.
Help string `json:"help,omitempty"`
// HelpURI points at the rule's documentation or advisory.
HelpURI string `json:"helpUri,omitempty"`
}
Rule is what a scanner says about one of its rules, beyond the bare id. Every field is optional: scanners vary widely in how much they publish, and an absent field is normal.
type Severity ¶ added in v0.5.0
type Severity string
Severity is Draugr's normalized, cross-control severity ladder. Unlike Level (the SARIF wire values error/warning/note), Severity is what prioritization ranks on: it splits a numeric CVSS-style score into four bands so a dependency CVE, a leaked secret, and an IaC misconfiguration can share one ordered list. See docs/concepts.md (prioritization).
const ( SeverityCritical Severity = "critical" SeverityHigh Severity = "high" SeverityMedium Severity = "medium" SeverityLow Severity = "low" )
Severity bands, from most to least severe.
type Suppression ¶ added in v0.42.0
type Suppression struct {
// Kind is the SARIF suppression kind. Draugr writes "external": the decision came from the
// Saga, not from an annotation in the source.
Kind string `json:"kind"`
// Justification is the reason the Saga gave. Required by Draugr even though SARIF allows
// it to be absent.
Justification string `json:"justification"`
// AcceptedBy is who decided this was acceptable, when the exclusion said. Empty means the
// suppression is unattributed — reported as such, because "who decided" is half the question
// an auditor is asking and a blank is an answer worth seeing.
AcceptedBy string `json:"acceptedBy,omitempty"`
// Expires is when the acceptance lapses, as YYYY-MM-DD. Empty means it does not.
Expires string `json:"expires,omitempty"`
}
Suppression records that a finding was excluded, and why.