Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package grype for openSUSE:Factory checked in at 2026-10-07 19:33:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/grype (Old) and /work/SRC/openSUSE:Factory/.grype.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "grype" Wed Oct 7 19:33:24 2026 rev:131 rq:1382905 version:0.120.1 Changes: -------- --- /work/SRC/openSUSE:Factory/grype/grype.changes 2026-10-05 13:03:19.435096620 +0200 +++ /work/SRC/openSUSE:Factory/.grype.new.3698736/grype.changes 2026-10-07 19:33:57.247020498 +0200 @@ -1,0 +2,25 @@ +Wed Oct 07 05:01:48 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.120.1: + * Bug Fixes + - run unit tests on windows and fix the handle leaks it + surfaced [PR #3766] + - Table presenter appends a spurious ", ..." to the FIX column + when all fix versions are already shown [Issue #3760] [PR + #3770] + * Dependencies + 9 dependency changes (9 updated). + - Updated (9 packages) + - github.com/anchore/go-rpmdb v0.2.1 → v0.2.2 + - github.com/anchore/stereoscope v0.3.3 → v0.3.4 + - github.com/anchore/syft v1.54.0 → v1.54.1 + - github.com/docker/cli v29.8.1+incompatible → + v29.8.2+incompatible + - github.com/klauspost/compress v1.20.0 → v1.20.1 + - github.com/secure-systems-lab/go-securesystemslib v0.11.0 → + v0.11.1 + - github.com/sigstore/protobuf-specs v0.5.1 → v0.5.2 + - github.com/sigstore/sigstore v1.10.8 → v1.10.11 + - github.com/sylabs/sif/v2 v2.24.1 → v2.24.2 + +------------------------------------------------------------------- Old: ---- grype-0.120.0.obscpio New: ---- grype-0.120.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ grype.spec ++++++ --- /var/tmp/diff_new_pack.MDryGX/_old 2026-10-07 19:33:59.670121692 +0200 +++ /var/tmp/diff_new_pack.MDryGX/_new 2026-10-07 19:33:59.671121734 +0200 @@ -17,7 +17,7 @@ Name: grype -Version: 0.120.0 +Version: 0.120.1 Release: 0 Summary: A vulnerability scanner for container images and filesystems License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.MDryGX/_old 2026-10-07 19:33:59.716123613 +0200 +++ /var/tmp/diff_new_pack.MDryGX/_new 2026-10-07 19:33:59.720123780 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/anchore/grype.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/v0.120.0</param> + <param name="revision">refs/tags/v0.120.1</param> <param name="match-tag">v*</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.MDryGX/_old 2026-10-07 19:33:59.765125659 +0200 +++ /var/tmp/diff_new_pack.MDryGX/_new 2026-10-07 19:33:59.770125868 +0200 @@ -3,6 +3,6 @@ <param name="url">https://github.com/anchore/grype</param> <param name="changesrevision">fa8b7e2a528cf1f8b098123f256c61db9e5df69c</param></service><service name="tar_scm"> <param name="url">https://github.com/anchore/grype.git</param> - <param name="changesrevision">10fbc043ecbaf8436daa9efac9f1632557caf277</param></service></servicedata> + <param name="changesrevision">6f8d854af29d3a3086b11a84afa51554a2a245fe</param></service></servicedata> (No newline at EOF) ++++++ grype-0.120.0.obscpio -> grype-0.120.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/.golangci.yaml new/grype-0.120.1/.golangci.yaml --- old/grype-0.120.0/.golangci.yaml 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/.golangci.yaml 2026-10-06 18:42:08.000000000 +0200 @@ -1,4 +1,9 @@ version: "2" +run: + # keep in sync with buildTags in .make/main.go + build-tags: + - containers_image_openpgp + - exclude_graphdriver_btrfs linters: # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint default: none diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/.goreleaser.yaml new/grype-0.120.1/.goreleaser.yaml --- old/grype-0.120.0/.goreleaser.yaml 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/.goreleaser.yaml 2026-10-06 18:42:08.000000000 +0200 @@ -13,6 +13,7 @@ - id: linux-build dir: ./cmd/grype binary: grype + tags: [containers_image_openpgp, exclude_graphdriver_btrfs] goos: - linux goarch: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/.make/main.go new/grype-0.120.1/.make/main.go --- old/grype-0.120.0/.make/main.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/.make/main.go 2026-10-06 18:42:08.000000000 +0200 @@ -3,9 +3,12 @@ import ( "fmt" "os" + "path/filepath" + "runtime" "strings" . "github.com/anchore/go-make" + "github.com/anchore/go-make/config" "github.com/anchore/go-make/file" "github.com/anchore/go-make/lang" "github.com/anchore/go-make/log" @@ -20,6 +23,12 @@ snapshotBn = "snapshot" // goreleaser dist dir ) +// buildTags must match the linux build in .goreleaser.yaml so tests and lint compile the same code that ships +// (containers_image_openpgp compiles in stereoscope's real containers-storage provider instead of its stub). +// exclude_graphdriver_btrfs drops the cgo-only btrfs driver, which needs libbtrfs headers whenever cgo is on (e.g. +// under -race); release builds are CGO_ENABLED=0 and never include it anyway. +const buildTags = "containers_image_openpgp,exclude_graphdriver_btrfs" + func main() { Makefile( golint.Tasks(golint.SkipTests()), @@ -27,12 +36,14 @@ gotest.CoverageThreshold(47), // excludes both anchore/grype/test/** and anchore/grype/internal/test/** gotest.ExcludeGlob("**/test/**"), + gotest.Tags(buildTags), ), goreleaser.Tasks(), buildTask(), integrationTask(), cliTask(), + containersStorageTestTask(), qualityTask(), generateTask(), @@ -86,12 +97,12 @@ Description: "run integration tests", RunsOn: lang.List("test"), Run: func() { - Run(`go test -v ./test/integration`) + Run(`go test -v -tags=` + buildTags + ` ./test/integration`) // update database outside race detector, since doing so with // race detector on is very slow Run(fmt.Sprintf(`go run ./cmd/%s db update`, project)) // exercise most of the CLI with the data race detector enabled - Run(fmt.Sprintf(`go run -race ./cmd/%s alpine:latest`, project)) + Run(fmt.Sprintf(`go run -race -tags=%s ./cmd/%s alpine:latest`, buildTags, project)) }, } } @@ -127,6 +138,39 @@ Run(`go run -C .make . snapshot:single-target`) } +// containersStorageTestTask builds a fixture image with BUILDER (podman or buildah) into an isolated local store and +// scans it with the linux snapshot binary. Linux only, and not hooked into "test" since it needs the builder installed. +func containersStorageTestTask() Task { + return Task{ + Name: "containers-storage-test", + Description: "run containers-storage tests (BUILDER=podman|buildah)", + Run: func() { + bin := linuxSnapshotBinPath() + if !file.Exists(bin) { + log.Info("snapshot binary not found at %s; building single-target snapshot", bin) + Run(`go run -C .make . build`) + } + Run( + "bash test/containers-storage/source-test.sh "+config.Env("BUILDER", "podman"), + run.Env("GRYPE_BINARY_LOCATION", bin), + ) + }, + } +} + +// linuxSnapshotBinPath returns the absolute path to the linux snapshot binary for the host arch, mirroring +// goreleaser's <build id>_<goos>_<goarch><microarch level> dist dir naming (see test/cli/utils_test.go). +func linuxSnapshotBinPath() string { + arch := runtime.GOARCH + switch arch { + case "amd64": + arch += "_v1" + case "arm64": + arch += "_v8.0" + } + return filepath.Join(RootDir(), snapshotBn, "linux-build_linux_"+arch, project) +} + func qualityTask() Task { return Task{ Name: "quality", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/README.md new/grype-0.120.1/README.md --- old/grype-0.120.0/README.md 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/README.md 2026-10-06 18:42:08.000000000 +0200 @@ -53,6 +53,19 @@ grype ./my-project ``` +### Local containers-storage images + +On Linux, grype can scan images from a local containers-storage store (e.g. built with Buildah or Podman) when asked explicitly; plain image references never look there: + +```bash +grype --from containers-storage localhost/myimage:latest + +# rootless stores must be read from inside the builder's user namespace +podman unshare grype --from containers-storage localhost/myimage:latest +``` + +This is included in the Linux release binaries. When building from source, add `-tags containers_image_openpgp,exclude_graphdriver_btrfs`. + Scan an SBOM for even faster vulnerability detection: ```bash diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/cmd/grype/cli/options/grype.go new/grype-0.120.1/cmd/grype/cli/options/grype.go --- old/grype-0.120.0/cmd/grype/cli/options/grype.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/cmd/grype/cli/options/grype.go 2026-10-06 18:42:08.000000000 +0200 @@ -157,7 +157,7 @@ flags.StringArrayVarP(&o.From, "from", "", - "specify the source behavior to use (e.g. docker, registry, podman, oci-dir, ...)", + "specify the source behavior to use (e.g. docker, registry, podman, containers-storage, oci-dir, ...)", ) flags.StringArrayVarP(&o.VexDocuments, @@ -181,7 +181,7 @@ func (o *Grype) DescribeFields(descriptions clio.FieldDescriptionSet) { descriptions.Add(&o.CheckForAppUpdate, `enable/disable checking for application updates on startup`) descriptions.Add(&o.DefaultImagePullSource, `allows users to specify which image source should be used to generate the sbom -valid values are: registry, docker, podman`) +valid values are: registry, docker, podman, containers-storage`) descriptions.Add(&o.Name, `same as --name; set the name of the target being analyzed`) descriptions.Add(&o.Exclusions, `a list of globs to exclude from scanning, for example: - '/etc/**' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/go.mod new/grype-0.120.1/go.mod --- old/grype-0.120.0/go.mod 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/go.mod 2026-10-06 18:42:08.000000000 +0200 @@ -17,7 +17,7 @@ github.com/anchore/go-logger v0.2.0 github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 github.com/anchore/packageurl-go v0.2.0 - github.com/anchore/stereoscope v0.3.3 + github.com/anchore/stereoscope v0.3.4 github.com/aquasecurity/go-pep440-version v0.0.1 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/bitnami/go-version v0.0.0-20250505154626-452e8c5ee607 @@ -47,7 +47,7 @@ github.com/iancoleman/strcase v0.3.0 github.com/invopop/jsonschema v0.14.0 github.com/jinzhu/copier v0.4.0 - github.com/klauspost/compress v1.20.0 + github.com/klauspost/compress v1.20.1 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f github.com/knqyf263/go-deb-version v0.0.0-20241115132648-6f4aee6ccd23 github.com/masahiro331/go-mvn-version v0.0.0-20250131095131-f4974fa13b8a @@ -78,7 +78,7 @@ ) require ( - github.com/anchore/syft v1.54.0 + github.com/anchore/syft v1.54.1 github.com/bmatcuk/doublestar/v4 v4.10.2 github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 ) @@ -111,7 +111,7 @@ github.com/agext/levenshtein v1.2.3 // indirect github.com/anchore/go-lzo v0.1.1 // indirect github.com/anchore/go-macholibre v0.1.1 // indirect - github.com/anchore/go-rpmdb v0.2.1 // indirect + github.com/anchore/go-rpmdb v0.2.2 // indirect github.com/anchore/go-struct-converter v0.2.0-rc2 // indirect github.com/anchore/go-sync v0.1.2 // indirect github.com/andybalholm/brotli v1.2.0 // indirect @@ -177,7 +177,7 @@ github.com/deitch/magic v0.0.0-20240306090643-c67ab88f10cb // indirect github.com/diskfs/go-diskfs v1.9.4 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v29.8.1+incompatible // indirect + github.com/docker/cli v29.8.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.8 // indirect github.com/docker/go-connections v0.8.1 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -291,11 +291,11 @@ github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/sassoftware/go-rpmutils v0.4.0 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.11.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sigstore/fulcio v1.8.7 // indirect - github.com/sigstore/protobuf-specs v0.5.1 // indirect - github.com/sigstore/sigstore v1.10.8 // indirect + github.com/sigstore/protobuf-specs v0.5.2 // indirect + github.com/sigstore/sigstore v1.10.11 // indirect github.com/sirupsen/logrus v1.10.2 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect @@ -311,7 +311,7 @@ github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/sylabs/sif/v2 v2.24.1 // indirect + github.com/sylabs/sif/v2 v2.24.2 // indirect github.com/sylabs/squashfs v1.0.6 // indirect github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd // indirect github.com/tchap/go-patricia/v2 v2.3.3 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/go.sum new/grype-0.120.1/go.sum --- old/grype-0.120.0/go.sum 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/go.sum 2026-10-06 18:42:08.000000000 +0200 @@ -146,8 +146,8 @@ github.com/anchore/go-lzo v0.1.1/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk= github.com/anchore/go-macholibre v0.1.1 h1:sTBHi3S1WVIOCamc6NyT8Sn9mBmD30DCTfjfVm4k6/M= github.com/anchore/go-macholibre v0.1.1/go.mod h1:YNq2610RlvGCK0Za+Klz/OCMtUaEnwwAGA/VfUAQaro= -github.com/anchore/go-rpmdb v0.2.1 h1:BB+8usTkB1ajJ8i1xuzV7TGSTtjqn3/dDi1EuPsxO9w= -github.com/anchore/go-rpmdb v0.2.1/go.mod h1:Y64EbetdqhryMjtk8IzXkdCQw/aNDx+feZCT9aj6rjs= +github.com/anchore/go-rpmdb v0.2.2 h1:44VC7mvFAxV9t5JUZYg3S9sqG+E94iZvyWACMDq9hSk= +github.com/anchore/go-rpmdb v0.2.2/go.mod h1:Y64EbetdqhryMjtk8IzXkdCQw/aNDx+feZCT9aj6rjs= github.com/anchore/go-struct-converter v0.2.0-rc2 h1:q+859fW2/jbHJHB2etbNfRlFwYpknyvbqqk1hUdamQ4= github.com/anchore/go-struct-converter v0.2.0-rc2/go.mod h1:cDBA5vhcR62nXWo8QH9/Kk2807o65ISaHPNPX66L+Uw= github.com/anchore/go-sync v0.1.2 h1:RDD5RCanrWqhbtBulqPSZpERadUlgcYjpybYzYd3Uk8= @@ -156,10 +156,10 @@ github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.2.0 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk= github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE= -github.com/anchore/stereoscope v0.3.3 h1:sur8G2THAOnOrSXDjIc1SXzQwco8hjFBtGFq1jFvNVA= -github.com/anchore/stereoscope v0.3.3/go.mod h1:LxL4iOFtB8XMbA1MykUUTkmtQmDuYdbr4+TkBkCfljU= -github.com/anchore/syft v1.54.0 h1:8JEusKFXXHxezNpYQiUqx7KTpInkW284iGAQMEEqy3k= -github.com/anchore/syft v1.54.0/go.mod h1:wT/lRov8P7XmVNTuyCEgdgIakdLuxYjm+SWXDVvjecs= +github.com/anchore/stereoscope v0.3.4 h1:dT5AfjCTXUJowMiRIc+VBQVwvwnIEwnxxwA1GwAkMxw= +github.com/anchore/stereoscope v0.3.4/go.mod h1:+HxqHTt7dvH8mea0mf3ulqkf084VvHiT1grpAQf5uEs= +github.com/anchore/syft v1.54.1 h1:IdNS5Ha7WfatXHDL0qzjdf7DBVnfb8OI3FpSbltv0nc= +github.com/anchore/syft v1.54.1/go.mod h1:M3Nnl7wN05wWBbffETQ+P3TEhuCDuOUhfTD4tyNX4rA= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= @@ -364,8 +364,8 @@ github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/cli v29.8.1+incompatible h1:qYL1bCp6cRw2SB1xmLlIOPyV171dilw9W2Jew38vy9c= -github.com/docker/cli v29.8.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.8.2+incompatible h1:2zgdFuoFst2T80oS42vhKGxkd0JRo305eIEKTsxR7pQ= +github.com/docker/cli v29.8.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= @@ -695,8 +695,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= -github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -942,8 +942,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= -github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= -github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= +github.com/secure-systems-lab/go-securesystemslib v0.11.1 h1:ayahDPjfSIqKegyt5YVGEvQ7SAi72GHXTy+b2YjUd5w= +github.com/secure-systems-lab/go-securesystemslib v0.11.1/go.mod h1:UyOjhoZLi76ir63u1ptubEevVe6dlNPmNxwgVvKqFFY= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= @@ -953,10 +953,10 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sigstore/fulcio v1.8.7 h1:d7QJQxukgXarqPCT9uDNeyzt1Bbt/biw58/VfOnwI/o= github.com/sigstore/fulcio v1.8.7/go.mod h1:7fX+QyigZxRGKTgHvTfOfxwwniCDxK02PVloHy5/vg4= -github.com/sigstore/protobuf-specs v0.5.1 h1:/5OPaNuolRJmQfeZLayJGFXMpsRJEdgC6ah1/+7Px7U= -github.com/sigstore/protobuf-specs v0.5.1/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats= -github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= -github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= +github.com/sigstore/protobuf-specs v0.5.2 h1:RSWWUY8QrVTxbYH00jY/jg2e7YnjzrpwP+PeHTMll0E= +github.com/sigstore/protobuf-specs v0.5.2/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats= +github.com/sigstore/sigstore v1.10.11 h1:fjrwDfR0AWmRONYV/hKGJyqbT0osMtaBz9FTZ6MH9XI= +github.com/sigstore/sigstore v1.10.11/go.mod h1:ecPk+ftLqP8/TmzQFJmn0+QrL2yUQIJvyzVKNmDiw6U= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -1020,8 +1020,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/sylabs/sif/v2 v2.24.1 h1:OhTOfTwBaGXfbWYWXK9C6Pojma3A1bTaNJ6CEhyuKok= -github.com/sylabs/sif/v2 v2.24.1/go.mod h1:PRq9MoP0g+p0qE5ZRGrOhyVAUdHrPsAVmXHgoiW6VU0= +github.com/sylabs/sif/v2 v2.24.2 h1:rdkFf6z3xLC6yKweU7S9marlKfkS91Ht0ruIri6YWFY= +github.com/sylabs/sif/v2 v2.24.2/go.mod h1:bbrkk0JhTX+xNJcnUtmDkivoO2r4DGWM3LxNR0U2ooA= github.com/sylabs/squashfs v1.0.6 h1:PvJcDzxr+vIm2kH56mEMbaOzvGu79gK7P7IX+R7BDZI= github.com/sylabs/squashfs v1.0.6/go.mod h1:DlDeUawVXLWAsSRa085Eo0ZenGzAB32JdAUFaB0LZfE= github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/build.go new/grype-0.120.1/grype/db/build.go --- old/grype-0.120.0/grype/db/build.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/build.go 2026-10-06 18:42:08.000000000 +0200 @@ -161,6 +161,7 @@ return fmt.Errorf("failed to open cache entry %q: %w", opener.String(), err) } envelope, err := unmarshal.Envelope(f) + _ = f.Close() if err != nil { return fmt.Errorf("failed to unmarshal cache entry %q: %w", opener.String(), err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/internal/gormadapter/open.go new/grype-0.120.1/grype/db/internal/gormadapter/open.go --- old/grype-0.120.0/grype/db/internal/gormadapter/open.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/internal/gormadapter/open.go 2026-10-06 18:42:08.000000000 +0200 @@ -147,7 +147,15 @@ return nil, fmt.Errorf("unable to connect to DB: %w", err) } - return cfg.prepareDB(dbObj) + prepared, err := cfg.prepareDB(dbObj) + if err != nil { + // don't leave the file handle open (this blocks removing the file on windows) + if d, dbErr := dbObj.DB(); dbErr == nil { + _ = d.Close() + } + return nil, err + } + return prepared, nil } func (c config) prepareDB(dbObj *gorm.DB) (*gorm.DB, error) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/internal/gormadapter/open_test.go new/grype-0.120.1/grype/db/internal/gormadapter/open_test.go --- old/grype-0.120.0/grype/db/internal/gormadapter/open_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/internal/gormadapter/open_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -3,6 +3,7 @@ import ( "os" "path/filepath" + "runtime" "testing" "github.com/stretchr/testify/require" @@ -107,8 +108,9 @@ tempDir := t.TempDir() dbPath := filepath.Join(tempDir, "test.db") - _, err := os.Create(dbPath) + f, err := os.Create(dbPath) require.NoError(t, err) + require.NoError(t, f.Close()) _, err = os.Stat(dbPath) require.NoError(t, err) @@ -121,6 +123,9 @@ }) t.Run("returns error if unable to create parent directory", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("relies on posix permissions under /root") + } invalidDir := filepath.Join("/root", "invalidDir", "test.db") err := deleteDB(invalidDir) require.Error(t, err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/internal/tarutil/populate_test.go new/grype-0.120.1/grype/db/internal/tarutil/populate_test.go --- old/grype-0.120.0/grype/db/internal/tarutil/populate_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/internal/tarutil/populate_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -44,6 +44,7 @@ require.NoError(t, err) _, err = f.Write([]byte("hello world\n")) require.NoError(t, err) + require.NoError(t, f.Close()) archivePath := filepath.Join(dir, tt.tarPath) err = PopulateWithPaths(archivePath, tempPath) require.NoError(t, err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/internal/tarutil/writer.go new/grype-0.120.1/grype/db/internal/tarutil/writer.go --- old/grype-0.120.0/grype/db/internal/tarutil/writer.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/internal/tarutil/writer.go 2026-10-06 18:42:08.000000000 +0200 @@ -51,6 +51,33 @@ return nil, err } + c, err := newCompressor(archivePath, archive, compressorCommands) + if err != nil { + _ = archive.Close() + return nil, err + } + if c == io.WriteCloser(archive) { + return c, nil + } + // compressors don't own the underlying file, so close it after the compressor has flushed + return &archiveCompressor{WriteCloser: c, archive: archive}, nil +} + +// archiveCompressor closes the archive file after closing the compressor writing to it. +type archiveCompressor struct { + io.WriteCloser + archive *os.File +} + +func (a *archiveCompressor) Close() error { + err := a.WriteCloser.Close() + if closeErr := a.archive.Close(); closeErr != nil && err == nil { + err = closeErr + } + return err +} + +func newCompressor(archivePath string, archive *os.File, compressorCommands map[string]string) (io.WriteCloser, error) { // check for custom compressor command first for ext, cmd := range compressorCommands { if strings.HasSuffix(archivePath, "."+ext) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/internal/tarutil/writer_test.go new/grype-0.120.1/grype/db/internal/tarutil/writer_test.go --- old/grype-0.120.0/grype/db/internal/tarutil/writer_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/internal/tarutil/writer_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -45,7 +45,7 @@ for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() - require.NoError(t, os.Chdir(dir)) + t.Chdir(dir) testFilePath := "testfile" testString := "hello world" require.NoError(t, os.WriteFile(testFilePath, []byte(testString), 0644)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/provider/entry/sqlite.go new/grype-0.120.1/grype/db/provider/entry/sqlite.go --- old/grype-0.120.0/grype/db/provider/entry/sqlite.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/provider/entry/sqlite.go 2026-10-06 18:42:08.000000000 +0200 @@ -41,6 +41,7 @@ if err != nil { return nil, err } + defer closeDB(db) query := db.Model(&results{}) @@ -93,6 +94,7 @@ if err != nil { return 0, err } + defer closeDB(db) var count int64 db.Model(&results{}).Count(&count) @@ -124,6 +126,7 @@ openers := make(chan Opener) go func() { defer close(openers) + defer closeDB(db) var models []results @@ -187,6 +190,12 @@ return dbObj, nil } +func closeDB(db *gorm.DB) { + if d, err := db.DB(); err == nil { + _ = d.Close() + } +} + // ConnectionString creates a connection string for sqlite3 func connectionString(path string) (string, error) { if path == "" { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/provider/state.go new/grype-0.120.1/grype/db/provider/state.go --- old/grype-0.120.0/grype/db/provider/state.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/provider/state.go 2026-10-06 18:42:08.000000000 +0200 @@ -72,6 +72,7 @@ if err != nil { return nil, fmt.Errorf("unable to open listing file %q: %w", listingPath, err) } + defer f.Close() // note: bufio scanner is **much** faster than Fscan scanner := bufio.NewScanner(f) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/provider/workspace.go new/grype-0.120.1/grype/db/provider/workspace.go --- old/grype-0.120.0/grype/db/provider/workspace.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/provider/workspace.go 2026-10-06 18:42:08.000000000 +0200 @@ -121,7 +121,7 @@ // format: <hash> results/<relative-path> // the path is relative to the provider directory - listingPath := filepath.Join("results", relPath) + listingPath := filepath.ToSlash(filepath.Join("results", relPath)) if _, err := fmt.Fprintf(writer, "%s %s\n", hash, listingPath); err != nil { return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/provider/workspace_writer.go new/grype-0.120.1/grype/db/provider/workspace_writer.go --- old/grype-0.120.0/grype/db/provider/workspace_writer.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/provider/workspace_writer.go 2026-10-06 18:42:08.000000000 +0200 @@ -116,7 +116,7 @@ _, _ = hasher.Write(formattedContent) return &File{ - Path: filepath.Join("results", filename), + Path: filepath.ToSlash(filepath.Join("results", filename)), Digest: hex.EncodeToString(hasher.Sum(nil)), Algorithm: xxh64Algorithm, }, nil diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v5/distribution/curator.go new/grype-0.120.1/grype/db/v5/distribution/curator.go --- old/grype-0.120.0/grype/db/v5/distribution/curator.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v5/distribution/curator.go 2026-10-06 18:42:08.000000000 +0200 @@ -542,6 +542,7 @@ if err != nil { return err } + defer root.Close() visitor := func(_ context.Context, file archives.FileInfo) error { if file.IsDir() || file.LinkTarget != "" { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v5/distribution/curator_test.go new/grype-0.120.1/grype/db/v5/distribution/curator_test.go --- old/grype-0.120.0/grype/db/v5/distribution/curator_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v5/distribution/curator_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -789,6 +789,7 @@ } err = format.Archive(t.Context(), out, files) require.NoError(t, err) + require.NoError(t, out.Close()) destination := t.TempDir() err = unarchive(source, destination) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v5/distribution/metadata_test.go new/grype-0.120.1/grype/db/v5/distribution/metadata_test.go --- old/grype-0.120.0/grype/db/v5/distribution/metadata_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v5/distribution/metadata_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -1,6 +1,7 @@ package distribution import ( + "runtime" "testing" "time" @@ -38,6 +39,9 @@ for _, test := range tests { t.Run(test.fixture, func(t *testing.T) { + if test.err && runtime.GOOS == "windows" { + t.Skip("relies on posix ENOTDIR semantics, windows reports the path as not existing") + } metadata, err := NewMetadataFromDir(afero.NewOsFs(), test.fixture) if err != nil && !test.err { t.Fatalf("failed to get metadata: %+v", err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v5/store/diff_test.go new/grype-0.120.1/grype/db/v5/store/diff_test.go --- old/grype-0.120.0/grype/db/v5/store/diff_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v5/store/diff_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -18,6 +18,7 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s) //WHEN result, err := s.GetAllVulnerabilities() @@ -37,6 +38,7 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s) //WHEN result, err := s.GetAllVulnerabilityMetadata() @@ -54,12 +56,14 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s1) dbTempFile = t.TempDir() s2, err := New(dbTempFile, true) if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s2) baseVulns := []v5.Vulnerability{ { @@ -159,11 +163,13 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s1) dbTempFile = t.TempDir() s2, err := New(dbTempFile, true) if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s2) baseVulns := []v5.VulnerabilityMetadata{ { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v5/store/store_test.go new/grype-0.120.1/grype/db/v5/store/store_test.go --- old/grype-0.120.0/grype/db/v5/store/store_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v5/store/store_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -27,6 +27,15 @@ } } +// closeOnCleanup closes the underlying sqlite handle so the temp dir can be removed (open files can't be deleted on windows) +func closeOnCleanup(t *testing.T, s v5.Store) { + t.Cleanup(func() { + if d, err := s.(*store).db.DB(); err == nil { + _ = d.Close() + } + }) +} + func TestStore_GetID_SetID(t *testing.T) { dbTempFile := t.TempDir() @@ -34,6 +43,7 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s) expected := v5.ID{ BuildTimestamp: time.Now().UTC(), @@ -72,6 +82,7 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s) extra := []v5.Vulnerability{ { @@ -275,6 +286,7 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s) total := []v5.VulnerabilityMetadata{ { @@ -745,6 +757,7 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s) // add each metadata in order var theErr error @@ -1014,6 +1027,7 @@ if err != nil { t.Fatalf("could not create s: %+v", err) } + closeOnCleanup(t, s) // add each metadata in order for _, metadata := range test.add { @@ -1061,6 +1075,7 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s) extra := []v5.VulnerabilityMatchExclusion{ { @@ -1217,11 +1232,13 @@ if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s1) dbTempFile = t.TempDir() s2, err := New(dbTempFile, true) if err != nil { t.Fatalf("could not create store: %+v", err) } + closeOnCleanup(t, s2) baseVulns := []v5.Vulnerability{ { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/build/transformers/openvex/transform_test.go new/grype-0.120.1/grype/db/v6/build/transformers/openvex/transform_test.go --- old/grype-0.120.0/grype/db/v6/build/transformers/openvex/transform_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/build/transformers/openvex/transform_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -8,13 +8,12 @@ govex "github.com/openvex/go-vex/pkg/vex" "github.com/stretchr/testify/require" - "github.com/anchore/packageurl-go" - "github.com/anchore/grype/grype/db/internal/provider/unmarshal" "github.com/anchore/grype/grype/db/provider" db "github.com/anchore/grype/grype/db/v6" "github.com/anchore/grype/grype/db/v6/build/transformers" "github.com/anchore/grype/grype/db/v6/build/transformers/internal" + "github.com/anchore/packageurl-go" ) var timeVal = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/db_metadata_store_test.go new/grype-0.120.1/grype/db/v6/db_metadata_store_test.go --- old/grype-0.120.0/grype/db/v6/db_metadata_store_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/db_metadata_store_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -71,6 +71,7 @@ DBDirPath: dir, }, true, true) require.NoError(t, err) + closeOnCleanup(t, s) require.NoError(t, s.SetDBMetadata()) @@ -82,6 +83,17 @@ DBDirPath: dir, }, false, false) require.NoError(t, err) + closeOnCleanup(t, s) return s } + +// closeOnCleanup closes the underlying connection when the test ends (without the vacuum/index work that +// store.Close does), since windows cannot remove the temp dir while the db file is still open. +func closeOnCleanup(t testing.TB, s *store) { + t.Cleanup(func() { + if d, err := s.db.DB(); err == nil { + _ = d.Close() + } + }) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/diff/db_resolver.go new/grype-0.120.1/grype/db/v6/diff/db_resolver.go --- old/grype-0.120.0/grype/db/v6/diff/db_resolver.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/diff/db_resolver.go 2026-10-06 18:42:08.000000000 +0200 @@ -156,6 +156,7 @@ if err != nil { return err } + defer log.CloseAndLogError(root, destination) visitor := func(_ context.Context, file archives.FileInfo) error { if file.IsDir() || file.LinkTarget != "" { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/diff/differ.go new/grype-0.120.1/grype/db/v6/diff/differ.go --- old/grype-0.120.0/grype/db/v6/diff/differ.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/diff/differ.go 2026-10-06 18:42:08.000000000 +0200 @@ -68,10 +68,12 @@ beforeTime, err := time.Parse(time.RFC3339, oldDB.Info.BuildTimestamp) if err != nil { + closeLowLevelDB(db) return nil, fmt.Errorf("failed to parse build timestamp: %w", err) } afterTime, err := time.Parse(time.RFC3339, newDB.Info.BuildTimestamp) if err != nil { + closeLowLevelDB(db) return nil, fmt.Errorf("failed to parse build timestamp: %w", err) } @@ -94,10 +96,11 @@ // Close detaches the new database and closes the connection. func (d *DBDiffer) Close() error { + // always release the connection, even if the detach fails (an open handle keeps the db files locked on windows) + defer closeLowLevelDB(d.db) if err := d.db.Exec("DETACH DATABASE new_db").Error; err != nil { return fmt.Errorf("failed to detach new database: %w", err) } - closeLowLevelDB(d.db) return nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/diff/differ_test.go new/grype-0.120.1/grype/db/v6/diff/differ_test.go --- old/grype-0.120.0/grype/db/v6/diff/differ_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/diff/differ_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -275,6 +275,7 @@ NewDB: newDB, }) require.NoError(t, err) + t.Cleanup(func() { _ = differ.Close() }) require.NotNil(t, differ) packageResult, err := differ.Diff() @@ -887,6 +888,7 @@ NewDB: newDB, }) require.NoError(t, err) + t.Cleanup(func() { _ = differ.Close() }) require.NotNil(t, differ) packageResult, err := differ.Diff() @@ -1097,6 +1099,7 @@ NewDB: newDBDir, }) require.NoError(t, err) + t.Cleanup(func() { _ = differ.Close() }) require.NotNil(t, differ) packageResult, err := differ.Diff() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/diff/vuln_diff_test.go new/grype-0.120.1/grype/db/v6/diff/vuln_diff_test.go --- old/grype-0.120.0/grype/db/v6/diff/vuln_diff_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/diff/vuln_diff_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -363,6 +363,7 @@ EPSSThreshold: epssThreshold, }) require.NoError(t, err) + t.Cleanup(func() { _ = differ.Close() }) require.NotNil(t, differ) result, err := differ.Diff() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/distribution/latest_test.go new/grype-0.120.1/grype/db/v6/distribution/latest_test.go --- old/grype-0.120.0/grype/db/v6/distribution/latest_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/distribution/latest_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -271,6 +271,7 @@ require.NoError(t, err) _, err = tempFile.WriteString(tt.contents) require.NoError(t, err) + require.NoError(t, tempFile.Close()) archive, err := NewArchive(tempFile.Name(), tt.time, tt.model, tt.revision, tt.addition) tt.expectErr(t, err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/import_metadata_test.go new/grype-0.120.1/grype/db/v6/import_metadata_test.go --- old/grype-0.120.0/grype/db/v6/import_metadata_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/import_metadata_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -62,8 +62,7 @@ err := os.WriteFile(filePath, []byte(tt.fileContent), 0644) require.NoError(t, err) } else if tt.emptyFile { - _, err := os.Create(filePath) - require.NoError(t, err) + require.NoError(t, os.WriteFile(filePath, nil, 0644)) } result, err := ReadImportMetadata(afero.NewOsFs(), dir) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/installation/curator.go new/grype-0.120.1/grype/db/v6/installation/curator.go --- old/grype-0.120.0/grype/db/v6/installation/curator.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/installation/curator.go 2026-10-06 18:42:08.000000000 +0200 @@ -102,6 +102,7 @@ m, err := s.GetDBMetadata() if err != nil { + log.CloseAndLogError(s, c.config.DBDirectoryPath()) return nil, fmt.Errorf("unable to get vulnerability store metadata: %w", err) } @@ -113,6 +114,7 @@ doRehydrate, err := isRehydrationNeeded(c.fs, c.config.DBDirectoryPath(), currentDBSchemaVersion, schemaver.New(db.ModelVersion, db.Revision, db.Addition)) if err != nil { + log.CloseAndLogError(s, c.config.DBDirectoryPath()) return nil, err } if doRehydrate { @@ -643,6 +645,7 @@ if err != nil { return err } + defer root.Close() visitor := func(_ context.Context, file archives.FileInfo) error { if file.IsDir() || file.LinkTarget != "" { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/db/v6/installation/curator_test.go new/grype-0.120.1/grype/db/v6/installation/curator_test.go --- old/grype-0.120.0/grype/db/v6/installation/curator_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/db/v6/installation/curator_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -138,6 +138,11 @@ require.NoError(t, d.Exec("VACUUM").Error) + // release the file before hashing it (and so the temp dir can be removed on windows) + sqlDB, err := d.DB() + require.NoError(t, err) + require.NoError(t, sqlDB.Close()) + digest, err := db.CalculateDBDigest(afero.NewOsFs(), c.DBFilePath()) require.NoError(t, err) @@ -523,6 +528,7 @@ m, err := s.GetDBMetadata() require.NoError(t, err) + require.NoError(t, s.Close()) return ci.(curator), db.DescriptionFromMetadata(m) } @@ -937,6 +943,7 @@ } err = format.Archive(t.Context(), out, files) require.NoError(t, err) + require.NoError(t, out.Close()) destination := t.TempDir() err = unarchive(source, destination) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/matcher/golang/testdata/gobin-httpclient/Makefile new/grype-0.120.1/grype/matcher/golang/testdata/gobin-httpclient/Makefile --- old/grype-0.120.0/grype/matcher/golang/testdata/gobin-httpclient/Makefile 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/matcher/golang/testdata/gobin-httpclient/Makefile 2026-10-06 18:42:08.000000000 +0200 @@ -1,8 +1,12 @@ BINARY := binary .DEFAULT_GOAL := $(BINARY) +# always build an ELF binary: syft only extracts go symbols from ELF and Mach-O (not PE), so a +# native windows build would carry no symbols +export GOOS := linux + .PHONY: clean -$(BINARY): $(wildcard *.go) go.mod +$(BINARY): $(wildcard *.go) Makefile go.mod go build -trimpath -o $(BINARY) . clean: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/matcher/golang/testdata/gobin-httpserver/Makefile new/grype-0.120.1/grype/matcher/golang/testdata/gobin-httpserver/Makefile --- old/grype-0.120.0/grype/matcher/golang/testdata/gobin-httpserver/Makefile 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/matcher/golang/testdata/gobin-httpserver/Makefile 2026-10-06 18:42:08.000000000 +0200 @@ -1,8 +1,12 @@ BINARY := binary .DEFAULT_GOAL := $(BINARY) +# always build an ELF binary: syft only extracts go symbols from ELF and Mach-O (not PE), so a +# native windows build would carry no symbols +export GOOS := linux + .PHONY: clean -$(BINARY): $(wildcard *.go) go.mod +$(BINARY): $(wildcard *.go) Makefile go.mod go build -trimpath -o $(BINARY) . clean: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/matcher/golang/testdata/gobin-xnet-html/Makefile new/grype-0.120.1/grype/matcher/golang/testdata/gobin-xnet-html/Makefile --- old/grype-0.120.0/grype/matcher/golang/testdata/gobin-xnet-html/Makefile 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/matcher/golang/testdata/gobin-xnet-html/Makefile 2026-10-06 18:42:08.000000000 +0200 @@ -1,8 +1,12 @@ BINARY := binary .DEFAULT_GOAL := $(BINARY) +# always build an ELF binary: syft only extracts go symbols from ELF and Mach-O (not PE), so a +# native windows build would carry no symbols +export GOOS := linux + .PHONY: clean -$(BINARY): $(wildcard *.go) go.mod go.sum +$(BINARY): $(wildcard *.go) Makefile go.mod go.sum go build -trimpath -o $(BINARY) . clean: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/matcher/golang/testdata/gobin-xnet-http2server/Makefile new/grype-0.120.1/grype/matcher/golang/testdata/gobin-xnet-http2server/Makefile --- old/grype-0.120.0/grype/matcher/golang/testdata/gobin-xnet-http2server/Makefile 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/matcher/golang/testdata/gobin-xnet-http2server/Makefile 2026-10-06 18:42:08.000000000 +0200 @@ -1,8 +1,12 @@ BINARY := binary .DEFAULT_GOAL := $(BINARY) +# always build an ELF binary: syft only extracts go symbols from ELF and Mach-O (not PE), so a +# native windows build would carry no symbols +export GOOS := linux + .PHONY: clean -$(BINARY): $(wildcard *.go) go.mod go.sum +$(BINARY): $(wildcard *.go) Makefile go.mod go.sum go build -trimpath -o $(BINARY) . clean: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/pkg/provider_test.go new/grype-0.120.1/grype/pkg/provider_test.go --- old/grype-0.120.0/grype/pkg/provider_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/pkg/provider_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -2,6 +2,7 @@ import ( "os" + "runtime" "testing" "github.com/google/go-cmp/cmp" @@ -134,6 +135,10 @@ } func TestSyftLocationExcludes(t *testing.T) { + if runtime.GOOS == "windows" { + // the docker image fixtures are linux images (FROM scratch), which windows docker cannot build + t.Skip("linux docker image fixtures are not supported on windows") + } tests := []struct { name string fixture string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/presenter/internal/test_helpers.go new/grype-0.120.1/grype/presenter/internal/test_helpers.go --- old/grype-0.120.0/grype/presenter/internal/test_helpers.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/presenter/internal/test_helpers.go 2026-10-06 18:42:08.000000000 +0200 @@ -113,8 +113,10 @@ cycloneDxBomRefPattern := regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`) tempDirPattern := regexp.MustCompile(`/tmp/[^"]+`) macTempDirPattern := regexp.MustCompile(`/var/folders/[^"]+`) + // json-escaped windows paths (e.g. "C:\\Users\\...\\Temp\\...") + windowsTempDirPattern := regexp.MustCompile(`[A-Za-z]:\\\\[^"]+`) - for _, pattern := range []*regexp.Regexp{serialPattern, rfc3339Pattern, refPattern, uuidPattern, cycloneDxBomRefPattern, tempDirPattern, macTempDirPattern} { + for _, pattern := range []*regexp.Regexp{serialPattern, rfc3339Pattern, refPattern, uuidPattern, cycloneDxBomRefPattern, tempDirPattern, macTempDirPattern, windowsTempDirPattern} { s = pattern.ReplaceAll(s, []byte("")) } return s diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/presenter/sarif/presenter.go new/grype-0.120.1/grype/presenter/sarif/presenter.go --- old/grype-0.120.0/grype/presenter/sarif/presenter.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/presenter/sarif/presenter.go 2026-10-06 18:42:08.000000000 +0200 @@ -5,7 +5,7 @@ "fmt" "hash" "io" - "path/filepath" + "path" "regexp" "strings" @@ -194,20 +194,20 @@ // locationPath returns a path for the location, relative to the cwd func (p Presenter) locationPath(l file.Location) string { - path := l.Path() + loc := strings.TrimPrefix(l.Path(), "./") in := p.inputPath() - path = strings.TrimPrefix(path, "./") // trimmed off any ./ and accounted for dir:. for both path and input path _, ok := p.src.Metadata.(source.DirectoryMetadata) if ok { - if filepath.IsAbs(path) || in == "" { - return path + // location paths are always posix (regardless of host OS), so use path over filepath here + if path.IsAbs(loc) || in == "" { + return loc } // return a path relative to the cwd, if it's not absolute - return fmt.Sprintf("%s/%s", in, path) + return fmt.Sprintf("%s/%s", in, loc) } - return path + return loc } // locations the locations array is a single "physical" location with potentially multiple logical locations diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/presenter/table/presenter.go new/grype-0.120.1/grype/presenter/table/presenter.go --- old/grype-0.120.0/grype/presenter/table/presenter.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/presenter/table/presenter.go 2026-10-06 18:42:08.000000000 +0200 @@ -315,7 +315,6 @@ m, getRecommendedVersions(m), ), - m.Vulnerability.Fix.Versions, ) } @@ -334,7 +333,7 @@ const maxVersionFieldLength = 30 -func (p *Presenter) formatVersionsToDisplay(m models.Match, recommendedVersions *strset.Set) []string { +func (p *Presenter) formatVersionsToDisplay(m models.Match, recommendedVersions *strset.Set) (versions []string, omittedAny bool) { hasMultipleVersions := len(m.Vulnerability.Fix.Versions) > 1 shouldHighlightRecommended := hasMultipleVersions && recommendedVersions.Size() > 0 @@ -358,6 +357,7 @@ // skip not-necessarily-recommended versions if we're running out of space if currentCharacterCount+len(v) > maxVersionFieldLength { + omittedAny = true continue } @@ -372,18 +372,14 @@ } } - return vers + return vers, omittedAny } -func (p *Presenter) applyTruncation(formattedVersions []string, allVersions []string) string { +func (p *Presenter) applyTruncation(formattedVersions []string, omittedAny bool) string { finalVersions := strings.Join(formattedVersions, p.auxiliaryStyle.Render(", ")) - var characterCount int - for _, v := range allVersions { - characterCount += len(v) - } - - if characterCount > maxVersionFieldLength && len(allVersions) > 1 { + // only hint at more versions when one was actually left out + if omittedAny { finalVersions += p.auxiliaryStyle.Render(", ...") } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/grype/presenter/table/presenter_test.go new/grype-0.120.1/grype/presenter/table/presenter_test.go --- old/grype-0.120.0/grype/presenter/table/presenter_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/grype/presenter/table/presenter_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -112,6 +112,67 @@ } } +func TestFormatFix(t *testing.T) { + matchWithFixVersions := func(versions []string, recommended ...string) models.Match { + var details []models.MatchDetails + for _, r := range recommended { + details = append(details, models.MatchDetails{ + Type: match.ExactDirectMatch.String(), + Matcher: match.DpkgMatcher.String(), + Fix: &models.FixDetails{SuggestedVersion: r}, + }) + } + return models.Match{ + Vulnerability: models.Vulnerability{ + Fix: models.Fix{ + Versions: versions, + State: vulnerability.FixStateFixed.String(), + }, + }, + MatchDetails: details, + } + } + + cases := []struct { + name string + match models.Match + expected string + }{ + { + name: "all recommended versions are shown without a truncation hint", + match: matchWithFixVersions( + []string{"10.20.30-alpha.1", "10.20.30-beta.11"}, + "10.20.30-alpha.1", "10.20.30-beta.11", + ), + expected: "*10.20.30-alpha.1, *10.20.30-beta.11", + }, + { + name: "a version dropped for space keeps the truncation hint", + match: matchWithFixVersions( + []string{"1.0.0", "2.0.0-really-long-version-string"}, + "1.0.0", + ), + expected: "*1.0.0, ...", + }, + { + name: "duplicate versions are not reported as truncated", + match: matchWithFixVersions( + []string{"10.20.30-alpha.1", "10.20.30-alpha.1"}, + "10.20.30-alpha.1", + ), + expected: "*10.20.30-alpha.1", + }, + } + + for _, testCase := range cases { + t.Run(testCase.name, func(t *testing.T) { + p := NewPresenter(models.PresenterConfig{}, false) + + assert.Equal(t, testCase.expected, p.formatFix(testCase.match)) + }) + } +} + func TestTablePresenter(t *testing.T) { pb := internal.GeneratePresenterConfig(t, internal.ImageSource) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/internal/dbtest/builder.go new/grype-0.120.1/internal/dbtest/builder.go --- old/grype-0.120.0/internal/dbtest/builder.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/internal/dbtest/builder.go 2026-10-06 18:42:08.000000000 +0200 @@ -339,10 +339,12 @@ // "results/" segment is not present in the path, only the basename is returned // to fall back to the prior behavior. func relativeResultPath(path string) string { - const sep = string(filepath.Separator) + "results" + string(filepath.Separator) + // normalize to forward slashes so this behaves the same regardless of platform separators + const sep = "/results/" + path = filepath.ToSlash(path) idx := strings.LastIndex(path, sep) if idx < 0 { - return filepath.Base(path) + return filepath.Base(filepath.FromSlash(path)) } return path[idx+len(sep):] } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/internal/dbtest/extractor_test.go new/grype-0.120.1/internal/dbtest/extractor_test.go --- old/grype-0.120.0/internal/dbtest/extractor_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/internal/dbtest/extractor_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -223,6 +223,12 @@ connStr := "file:" + dbPath + "?cache=shared" db, err := gorm.Open(sqlite.Open(connStr), &gorm.Config{Logger: logger.Discard}) require.NoError(t, err) + // windows can't remove the temp dir while the db is open + t.Cleanup(func() { + if d, err := db.DB(); err == nil { + _ = d.Close() + } + }) // create table require.NoError(t, db.AutoMigrate(&testResults{})) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/internal/format/writer_test.go new/grype-0.120.1/internal/format/writer_test.go --- old/grype-0.120.0/internal/format/writer_test.go 2026-10-02 15:23:52.000000000 +0200 +++ new/grype-0.120.1/internal/format/writer_test.go 2026-10-06 18:42:08.000000000 +0200 @@ -1,6 +1,7 @@ package format import ( + "io" "path/filepath" "strings" "testing" @@ -153,7 +154,7 @@ outputs := test.outputs for i := range outputs { if outputs[i].Path != "" { - outputs[i].Path = tmp + outputs[i].Path + outputs[i].Path = filepath.Join(tmp, outputs[i].Path) } } @@ -165,6 +166,14 @@ } else { assert.NoError(t, err) } + // release file handles so the temp dir can be removed (required on windows) + t.Cleanup(func() { + for _, w := range mw.writers { + if c, ok := w.(io.Closer); ok { + _ = c.Close() + } + } + }) assert.Len(t, mw.writers, len(test.expected)) @@ -174,7 +183,7 @@ assert.Equal(t, string(w.format), e.format) assert.NotNil(t, w.out) if e.file != "" { - assert.FileExists(t, tmp+e.file) + assert.FileExists(t, filepath.Join(tmp, e.file)) } case *scanResultPublisher: assert.Equal(t, string(w.format), e.format) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.120.0/test/containers-storage/source-test.sh new/grype-0.120.1/test/containers-storage/source-test.sh --- old/grype-0.120.0/test/containers-storage/source-test.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/grype-0.120.1/test/containers-storage/source-test.sh 2026-10-06 18:42:08.000000000 +0200 @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +# build an image with podman or buildah into an isolated containers-storage store, then verify grype can scan it out +# of that store (for both the vfs and overlay drivers) and that plain image references never touch the store. +# +# usage: GRYPE_BINARY_LOCATION=/path/to/grype source-test.sh podman|buildah +set -euo pipefail + +builder="${1:?expected podman or buildah}" +case "$builder" in + podman|buildah) ;; + *) + printf 'unsupported image builder: %s\n' "$builder" >&2 + exit 2 + ;; +esac + +grype_binary="${GRYPE_BINARY_LOCATION:?expected GRYPE_BINARY_LOCATION to point at a linux grype binary built with containers-storage support}" +if [[ ! -f "$grype_binary" ]]; then + printf 'grype binary not found at %s\n' "$grype_binary" >&2 + exit 1 +fi +chmod +x "$grype_binary" + +test_dir="$(mktemp -d)" +# the overlay driver bind-mounts its graphroot onto itself (in the builder's user namespace when rootless), which must +# be unmounted before the store can be removed. Cleanup is best-effort so it never masks the test result. +cleanup_store() { + umount -l "$1"/*/graphroot/overlay 2>/dev/null || true + rm -rf "$1" +} +cleanup() { + if [[ "$(id -u)" -eq 0 ]]; then + cleanup_store "$test_dir" + else + "$builder" unshare bash -c "$(declare -f cleanup_store); cleanup_store \"\$1\"" _ "$test_dir" + fi || printf 'warning: unable to fully clean up %s\n' "$test_dir" >&2 +} +trap cleanup EXIT + +# a rootless store can only be opened from inside the builder's user namespace (as podman/buildah/skopeo do for +# themselves), so rootless users must run grype under "<builder> unshare". Root opens the store directly. +run_grype() { + if [[ "$(id -u)" -eq 0 ]]; then + "$grype_binary" "$@" + else + "$builder" unshare "$grype_binary" "$@" + fi +} + +export GRYPE_CHECK_FOR_APP_UPDATE=false +export GRYPE_DB_CACHE_DIR="$test_dir/db" +"$grype_binary" db update +export GRYPE_DB_AUTO_UPDATE=false + +# lodash 4.17.20 has known vulnerabilities, so a match proves the package was cataloged out of the image +build_context="$test_dir/context" +mkdir -p "$build_context" +cat > "$build_context/package.json" <<'EOF' +{"name":"lodash","version":"4.17.20"} +EOF +cat > "$build_context/Containerfile" <<'EOF' +FROM scratch +COPY package.json /app/node_modules/lodash/package.json +EOF + +# note: the image name deliberately avoids the string "containers-storage" so stderr greps below are unambiguous +image_ref="localhost/grype-cs-fixture:latest" +missing_ref="localhost/grype-cs-missing:latest" + +for driver in vfs overlay; do + store_dir="$test_dir/$driver" + mkdir -p "$store_dir" + export CONTAINERS_STORAGE_CONF="$store_dir/storage.conf" + cat > "$CONTAINERS_STORAGE_CONF" <<EOF +[storage] +driver = "$driver" +graphroot = "$store_dir/graphroot" +rootless_storage_path = "$store_dir/graphroot" +runroot = "$store_dir/runroot" +EOF + + printf 'Building %s with %s (%s driver)\n' "$image_ref" "$builder" "$driver" + case "$builder" in + podman) + podman build --pull=never --tag "$image_ref" "$build_context" + ;; + buildah) + buildah build --pull=false --tag "$image_ref" "$build_context" + ;; + esac + + for scan_mode in from-flag scheme default-pull-source; do + printf 'Testing %s resolution with %s (%s driver)\n' "$scan_mode" "$builder" "$driver" + out="$store_dir/$scan_mode" + case "$scan_mode" in + from-flag) + run_grype -vv --from containers-storage "$image_ref" --output json > "$out.json" 2> "$out.stderr" + ;; + scheme) + run_grype -vv "containers-storage:$image_ref" --output json > "$out.json" 2> "$out.stderr" + ;; + default-pull-source) + GRYPE_DEFAULT_IMAGE_PULL_SOURCE=containers-storage \ + run_grype -vv "$image_ref" --output json > "$out.json" 2> "$out.stderr" + ;; + esac + + # prove the image came out of the store, not from some other provider that happened to answer + if ! grep -q 'copied image from containers-storage' "$out.stderr"; then + printf 'Expected %s scan to resolve via containers-storage\n' "$scan_mode" >&2 + cat "$out.stderr" >&2 + exit 1 + fi + jq -e --arg image_ref "$image_ref" ' + .source.type == "image" and + .source.target.userInput == $image_ref and + any(.matches[].artifact; .name == "lodash" and .version == "4.17.20" and .type == "npm") + ' "$out.json" >/dev/null + done + + printf 'Testing plain reference skips containers-storage with %s (%s driver)\n' "$builder" "$driver" + # the image only exists in the local store, so automatic resolution (daemons, then registry) must fail without + # ever trying containers-storage + if run_grype "$image_ref" --output json > "$store_dir/plain.json" 2> "$store_dir/plain.stderr"; then + printf 'Expected plain reference scan to fail since the image only exists in containers-storage\n' >&2 + exit 1 + fi + if grep -q 'containers-storage' "$store_dir/plain.stderr"; then + printf 'Plain reference unexpectedly attempted containers-storage resolution\n' >&2 + cat "$store_dir/plain.stderr" >&2 + exit 1 + fi + + printf 'Testing missing image with %s (%s driver)\n' "$builder" "$driver" + if run_grype --from containers-storage "$missing_ref" --output json > "$store_dir/missing.json" 2> "$store_dir/missing.stderr"; then + printf 'Expected the missing containers-storage image scan to fail\n' >&2 + exit 1 + fi + if ! grep -q 'does not resolve to an image ID' "$store_dir/missing.stderr"; then + printf 'Expected a containers-storage "image not found" error\n' >&2 + cat "$store_dir/missing.stderr" >&2 + exit 1 + fi +done ++++++ grype.obsinfo ++++++ --- /var/tmp/diff_new_pack.MDryGX/_old 2026-10-07 19:34:06.072389065 +0200 +++ /var/tmp/diff_new_pack.MDryGX/_new 2026-10-07 19:34:06.076389233 +0200 @@ -1,5 +1,5 @@ name: grype -version: 0.120.0 -mtime: 1790947432 -commit: 10fbc043ecbaf8436daa9efac9f1632557caf277 +version: 0.120.1 +mtime: 1791304928 +commit: 6f8d854af29d3a3086b11a84afa51554a2a245fe ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/grype/vendor.tar.gz /work/SRC/openSUSE:Factory/.grype.new.3698736/vendor.tar.gz differ: char 134, line 2
