diff

package
v0.125.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 15, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package diff compares two Draugr scan results and classifies every finding as new, fixed, or unchanged, the security delta of a change (typically a PR's head vs the base branch). It powers `draugr diff` and its differential gate ("fail only on findings this change introduces").

Inputs are SARIF reports (the results.sarif that `draugr scan -o` writes): SARIF is Draugr's complete, structured result currency, whereas the JSON summary can be trimmed by --min-priority.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Formats

func Formats() []string

Formats lists the diff output formats, sorted.

func Render

func Render(w io.Writer, format string, r Result, opts Options) error

Render writes the diff in the named format. Unknown formats error.

func Views added in v0.121.0

func Views() []string

Views lists them, sorted, for the flag's own help.

Types

type Change added in v0.121.0

type Change string

Change is what happened to a finding between the two scans.

One vocabulary for the four states, so a renderer, a gate and a reader all name them the same way. Unchanged is not here: it is the pre-existing backlog this exists to leave out.

const (
	ChangeNew        Change = "new"
	ChangeUnaccepted Change = "unaccepted"
	ChangeAccepted   Change = "accepted"
	ChangeFixed      Change = "fixed"
)

The four states a diff reports, and the mark each one carries.

func (Change) Mark added in v0.121.0

func (c Change) Mark() string

Mark is the sign this change wears in a listing.

A glyph as well as a word, because a column of words is read and a column of marks is scanned, and the four are told apart at a glance where a reader is looking for the new ones among sixty.

func (Change) NeedsSomebody added in v0.121.0

func (c Change) NeedsSomebody() bool

NeedsSomebody reports whether this change is work or a decision rather than good news.

A fix is the one state nobody has to do anything about. The rest are something introduced, something whose acceptance ended, or something somebody chose to live with, and each of those wants a person.

type Entry added in v0.121.0

type Entry struct {
	Change Change
	sarif.Result
}

Entry is one finding and what happened to it.

type Gate added in v0.121.0

type Gate struct {
	FailOn         sarif.Severity
	FailOnPriority string
}

Gate is the differential rule a run applied, carried on the result so every rendering can state what the verdict was measured against.

Empty when nothing was asked for, which is a real state and not a default: `draugr diff` with no gate reports and exits 0, and a report claiming a verdict nobody asked for would be inventing one.

func (Gate) Sentence added in v0.121.0

func (g Gate) Sentence() string

Sentence is the rule in one clause, for a reader who was not there when it ran.

func (Gate) Stated added in v0.121.0

func (g Gate) Stated() bool

Stated reports whether a gate was asked for at all.

type Options added in v0.121.0

type Options struct {
	View View
	// Top caps the listing. Zero shows everything, which is the default here and not on `scan`:
	// a diff is already narrowed to what one change did, and hiding part of that removes the thing
	// the command exists to show. The flag is for the dependency bump that introduces forty.
	Top int
}

Options are what the caller asked for, beyond the comparison itself.

type PriorityCounts

type PriorityCounts struct {
	P1 int `json:"p1"`
	P2 int `json:"p2"`
	P3 int `json:"p3"`
	P4 int `json:"p4"`
}

PriorityCounts tallies findings by action band. Unprioritized findings are not counted here.

type Result

type Result struct {
	New       []sarif.Result // present and active in head, absent from base
	Fixed     []sarif.Result // active in base, absent from head
	Unchanged []sarif.Result // active in both (head copy)
	// Accepted is a finding somebody excused between the two scans, and one that arrived already
	// excused by a rule that was already there.
	//
	// Its own category because accepting a risk is not fixing it, and reporting it as a fix tells
	// a reviewer the opposite of what happened: nobody removed the finding, somebody decided to
	// live with it. That decision is the change most worth a second pair of eyes, and it used to
	// read as good news.
	Accepted []sarif.Result
	// Unaccepted is a finding that was excused in the base and counts again in the head, an
	// exclusion removed, or one that reached its expiry date.
	//
	// Distinct from New because nobody introduced it. It was known, it was accepted, and the
	// acceptance ended; reporting it as a fresh discovery loses the part somebody needs to act on,
	// which is that a decision has to be made again.
	//
	// Named for the decision rather than for the finding. "Reopened" is issue-tracker vocabulary
	// for something that was fixed and came back, and nothing here was ever fixed.
	Unaccepted []sarif.Result
	// Rules is what the scanners said about the rules these findings cite, carried over from the
	// reports being compared.
	//
	// A diff that keeps only results keeps only identifiers. `CVE-2018-1000656` in a table is a
	// string to copy into a search box, and the same id uploaded to code scanning arrives with no
	// description and whatever link can be guessed from its shape, while the scanner that found it
	// published both. Keeping the rules is what lets a reader click the finding instead of looking it
	// up.
	Rules map[string]sarif.Rule
	// Gate is the differential rule this run applied, so every rendering can say what the verdict
	// was measured against rather than leaving a reader to assume it.
	Gate Gate
	// Tripped is the new findings that met the gate. Empty where none did, and where no gate was
	// asked for at all, which are different states the Gate itself tells apart.
	Tripped []sarif.Result
}

Result is the classified delta between a base and a head report.

func Compare

func Compare(base, head sarif.Report) Result

Compare classifies every finding across the two reports by stable identity.

Five outcomes rather than three, because a suppression is a state a finding can be in and not a reason to stop tracking it. A finding that became suppressed was accepted, not fixed; one that stopped being suppressed came back, and nobody introduced it.

func (Result) Changed added in v0.121.0

func (r Result) Changed() []Entry

Changed is every finding this change touched, worst first.

One list rather than four, because a reviewer asks "what did this change do" and gets the answer in priority order, where four sections make them read the same ranking four times and compare across the gaps. The lists stay separately addressable for the gate and the SARIF upload, which ask about the new findings alone.

func (Result) Counted added in v0.121.1

func (r Result) Counted() Result

Counted drops a second scanner's copy of a flaw the first one already accounts for, from every state a reader is shown a number for.

Two scanners over one target report one flaw twice, and the scan already answers for that: the copy carries correlation.countedUnder, and every count a person reads leaves it out. A diff that did not would put two numbers for one change in front of the same reader, nine in the terminal and fourteen on the pull request, with nothing on either screen saying which is the arithmetic.

The copies are dropped from the lists rather than from the report. renderSARIF works from the uncounted result, so what is uploaded to code scanning still carries what each scanner said, and so does the head scan's own SARIF.

The gate already asked the right question, so Tripped is carried through untouched.

func (Result) GateNew

func (r Result) GateNew(failOn sarif.Severity, failOnPriority string) []sarif.Result

GateNew returns the new findings that meet the differential gate: level at or above failOn (when set) OR priority at or above failOnPriority (when set). An empty threshold disables that dimension. With both empty, nothing is returned.

func (Result) HelpURI added in v0.83.0

func (r Result) HelpURI(ruleID string) string

HelpURI is where a reader can look up a rule: what the scanner published, or a URL derived from a well-known identifier scheme. Empty when neither applies. A wrong link is worse than none.

func (Result) NarrowNew added in v0.82.0

func (r Result) NarrowNew(band string) Result

NarrowNew drops new findings below a priority band, leaving fixed and unchanged alone.

Only the new ones, because they are what the diff is reporting and what a reviewer is asked to act on; fixed and unchanged are context, and a count of them that moved with a threshold would mean something different on every run.

A finding the scanner never prioritized is kept. An empty Priority means prioritization did not run for it, not that it ranked low. Dropping it would hide a finding for the reason it was hardest to judge.

func (Result) OnlyRepository added in v0.85.0

func (r Result) OnlyRepository(ref string) Result

OnlyRepository keeps the new findings a given repository's checkout can actually anchor.

Paths are repository-relative, so a finding from another repository uploaded against this one resolves to a same-named file here, an annotation on a line that does not have that problem. That is wrong rather than merely noisy, and there is no case where it is wanted, so this is not offered as a preference.

A finding with no repository is kept. Not everything Draugr reports comes from a checkout: an image finding is located at an image reference and belongs to no repository, and dropping those would remove most of a container scan from the surface a reviewer reads.

type SeverityCounts added in v0.63.0

type SeverityCounts struct {
	Critical int `json:"critical"`
	High     int `json:"high"`
	Medium   int `json:"medium"`
	Low      int `json:"low"`
}

SeverityCounts tallies findings by Draugr's normalized severity band.

Bands rather than SARIF levels, because a diff is read next to the scan report it came from and the two have to agree. error/warning/note is the wire vocabulary of the file; a reader deciding whether a pull request made things worse is thinking in critical/high/medium/low.

type View added in v0.121.0

type View string

View is how much of each row a listing draws, the same three names `draugr scan` takes and with the same meaning, so a reader who has learned one has learned the other.

const (
	ViewFindings View = "findings"
	ViewActions  View = "actions"
	ViewCompact  View = "compact"
)

The three views.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL