Public scan API: submit SBOM or image:tag@sha256:digest, get expandable results #23
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Motstandskraft/spam#23
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
A machine-facing API so CI/CD pipelines can submit a scan target and get a result back, without going through the human UI/OIDC flow. Two submission shapes:
image:tag@sha256:digest→ SPAM scans (or returns cached results for that digest).The response must be expandable so we can later attach SPAM's triage recommendation (tier, threat/trust score, advisory narrative, suppression state) to the same payload.
What already exists (reuse, don't rebuild)
artifacts.StoreSBOM()→SBOM(content-hash dedup) +SBOMBinding((AssetType, AssetRefID), typesREPO_COMMIT/IMAGE_DIGEST) —api/internal/artifacts/models.go.ImageDigestkeyed by(Registry, Repository, Digest)—api/internal/assets/models.go:76. Theimage:tag@sha256:digestform maps cleanly onto this.SBOM_SCAN,IMAGE_SCAN,SBOM_ADHOC_SCANjob types (api/internal/jobs/types.go) run as scanner Deployments; results upserted asImageVulnFinding/ unified vuln views./api/sbom-scan/image-result,/api/sbom-scan/repo-result,/api/image-scan/results. These are the internal plumbing, NOT the public API — the new endpoints sit in front and enqueue jobs.asset_risktable +Tier()/ThreatScore()/TrustScore()inapi/internal/assetrisk/score.go; advisory narratives inasset_advisories.Design — public scan API
New router group
/api/v1/scan(versioned, distinct from UI/api/*), ininternal/uiapior a newinternal/publicapipackage.Auth
OIDC sessions are for humans; CI needs service tokens. Proposal: new
api_tokentable (scoped, hashed, prefix-identifiable likespam_pat_…), reusing the AES-GCMsecretspackage for any stored secret material. Tokens carry an ACL scope (which repos/images they may submit/read) consistent withacl.Provider. Admin-issued via/api/admin/....Submit
POST /api/v1/scan/sbom— body: SBOM document (+ optionalassethint{type, ref});Content-Typeindicates format. →StoreSBOM, create binding, enqueueSBOM_SCAN, return{scan_id, status: "queued", self}.POST /api/v1/scan/image— body:{"image": "registry/repo:tag@sha256:..."}. Parse → upsertImageDigest, enqueueIMAGE_SCAN. If a recent scan exists for that digest, short-circuit and return cached results (configurable freshness).Poll / fetch result
GET /api/v1/scan/{scan_id}—{status: queued|running|succeeded|failed, ...}.The
triage/advisory/suppressionskeys are the expandability contract: reserved now, filled once we wireassetrisk+ advisory + the suppression epic into the response. Gate population behind?include=triage,advisory.Sync convenience
POST /api/v1/scan/image?wait=30slong-poll that blocks until done or timeout, for simple pipeline steps. Default is async submit + poll (multi-replica friendly — job + DB, no in-memory wait state).Edge cases
tagalone is mutable; cache/identity keys on@sha256:digest. If only a tag is given, reject or resolve-then-record the digest.registrycredsfor pull auth; clear error if SPAM can't pull.ImageDigest, dedup scans within freshness window; support anIdempotency-Key.schema_version; additive-only changes for the expansion slots.Tasks
POST /api/v1/scan/sbom+POST /api/v1/scan/image(parseimage:tag@sha256:digest)GET /api/v1/scan/{id}with versioned result envelope + reserved expansion slots?wait=long-polltriage/advisory/suppressionsviaassetrisk+ advisory + suppression epic