pipeline

package
v3.100.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package pipeline is the owner of "run an analysis".

Both the CLI's scan family and the language server reach analysis through this package, which is what AGENTS.md means by "every capability has exactly one owner, reached through one options-struct entry function". Before this package existed, the analysis lived in cmd/runLocalScan: 1,600 lines, 40 positional parameters, and hard-wired to a terminal.

Reporter is the seam that made the extraction possible. The analysis emits progress and diagnostics through this interface instead of writing to a terminal, so the same code path can drive a spinner on stderr, an LSP $/progress token, or nothing at all in a test.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Level

type Level int

Level classifies a Reporter log line. It maps onto LSP MessageType (1=Error, 2=Warning, 3=Info) without translation.

const (
	LevelInfo Level = iota
	LevelWarn
	LevelError
)

func (Level) String

func (l Level) String() string

type LicensePolicy

type LicensePolicy struct {
	// Mode is "inclusive" or "individual".
	Mode string
	// AllowCSV is a comma-separated SPDX allowlist.
	AllowCSV string
	// AllowFile is a path to a newline-separated SPDX allowlist.
	AllowFile string
	// PolicyFile and ExceptionsFile are the declarative forms of the same
	// decision: a category-based policy and its approved exceptions. Empty
	// means "discover the default path under the scanned root, and fall back to
	// the built-in policy" — so a project that has neither behaves exactly as
	// it did before they existed.
	PolicyFile     string
	ExceptionsFile string
}

LicensePolicy is the licence policy as the scan-family flags express it, handed to the licence owner (runLicensePipeline) when that stage runs.

type Options

