scanner

package
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: GPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package scanner provides pluggable pre-cache artifact scanning.

A Scanner inspects an artifact staged in the proxy's own storage before it becomes visible to clients, and returns a verdict on whether it may be cached. The proxy never uploads artifact bytes to a scanner directly: it hands the scanner a short-lived signed URL and the scanner pulls the bytes itself. See HTTPScanner for the built-in adapter that implements this over a small HTTP/JSON contract, letting trivy, ClamAV, Wiz, or any custom service integrate without the proxy needing built-in knowledge of any specific tool.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Finding

type Finding struct {
	Severity    string
	Title       string
	Description string
}

Finding describes a single issue reported by a scanner.

type Group

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

Group runs a set of configured Scanners concurrently and turns their individual verdicts into a single decision.

func NewGroup

func NewGroup(cfg config.ScanningConfig, logger *slog.Logger) (*Group, error)

NewGroup builds a Group from cfg. If cfg.Enabled is false, the returned Group has no entries and Enabled() reports false, so callers can skip the scan path entirely.

func (*Group) Enabled

func (g *Group) Enabled() bool

Enabled reports whether any scanner is configured.

func (*Group) Scan

func (g *Group) Scan(ctx context.Context, req Request) Result

Scan runs every scanner applicable to req.Ecosystem concurrently, never sequentially, and returns a single decision.

The moment any "block" mode scanner reports Allowed: false (or errors, unless FailOpen is set), Scan cancels a context shared by every goroutine: in-flight calls to the other scanners are aborted rather than waited out, since a single block already decides the outcome. Scan still waits for all goroutines to observe that cancellation and return before it itself returns, so no scan call outlives this method call.

If nothing blocks, Scan waits for every "block" mode scanner to finish before reporting Allowed: true — an allow decision can't be finalized until all of them have answered. "monitor" mode scanners never gate the wait or trigger cancellation: a monitor verdict of Allowed: false is logged and folded into Result.Findings, but never blocks.

func (*Group) Timeout

func (g *Group) Timeout() time.Duration

Timeout returns the per-scan-call timeout used to bound the signed fetch URL's validity.

type HTTPScanner

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

HTTPScanner adapts an external HTTP scanning service to the Scanner interface. It POSTs a small JSON notification describing the staged artifact, including a signed fetch URL; the external service is responsible for GETting that URL itself, running the real scan against those bytes, and replying with a verdict before the request's deadline.

Request body:

{
  "ecosystem": "npm", "name": "left-pad", "version": "1.0.0",
  "filename": "left-pad-1.0.0.tgz", "purl": "pkg:npm/left-pad@1.0.0",
  "content_type": "application/octet-stream", "size": 1234,
  "fetch_url": "https://proxy.internal/_internal/scan-fetch?..."
}

Response body:

{
  "allowed": true, "reason": "",
  "findings": [{"severity": "high", "title": "...", "description": "..."}]
}

Any compliant adapter — a trivy wrapper, a clamav-rest bridge, a Wiz connector, or an in-house service — need only implement this contract.

func NewHTTPScanner

func NewHTTPScanner(name, url string, headers map[string]string, client *http.Client) *HTTPScanner

NewHTTPScanner creates an HTTPScanner named name that notifies url of staged artifacts, attaching headers to every request (e.g. for auth). If client is nil, http.DefaultClient is used.

func (*HTTPScanner) Name

func (s *HTTPScanner) Name() string

Name returns the scanner's configured name.

func (*HTTPScanner) Scan

func (s *HTTPScanner) Scan(ctx context.Context, req Request) (Result, error)

Scan notifies the configured URL of req and waits for a verdict.

type Request

type Request struct {
	Ecosystem   string `json:"ecosystem"`
	Name        string `json:"name"`
	Version     string `json:"version"`
	Filename    string `json:"filename"`
	PURL        string `json:"purl"`
	ContentType string `json:"content_type"`
	Size        int64  `json:"size"`

	// FetchURL is a short-lived signed URL the scanner must GET itself to
	// retrieve the exact bytes staged in the proxy's storage.
	FetchURL string `json:"fetch_url"`
}

Request describes a staged artifact awaiting a scan verdict.

type Result

type Result struct {
	Allowed  bool
	Reason   string
	Findings []Finding

	// ScannerName identifies which scanner produced this result. Set by
	// Group, not by individual Scanner implementations.
	ScannerName string

	// InfraError reports whether Allowed: false was forced by a scanner
	// call failing (network error, timeout, bad response) rather than an
	// actual verdict from the scanner. Set by Group. Callers that surface
	// Reason to untrusted clients must not do so when this is true: it may
	// contain raw connection errors (internal hostnames, ports) instead of
	// a verdict meant to be shown outside the proxy.
	InfraError bool
}

Result is a scanner's verdict for a Request.

type Scanner

type Scanner interface {
	// Name identifies this scanner in logs and metrics.
	Name() string

	// Scan requests a verdict for req. Implementations must respect ctx
	// cancellation: Group cancels in-flight scans once a blocking verdict
	// has already been decided by another scanner.
	Scan(ctx context.Context, req Request) (Result, error)
}

Scanner is the extension point for pluggable pre-cache scanning.

Jump to

Keyboard shortcuts

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