Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-09-16 15:52:05 -04:00
pkg-proxy/internal/scanner/scanner.go

65 lines
2.4 KiB
Go
Raw Permalink Normal View History

Add pre-cache artifact scanning hook (trivy/ClamAV/Wiz/custom) (#298) 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>
2026-09-03 05:06:44 -04:00
// 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)
}