type Options struct {

	// Files are the manifests and SBOMs already detected under RootPath. The
	// caller runs detection so it can report and filter before committing to a
	// scan.
	Files []scan.DetectedFile

	// RootPath is the absolute directory being analysed.
	RootPath string

	// Depth bounds directory recursion during manifest detection (--depth).
	Depth int

	// Excludes are glob patterns removed from the walk (--exclude).
	Excludes []string

	// Deployment labels where these results are deployed and who owns them
	// (--project/--cluster/--namespace/--environment/--tag). It is inert for
	// the analysis itself — nothing branches on it — and exists so the labels
	// reach the CycloneDX metadata and the upload envelope, where the server
	// can join results across repositories. See internal/cdx/deployment.go.
	Deployment cdx.DeploymentContext

	// ManufacturerSources are the candidates for the organization that created
	// metadata.manufacturer, which is the field for documents produced by an
	// automated process. It is the organization running this scan, not the
	// vendor of the scanner; the scanner is named in metadata.tools. Nil when
	// nothing resolved it, which leaves the member absent rather than guessed.
	// Resolved by the caller because only it has the command's flags.
	ManufacturerSources cdx.ManufacturerSources

	// LifecycleOverride replaces the capture stages the engine would derive from
	// what it read (--lifecycle). Nil means derive. A pipeline that states its
	// own stage is making a claim about its process that no amount of filesystem
	// inspection can contradict, so the override wins outright.
	LifecycleOverride []cdx.LifecyclePhase

	// VEXFiles are third-party VEX documents to apply to the findings before
	// the quality gates are evaluated (--vex-file). Empty when --no-vex was
	// passed, so the engine has one thing to check rather than two.
	VEXFiles []string

	NoSCA        bool
	NoSASTRules  bool
	NoSecrets    bool
	NoContainers bool
	NoIAC        bool
	NoLicenses   bool

	// NoExploits and NoRemediation drop the corresponding VDB enrichment
	// passes; the SCA lookup itself still runs.
	NoExploits    bool
	NoRemediation bool

	// SeverityThreshold is validated against scan.ValidSeverityThresholds.
	SeverityThreshold string
	// ExploitThreshold is validated against scan.ValidExploitThresholds.
	ExploitThreshold string
	BlockMalware     bool
	BlockEOL         bool
	BlockUnpinned    bool
	VersionLag       int
	CooldownDays     int

	// Jail runs the organisation's jail policy against this repository after the
	// scan has uploaded, and gates on the verdict.
	//
	// Unlike the thresholds above, which grade THIS run's findings, the jail
	// policy grades the repository's accumulated state across every tool and
	// category that has reported for it. A breach exits 1 alongside the other
	// gates; a rule that cannot be evaluated against current scan coverage exits
	// 3, which is a different problem with a different owner.
	Jail bool

	// DisableDefaultRules drops the embedded corpus, leaving only RuleRefs.
	DisableDefaultRules bool
	// RuleRefs are external rule packs, cloned from RuleRegistry.
	RuleRefs []sast.RuleRef
	// RuleRegistry defaults to sast.DefaultRegistry when empty.
	RuleRegistry string
	// RuleID restricts the run to a single VNX-NNNN rule.
	RuleID string
	// LockedKinds restricts the run to these rule kinds, embedded and external
	// alike. Nil means "no lock", which is the generic scan command's
	// behaviour. The language server sets this per trigger: the keystroke path
	// runs sast/iac/oci only, because the 1,092 secrets rules cost roughly 20x
	// what every other kind costs combined.
	LockedKinds []string
	// SnippetContext shapes SARIF snippets: -1 picks a width from the finding
	// span, 0 disables snippets.
	SnippetContext int

	// Reachability selects the tree-sitter reachability mode applied to the
	// CVEs the SCA pass produces: "direct" scans only each vulnerable
	// package's installed directory, "transitive" sweeps the rest of the
	// project for callers, "both" does both, and "off" skips the analysis (and
	// the server-side query fetch) entirely. Empty means "both".
	//
	// It is a string rather than a reachability.Mode so internal/pipeline stays
	// free of the CGo tree-sitter dependency; the value is validated by
	// internal/scanopts before it gets here.
	Reachability string

	IgnoreGlobs          []string
	IgnoreGit            bool
	IgnoreBinaries       bool
	GitHistory           bool
	GitHistoryMaxCommits int
	GitHistoryMaxFiles   int
	// RespectGitignore prunes files matched by .gitignore.
	RespectGitignore bool

	SCAAutofix     bool
	SCAAutofixOpts fix.Options
	// AutofixResolved carries findings a previous autofix pass fixed, so the
	// confirmation re-scan can mark them resolved rather than re-reporting.
	AutofixResolved []*triage.TriageFinding

	License LicensePolicy

	GitCtx  *gitctx.GitContext
	SysInfo *gitctx.SystemInfo
	// SeedBOM and VulnetixSeedBOM carry components from an existing SBOM into
	// the produced document, so a scan never loses what a previous one found.
	SeedBOM         *cdx.BOM
	VulnetixSeedBOM *cdx.BOM

	// ShowPaths includes the dependency chain that introduced each package.
	ShowPaths bool
	// ResultsOnly suppresses all output when the run is clean.
	ResultsOnly bool
	// DryRun performs detection, parsing and memory reads with zero API calls.
	DryRun bool
	// NoProgress suppresses the live progress row. Kept here because it also
	// reaches callees that build their own display contexts.
	NoProgress bool
}

Options is everything an analysis run needs, as named fields.

This replaces the 40 positional parameters cmd/runLocalScan used to take, a signature where `true, noSCA, true, true, true` was a real call site and adding a parameter meant touching every caller. More importantly it is the entry point AGENTS.md asks for: one options struct that both the scan subcommands and the language server fill in, so neither owns a private version of "what a scan is".

Deliberately absent: anything about presentation. Where results are written, how they are rendered, and whether there is a terminal at all are the caller's business. Progress and log output go through Reporter.

type Reporter

type Reporter interface {
	// Stage changes the current stage label without changing numeric progress.
	Stage(stage string)

	// Update sets numeric progress out of the total declared at construction,
	// and, when stage is non-empty, the stage label with it.
	Update(done int, stage string)

	// Logf writes one diagnostic line. Callers must not append a newline.
	Logf(level Level, format string, args ...any)

	// Degraded records that part of the analysis did not run to completion:
	// files skipped over the size cap, git history unavailable, a stage that
	// failed without failing the run. These are collected and surfaced, because
	// "no findings" and "did not look" must not be indistinguishable.
	Degraded(note string)

	// Degradations returns everything passed to Degraded, in order.
	Degradations() []string

	// Complete and Fail finalise the run exactly once. Subsequent calls are
	// ignored, so a deferred Complete after an early Fail is harmless.
	Complete(stage string)
	Fail(stage string)

	// Writer is the transitional escape hatch for callees that still take an
	// io.Writer directly (sast.LoadAllModules, autofix.RunInstall,
	// postScanSARIF). Everything written to it is ordinary log output. New code
	// should use Logf; this exists so the extraction could land without also
	// rewriting every callee's signature in the same change.
	Writer() io.Writer
}

