bug : Image exceptions silently do nothing for archive scans
Description
Ran into this while gating CI on tarball scans: kubescape scan image ./app.tar --exceptions exc.json succeeds, but none of my exceptions are applied. No error, no warning — just a clean-looking report.
From what I can tell, exception matching only understands registry-style references (reference.ParseNormalizedNamed in getAttributesFromImage, core/core/image_scan.go on master as of Sep 2026). Anything like docker-archive: / oci-archive: / oci-dir: / dir: / sbom: or a bare local path fails to parse, the error gets logged and swallowed, and matching runs against empty Attributes{}. Since empty-pattern regexStringMatch("", x) is true, my empty-target policies over-match and my specific ones under-match. Both silently.
Environment
OS: Ubuntu 22.04 LTS
Version: v3.0.x (built from commit 153e9d9f)
Steps To Reproduce
- Write an exceptions file for a CVE you know is in the image.
kubescape scan image quay.io/kubescape/kubescape-cli:v3.0.0 --exceptions /tmp/exc.json -v— exception honored.- Save the same image to
./app.tar, runkubescape scan image ./app.tar --exceptions /tmp/exc.json -v— success, exception never applied, exit 0. Same withdocker-archive:./app.tar. - Mixed
kubescape scan image ./app.tar nginx:1.27 --exceptions /tmp/exc.json— nginx fine, tarball silently skipped.
Expected behavior
Fail loudly per image instead of silently: report non-registry + --exceptions as a per-image Image Exceptions/Unsupported Input error (name the input, the detected scheme, and the remedies), still scan registry siblings, keep exit non-zero. If every image is non-registry, fail before the Grype DB download. Registry repos that happen to end in .tar (e.g. team/my.tar:v1) must keep working.
Actual Behavior
Exit 0 with a full report; the tarball half never had exceptions evaluated. My gate was inert and I didn't know it.
Additional context
Couldn't find an open/closed issue or PR covering this (searched archive + exception + docker-archive; closest were #3682, #3353, #3233 — all different). Nothing assigned.
Proposed approach:
- One classifier
classifyImageInput(img, stat)— case-insensitive scheme set (docker-archive|oci-archive|oci-dir|oci-layout|dir|file|sbom), tar-suffix only when the path exists or is unambiguously a file path (absolute /./-relative, so taggedteam/my.tar:v1stays registry), bare existing local paths via injectable stat,ParseNormalizedNamedas fallback. Empty input gets its own error. getUniqueVulnerabilitiesAndSeverities→([],[],error)— error on policies + non-registry with messageimage exceptions cannot target non-registry input %q (detected %q): scan by registry reference or remove --exceptions, including the exceptions file path. No empty-attributes fallback.- Per-job
ExceptionErronImageScanJob— one archive never poisons registry siblings; worker checks it before platform/mapping/scan; newErrCategoryExceptionUnsupportedso dashboards can separate this from real failures. ScanImageContextshort-circuits all-archive + policies before DB load; mixed scans return sibling report + joined error (thresholds over succeeded scans only).- Tests: classifier table (injectable stat), archive+policy error, archive+nil ok, mixed-jobs test, threshold (false, err) pin; existing suites unmodified. Docs: 2-line note in
scan image --help+ exceptions README.
Happy to implement it in that shape if maintainers agree with fail-per-image over fail-whole-scan...! :)
Source: kubescape/kubescape