mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-09-16 07:42:05 -04:00
Runs fetched artifacts through pluggable external scanners after they're staged in storage but before they're committed to the cache DB, so a block verdict deletes the object instead of ever exposing it to a client. Scanners pull the staged bytes themselves via a short-lived HMAC-signed internal route rather than the proxy pushing bytes to them, keeping the mechanism storage-backend-agnostic and avoiding uploading potentially huge artifacts through the proxy's own egress. Hardening baked in from the start: the internal scan-fetch route is gated both at router-mount time and in the handler so it's inert whenever scanning is disabled or unsigned; the signing key is mandatory whenever scanning is enabled, enforced directly in scanner.NewGroup rather than relying on callers to invoke config validation; the scan call and the delete-on-block cleanup both run on a context detached from the client's, so a client disconnecting mid-scan can't be mistaken for a scanner failure, doesn't cause a legitimate artifact to be deleted, and doesn't leave a genuinely blocked artifact's bytes orphaned in storage; and scanner infrastructure errors (connection failures, internal hostnames) are never forwarded verbatim to anonymous clients, only a generic message. The scan-error metric also correctly distinguishes a scanner's own timeout from being cancelled because a sibling scanner already decided the verdict. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
65 lines
2.4 KiB
Go
65 lines
2.4 KiB
Go
// 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.
|
|
package scanner
|
|
|
|
import "context"
|
|
|
|
// Request describes a staged artifact awaiting a scan verdict.
|
|
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"`
|
|
}
|
|
|
|
// Finding describes a single issue reported by a scanner.
|
|
type Finding struct {
|
|
Severity string
|
|
Title string
|
|
Description string
|
|
}
|
|
|
|
// Result is a scanner's verdict for a Request.
|
|
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
|
|
}
|
|
|
|
// Scanner is the extension point for pluggable pre-cache scanning.
|
|
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)
|
|
}
|