Documentation
How DevRadar works and how to feed it. Start with the concepts, then submit your first SBOM — DevRadar tracks an image by rescanning its SBOM over time and never pulls the image itself.
How DevRadar works
DevRadar tracks how a container image's vulnerabilities change over time by rescanning its SBOM — the software bill of materials your CI already produces — rather than pulling the image. Scanning the SBOM instead of the image removes registries, credentials, rate limits, and egress from the picture, and it means DevRadar can cover private-registry images: the SBOM crosses the trust boundary, not your registry credentials.
- Submit — you POST an SBOM (pinned to an image digest) through an authenticated API. DevRadar validates it, extracts the subject digest, and stores it immutably.
- Rescan — a job re-runs Grype and Trivy against each due SBOM on a schedule (every ~15 min; each SBOM at most about twice a day), normalizes both scanners into one scanner-agnostic schema, and records every change.
- Track change — DevRadar keeps current findings plus an append-only change log. The product is the delta over time, not a point-in-time scan.
Because an SBOM is immutable once submitted, the only variable across daily scans is the scanner's vulnerability database. That gives clean causality: a new finding on an unchanged digest is database-driven, while a finding that requires a new digest is image-driven. A scanner upgrade is tagged tooling so it never looks like a real regression.
Is SBOM scanning as accurate as image scanning?
Scanning splits into matching (packages → CVEs) and cataloging (image → packages). Matching is identical whether the input is an SBOM or an image — same matcher, same database — so accuracy comes down to how good the SBOM's generator was at cataloging, which is not a property of SBOM-vs-image at all.
- An all-layers SBOM from the scanner's own cataloger family has zero gap (Syft → Grype is bit-identical).
- Cross-tool (Syft → Trivy) has a small, bidirectional gap — which is exactly why DevRadar runs both: divergence is a useful cataloger-disagreement signal.
- Static binaries and unmanifested vendored dependencies are a real but shared weakness — direct image scanning is barely better.
DevRadar caveat: cataloging is frozen per digest (a newer cataloger's finds arrive only with a new digest), while matching stays live daily. That is the trade that buys clean change causality. Recommendation: Syft-generated CycloneDX, all layers.
Reading your results
Findings are filtered by a severity threshold (your account default is medium; override per request with ?min_severity=). Where to look:
- Images — fleet rollup: one row per tracked image with a severity breakdown, totals, and how much is fixable now.
- Work — a deterministic remediation queue ordered by known exploitation (KEV), fix availability, severity, EPSS, then blast radius. Every reason stays visible.
- Trends — posture over time, so you can see debt rising or falling.
- CVEs — every tracked vulnerability across your fleet, with the images it touches.
- Alerts — new KEV, newly-fixable, and posture regressions. Alerts filter to image and database causes so a scanner upgrade never pages you.
Use the Digest comparison view (from any image page) to see exactly what changed between two immutable digests — added, resolved, and re-rated vulnerabilities, plus package and license deltas.
Licenses & compliance
Every SBOM already carries a per-package license, so DevRadar captures it at ingest — no extra scan — into a frozen per-digest inventory. Licenses are classified into an obligation taxonomy (permissive, weak copyleft, strong copyleft, proprietary, unknown) and evaluated against an opt-in per-account policy.
The Licenses page shows the fleet distribution by category and family, and a policy editor. Policy is descriptive by default: with nothing denied it just reports. Deny a category (with per-license allow/deny exceptions) and DevRadar flags violating packages instantly — evaluation is live, so a policy change re-flags without touching stored data. Classification runs in Go over the raw stored IDs, so multi-license sets and SPDX OR/AND expressions are preserved and evaluated faithfully.
Trust & attestation
DevRadar is trust-on-submission. It always guarantees determinism (same SBOM + same scanner DB → same findings) and clean change causality. It does not guarantee, by default, that an SBOM faithfully represents its claimed digest — a wrong or stale SBOM yields wrong results, and that's on the submitter.
You can optionally attach a sigstore/cosign attestation on submission. When the service has a trust policy configured, DevRadar verifies the signature and binds it to the subject digest, moving the SBOM from unverified to verified. Verification is additive and never blocks ingest: a failed or unconfigured check never rejects a submission and never changes findings. A verified SBOM means the attestation checked out — not that the image is safe.
Submit with the CLI (recommended)
The easiest way to submit is the devradarctl CLI, which handles digest resolution, all-layers SBOM generation, and upload in one command. Prefer to do it by hand? The manual steps are below.
Recommended: the devradarctl CLI
easiestdevradarctl resolves the image's manifest digest, generates an all-layers CycloneDX SBOM, and submits it — one step, no base64 or curl plumbing.
1. Install
Homebrew (macOS/Linux):
brew install thingzio/tap/devradarctl
Or with Go:
go install github.com/thingzio/devradarctl@latest
Or grab a prebuilt binary from the releases page. Submitting from an image needs syft on PATH; digest resolution is built in (no crane needed) and uses your ambient Docker credentials.
2. Submit an image
Use an API token issued by an account admin, then:
export DEVRADAR_TOKEN=dr_xxxxxxxxxxxxxxxxxxxxxxxx # resolve digest + generate all-layers SBOM + submit, in one step devradarctl submit --image alpine:3.20 --label team-x --label prod
The token can also be piped via stdin (echo "$DEVRADAR_TOKEN" | devradarctl submit …). --label is repeatable (grouping labels); --tag overrides the recorded image version.
Already have an SBOM file?
Submit it directly — no syft needed. Pass --image-ref so it's pinned to the right digest:
devradarctl submit --file alpine.cdx.json --image-ref alpine@sha256:…
Prove authenticity (optional)
Attach a sigstore/cosign attestation and — when the service has a trust policy configured — DevRadar verifies its signature and that its subject matches this SBOM's image digest, moving the SBOM from unverified to verified. Verification is optional and never blocks a submission; without it, DevRadar still guarantees deterministic, reproducible findings.
The --attestation flag takes a path to a sigstore bundle. Many public images already ship keyless attestations you can pull with cosign. For example, the cosign image itself publishes an SBOM attestation:
# download the SBOM attestation bundle for a real, publicly-attested image IMAGE=ghcr.io/sigstore/cosign/cosign:v2.4.1 cosign download attestation --predicate-type https://spdx.dev/Document \ "$IMAGE" > cosign.att.jsonl # submit the image with its attestation; DevRadar verifies the binding devradarctl submit --image "$IMAGE" --attestation cosign.att.jsonl
For your own images, sign an SBOM attestation in CI (e.g. keyless via GitHub Actions OIDC: cosign attest --predicate sbom.cdx.json --type cyclonedx <image>), export the bundle, and pass it to --attestation. Full flag/env reference: the devradarctl README.
Manual submission
Prefer to drive it yourself, or can't install the CLI? Generate the SBOM and POST it directly. Four steps, copy-paste ready.
Obtain an API token
CI submits SBOMs with a dr_ bearer token. Ask an account admin to issue one; it is shown only once when created. Then export it in your shell:
export DR_TOKEN=dr_xxxxxxxxxxxxxxxxxxxxxxxx export DR_BASE_URL=https://devradar.thingz.io
Install the tools (once)
You need syft to generate the SBOM and crane to resolve the image's manifest digest. Skip this if they're already on PATH (many CI images ship them).
# macOS (Homebrew) brew install syft crane # Linux — syft has an install script; crane installs via Go (or a release tarball) curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin go install github.com/google/go-containerregistry/cmd/crane@latest
Generate the SBOM (by digest, all layers)
Two rules give DevRadar an accurate, correctly-pinned SBOM: generate by digest (not by tag — DevRadar pins the inventory to the manifest digest), and use --scope all-layers so packages in every layer are catalogued.
# pick your image and resolve its digest
IMAGE=alpine:3.19
DIGEST=$(crane digest "$IMAGE")
# CycloneDX, all layers (recommended — grype and trivy both read it cleanly).
# No -q here: if the reference isn't a container image, you want to SEE the error.
syft --scope all-layers -o cyclonedx-json=sbom.cdx.json \
"registry:${IMAGE%%:*}@$DIGEST"
# sanity check: the SBOM should be non-empty and list components
test -s sbom.cdx.json && echo "OK: $(wc -c < sbom.cdx.json) bytes" || echo "FAILED: empty SBOM"
unsupported layer media type / not an OCI model artifact, the reference points at something that isn't an image (a Helm chart's config media type is helm.config.v1+json); use the actual application image instead.
image@sha256:…, no :tag). Given the combined tag@digest form, syft records the tag and drops the digest — and DevRadar rejects an SBOM with no digest. If your tool can't embed a digest, pass it explicitly with image_ref in step 4.
Submit it
Base64-encode the SBOM and wrap it in a small JSON body. Writing the body to a file avoids the shell's argument-length limit (real SBOMs are multiple MB); the API accepts up to 20 MiB decoded. Native base64 — no extra tooling — works on macOS and Linux:
# guard: refuse to submit an empty/missing SBOM (catches a failed generate step)
test -s sbom.cdx.json || { echo "sbom.cdx.json is empty — re-run step 3"; exit 1; }
# base64-encode the SBOM (strip newlines) and build the request body
B64=$(base64 < sbom.cdx.json | tr -d '\n')
printf '{"sbom":"%s"}' "$B64" > body.json
# POST it
curl -sS -X POST "$DR_BASE_URL/v1/sboms" \
-H "Authorization: Bearer $DR_TOKEN" \
-H "Content-Type: application/json" \
--data @body.json
# -> 202 {"sbom_id":"…","digest":"sha256:…","existing":false}
The request body accepts a few optional fields alongside sbom:
image_ref— the digest-pinned reference (repo@sha256:…). Required only when the SBOM has no embedded digest (generated by tag); otherwise DevRadar reads the digest from the SBOM.version— the image tag (e.g.v1.20.2) shown in the UI when you pin by digest. Optional.labels— free-form grouping labels for filtering images on the dashboard (e.g.team-x,prod). Optional.attestation— a base64 sigstore/cosign bundle. When supplied (and the service has a trust policy configured), DevRadar verifies it against this SBOM's subject digest and reportsverification_statusin the response. Optional; a failed check never rejects the submission.
# same base64 as above, now with all optional fields
B64=$(base64 < sbom.cdx.json | tr -d '\n')
printf '{"sbom":"%s","image_ref":"%s","version":"%s","labels":["team-x","prod"]}' \
"$B64" "${IMAGE%%:*}@$DIGEST" "v1.20.2" > body.json
curl -sS -X POST "$DR_BASE_URL/v1/sboms" \
-H "Authorization: Bearer $DR_TOKEN" \
-H "Content-Type: application/json" \
--data @body.json
That's it
The scan job runs on a schedule (every ~15 min) and picks up new SBOMs automatically — no manual trigger needed. License data is captured immediately at submission; vulnerability findings appear on Images and licenses on Licenses after the next scan completes. Re-submitting the same image after a rebuild (new digest) tracks the change over time.
Reading results back? See the full REST API reference →
FAQ
Does DevRadar pull my images?
No. DevRadar only ever scans the SBOM you submit — it never pulls the image, needs registry credentials, or makes egress calls to your registry. That's what lets it cover private-registry images.
How often are my SBOMs rescanned?
The scan job runs every ~15 minutes and picks up any SBOM that's never been scanned or was last scanned more than the staleness window (default 12h) ago. So a freshly-submitted SBOM is scanned within a tick, while any given SBOM is rescanned at most about twice a day.
Why do Grype and Trivy sometimes disagree?
Running two scanners is deliberate. Matching (package → CVE) is the same regardless of input; divergence between the two almost always reflects a cataloging disagreement in how each tool reads the SBOM. Seeing both keeps the normalized schema honest and surfaces that signal.
My SBOM was rejected — what went wrong?
The most common cause is a missing subject digest. Generate the SBOM by digest (image@sha256:…, not :tag), or pass image_ref explicitly. DevRadar also rejects references that aren't container images (Helm charts, OCI artifacts) and enforces a 20 MiB decoded body cap.
Why is a package's license "unknown"?
Either the SBOM declared no license for it, or the declared value isn't a recognizable SPDX ID (a LicenseRef-* placeholder, NOASSERTION, or a value a generator wrote incorrectly). DevRadar fails visible here — it never silently treats an unresolved license as permissive.
What does a "verified" SBOM guarantee?
Only that an attached sigstore/cosign attestation checked out and binds to the subject digest. It does not mean the image is safe. Verification is optional and never blocks ingest — see Trust & attestation.