Reference for the plugin interfaces as implemented in pkg/plugin (and the
Reporter in pkg/report). See architecture.md for context.
The plugin kinds — Scanner, Controller, Surveyor, Reporter, Publisher — share a small set of value types. Scanners are transported in-process (built-ins) or declaratively (tool adapters that satisfy the Scanner contract at runtime); an out-of-process gRPC transport is planned.
// Target is something a scanner can act on. Identity is the stable cache-key value.
type Target interface {
Kind() TargetKind
Identity() string
}
type TargetKind string
const (
TargetRepository TargetKind = "repository"
TargetImage TargetKind = "image"
TargetHost TargetKind = "host"
TargetInfra TargetKind = "infrastructure"
)
type RepositoryTarget struct { URL, Revision string; Paths []string }
type ImageTarget struct { Ref, Digest string } // digest drives Identity() (cache key)
type HostTarget struct { Name, URL, Type string } // type: browser | api
type InfraTarget struct { Platform, Ref string } // e.g. kubernetes / prod-cluster
// ImageTarget.PinnedRef() returns the digest-pinned reference (repo:tag@sha256:…) a scanner
// should pull, so the bytes scanned match the digest the result is cached under.
// Config is validated against the plugin's declared JSON schema before use.
type Config map[string]any
// CacheKey = ComputeCacheKey(scanner name, version, target kind + Identity(), sorted config).
type CacheKey stringWraps a single tool; the atomic unit of work. Emits SARIF.
type Scanner interface {
Info() ScannerInfo
// Scan runs the tool against target and returns a SARIF report.
// Implementations must be side-effect-free w.r.t. the target and honor ctx cancellation.
Scan(ctx context.Context, target Target, cfg Config) (sarif.Report, error)
}
type ScannerInfo struct {
Name string // e.g. "trivy"
Binary string // external executable to check on PATH (e.g. "trivy"); "" if none
Version string // scanner/plugin version (part of the cache key)
Controls []string // controls it can serve, e.g. ["images"]
TargetKinds []TargetKind // targets it accepts
ConfigSchema json.RawMessage // JSON Schema for Config (drives the config wizard)
}
// A Scanner may optionally implement these; the engine uses them when present.
type CacheVersioner interface {
// CacheVersion contributes a tool/data version to the cache key (may do I/O, unlike Info),
// so a tool or vuln-DB update invalidates cached results. "" = no contribution.
CacheVersion(ctx context.Context) string
}
type Prewarmer interface {
// Prewarm warms shared tool state once before the concurrent fan-out (e.g. download the
// Trivy vuln DB). Best-effort.
Prewarm(ctx context.Context) error
}Orchestrates scanners for one security control; plans work and aggregates results.
type Controller interface {
Info() ControllerInfo
// Plan expands a component (or the project) into scan jobs for this control.
Plan(saga saga.Model, comp *saga.Component) ([]ScanJob, error)
// Aggregate merges + deduplicates this control's scanner outputs into one result.
Aggregate(results []sarif.Report) (ControlResult, error)
}
type ControllerInfo struct {
Name string // e.g. "images", "sast", "sca"
Scope Scope // project | component
Summary string // one-line purpose, shown by `draugr controls`
DefaultScanners []string // scanner(s) run by default (opt-in extras via config)
}
type Scope string
const ( ScopeProject Scope = "project"; ScopeComponent Scope = "component" )
type ScanJob struct {
Scanner string // scanner to run
Target Target
Config Config
CacheKey CacheKey // usually left empty; the engine computes the effective key
}
type ControlResult struct {
Control string
Report sarif.Report // merged, deduplicated
Summary Summary // counts by severity, for the Norn
}
type Summary struct { Errors, Warnings, Notes int }Discovers surface and contributes Saga fragments so the descriptor writes itself.
type Surveyor interface {
Info() SurveyorInfo
// Survey inspects a scope and returns a Saga fragment (components/targets it found).
Survey(ctx context.Context, scope SurveyScope) (saga.Fragment, error)
}
type SurveyorInfo struct {
Name string // e.g. "k8s-images", "github-org-repos"
Provides []TargetKind // what it can discover
ConfigSchema json.RawMessage
}
// SurveyScope examples: a kube context + namespace, a GitHub org, an ADO project.
type SurveyScope struct { Kind string; Ref string; Config Config }Renders a scan result in one format. Lives in pkg/report; draugr scan --format selects one. Built-in formats: console (default), markdown, html, junit,
json, sarif, and template (a user-supplied Go text/template).
type Reporter interface {
Format() string // "console", "markdown", "html", "junit", "json", "sarif"
Render(w io.Writer, d Data) error
}
// Data is everything a reporter needs to render a scan.
type Data struct {
Release saga.Release
Run engine.Result
Verdict norn.Result
MinPriority string
}
// report.Build renders Data in a format and returns it as an Artifact (bytes + filename +
// content type) — the unit a Publisher delivers.
type Artifact struct {
Format string // e.g. "sarif"
Filename string // e.g. "results.sarif"
ContentType string // e.g. "application/sarif+json"
Bytes []byte
}Delivers rendered reports to a destination — the "where" of reporting, separate from the Reporter
(the "what"). Lives in pkg/publish; configured from the Saga's
config.publishers. Built-in kinds: file, github (uploads the SARIF report to code
scanning), and github-pr-comment (posts the markdown report as a sticky PR comment). Every
rendered report is delivered to every configured publisher, so a publisher takes only the
artifacts it needs.
type Publisher interface {
Kind() string // config selector, e.g. "file"
Publish(ctx context.Context, artifacts []report.Artifact) error
}- Built-in: implement the interface directly, compiled into the core.
- gRPC (go-plugin): the same interfaces exposed over a versioned gRPC contract; the plugin runs as a subprocess. Language-agnostic, isolated, crash-contained.
- Tool adapter (declarative): a manifest (invoke this CLI/container with these args;
its stdout is SARIF, or map fields X→SARIF) that the runtime presents as a
Scanner. No code; covers the majority of existing tools and BYO-tool.
- SARIF is the only result currency. Adapters/plugins that don't emit SARIF natively must map to it at the boundary.
- Config is schema-validated up front; the JSON Schema also powers the config wizard.
- Determinism & cacheability: given the same target identity + version + config, a scanner should produce equivalent findings so cache keys are meaningful.
- Least privilege: plugins declare the credentials/network they need; nothing implicit.