Reporter receives progress, log output and degradation notices from an analysis run.

Implementations must be safe for concurrent use: the scan stages fan out across goroutines, and the language server evaluates shards in parallel.

A nil Reporter is not valid; use Discard() when output is not wanted.

func Discard

func Discard() Reporter

Discard returns a Reporter that produces no output. Use it for --silent runs and in tests that do not assert on progress.

type TerminalReporter

type TerminalReporter struct {
	// contains filtered or unexported fields
}

TerminalReporter drives the existing display.Progress activity: one live line on stderr, with log output routed through Progress.Writer so a log line clears and redraws the progress row instead of leaving duplicates behind.

This is the reporter the CLI uses, and it reproduces exactly what cmd/runLocalScan did inline before the extraction. The language server installs a different implementation that turns the same calls into $/progress notifications.

func NewTerminalReporter

func NewTerminalReporter(title string, total int, silent, noProgress bool) *TerminalReporter

NewTerminalReporter starts a progress activity titled `title` with `total` numbered steps, rendering to stderr.

The arguments mirror the CLI's own flags: silent suppresses everything, noProgress keeps log output but drops the live line. Both are honoured by display.Progress itself, so a disabled progress activity is a safe no-op rather than a nil that every call site has to check.

func NewTerminalReporterFrom

func NewTerminalReporterFrom(progress *display.Progress) *TerminalReporter

NewTerminalReporterFrom wraps an already-started progress activity. Use it when the caller owns the display context, for instance because it renders results through the same context after the analysis finishes.

func (*TerminalReporter) Complete

func (r *TerminalReporter) Complete(stage string)

func (*TerminalReporter) Degradations

func (r *TerminalReporter) Degradations() []string

func (*TerminalReporter) Degraded

func (r *TerminalReporter) Degraded(note string)

func (*TerminalReporter) Fail

func (r *TerminalReporter) Fail(stage string)

func (*TerminalReporter) Logf

func (r *TerminalReporter) Logf(level Level, format string, args ...any)

func (*TerminalReporter) Progress

func (r *TerminalReporter) Progress() *display.Progress

Progress exposes the underlying activity for the few call sites that still need it directly. Kept narrow on purpose: everything reachable through Reporter should go through Reporter.

func (*TerminalReporter) Stage

func (r *TerminalReporter) Stage(stage string)

func (*TerminalReporter) Update

func (r *TerminalReporter) Update(done int, stage string)

func (*TerminalReporter) Writer

func (r *TerminalReporter) Writer() io.Writer

type WriterReporter

type WriterReporter struct {
	// contains filtered or unexported fields
}

WriterReporter writes plain lines to w and keeps a degradation ledger. It is the implementation used by tests that want to assert on output, and by any caller that has a writer but no terminal.

func NewWriterReporter

func NewWriterReporter(w io.Writer) *WriterReporter

NewWriterReporter returns a Reporter writing to w. A nil w discards.

func (*WriterReporter) Complete

func (r *WriterReporter) Complete(stage string)

func (*WriterReporter) Degradations

func (r *WriterReporter) Degradations() []string

func (*WriterReporter) Degraded

func (r *WriterReporter) Degraded(note string)

func (*WriterReporter) Fail

func (r *WriterReporter) Fail(stage string)

func (*WriterReporter) Logf

func (r *WriterReporter) Logf(level Level, format string, args ...any)

func (*WriterReporter) Stage

func (r *WriterReporter) Stage(stage string)

func (*WriterReporter) Update

func (r *WriterReporter) Update(_ int, stage string)

func (*WriterReporter) Writer

func (r *WriterReporter) Writer() io.Writer

Jump to

Keyboard shortcuts

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