diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d52390045..638268f38 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,6 +21,9 @@ updates: - dependency-name: "github.com/aquasecurity/go-version" - dependency-name: "github.com/knqyf263/go-apk-version" - dependency-name: "github.com/knqyf263/go-deb-version" + # v1 changed SlicesAsSets hashing, which changes every artifact ID syft produces + - dependency-name: "github.com/gohugoio/hashstructure" + update-types: ["version-update:semver-major"] - package-ecosystem: "github-actions" directories: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 72ec17297..d1cad71b8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -46,7 +46,7 @@ jobs: # these are checks that should be run on pull-request and merges to main. # we do NOT want to kick off a release if these have not been verified on main. # Please see the validations.yaml workflow for the names that should be used here. - checks: '["Acceptance tests (Linux)", "Acceptance tests (Mac)", "Build snapshot artifacts", "CLI tests (Linux)", "Integration tests", "Static analysis", "Unit tests"]' + checks: '["Acceptance tests (Linux)", "Acceptance tests (Mac)", "Build snapshot artifacts", "CLI tests (Linux)", "Containers-storage tests (Podman)", "Containers-storage tests (Buildah)", "Integration tests", "Static analysis", "Unit tests"]' release: needs: [check-gate, version-available] diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 95092a476..998ba78ef 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -51,8 +51,19 @@ jobs: name: "Unit tests" runs-on: ubuntu-latest needs: [Unit-Test-Linux, Unit-Test-Windows] + # a skipped job satisfies a required check, so this job must always run and fail explicitly + # unless every dependency succeeded (otherwise a failing PR can still be auto-merged) + if: ${{ always() }} steps: - - run: echo All unit tests passed! + - name: Require all unit test jobs to succeed + env: + NEEDS: ${{ toJSON(needs) }} + run: | + echo "$NEEDS" | jq -e 'length > 0 and all(.[]; .result == "success")' > /dev/null || { + echo "$NEEDS" | jq -r 'to_entries[] | "\(.key): \(.value.result)"' + exit 1 + } + echo All unit tests passed! Unit-Test-Linux: name: "Unit tests (Linux)" @@ -85,16 +96,24 @@ jobs: with: persist-credentials: false - # go-make's setup only (not ./.github/actions/bootstrap): no apt, binny tools, or fixture cache are needed + # go-make's setup only (not ./.github/actions/bootstrap): binny ships no windows release, so binny tools + # are unavailable here (`make unit` skips the fixture refresh on windows) - name: Setup go + go-make tooling uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1 with: # keep in sync with the go-version default in ./.github/actions/bootstrap go-version: "1.26.2" - # the full unit suite leans on linux docker fixtures, so this runs the windows-relevant subset + # the binny-free version of `make download-test-fixture-cache` (keep the oras version in sync with .binny.yaml) + - name: Download test fixture cache + shell: bash + run: | + curl -sSfL https://github.com/oras-project/oras/releases/download/v1.3.4/oras_1.3.4_windows_amd64.zip -o "$RUNNER_TEMP/oras.zip" + unzip -q "$RUNNER_TEMP/oras.zip" oras.exe -d "$RUNNER_TEMP" + "$RUNNER_TEMP/oras.exe" pull ghcr.io/anchore/oss-cache/syft-test-fixture-cache:v2 + - name: Run unit tests - run: make unit:windows + run: make unit Integration-Test: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline @@ -158,6 +177,72 @@ jobs: path: snapshot/ retention-days: 30 + Containers-Storage-Podman: + name: "Containers-storage tests (Podman)" + needs: [Build-Snapshot-Artifacts] + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1 + with: + persist-credentials: false + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1 + with: + name: snapshot + path: snapshot + + # go-make's setup only (not ./.github/actions/bootstrap): the test only needs make + the snapshot binary + - name: Setup go + go-make tooling + uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1 + with: + # keep in sync with the go-version default in ./.github/actions/bootstrap + go-version: "1.26.2" + + - name: Install Podman + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends -y jq podman uidmap + + - name: Scan image from Podman's local store + run: make containers-storage-test + env: + BUILDER: podman + + Containers-Storage-Buildah: + name: "Containers-storage tests (Buildah)" + needs: [Build-Snapshot-Artifacts] + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1 + with: + persist-credentials: false + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1 + with: + name: snapshot + path: snapshot + + # go-make's setup only (not ./.github/actions/bootstrap): the test only needs make + the snapshot binary + - name: Setup go + go-make tooling + uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1 + with: + # keep in sync with the go-version default in ./.github/actions/bootstrap + go-version: "1.26.2" + + - name: Install Buildah + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends -y buildah jq uidmap + + - name: Scan image from Buildah's local store + run: make containers-storage-test + env: + BUILDER: buildah + Acceptance-Linux: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline name: "Acceptance tests (Linux)" diff --git a/.golangci.yaml b/.golangci.yaml index 5aa2a4905..bdb4e0a22 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,6 +1,10 @@ version: "2" run: tests: false + # keep in sync with buildTags in .make/main.go + build-tags: + - containers_image_openpgp + - exclude_graphdriver_btrfs linters: default: none enable: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d5bb7de18..4f4470432 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -15,6 +15,7 @@ builds: binary: syft goos: [linux] goarch: [amd64, arm64, ppc64le, riscv64, s390x] + tags: [containers_image_openpgp, exclude_graphdriver_btrfs] mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}' ldflags: &build-ldflags | -w @@ -167,16 +168,13 @@ sboms: - "--output" - "json=$document" +# the signature, certificate, and transparency log proof all land in a single sigstore bundle. signs: - cmd: .tool/cosign - signature: "${artifact}.sig" - certificate: "${artifact}.pem" + signature: "${artifact}.sigstore.json" args: - "sign-blob" - - "--use-signing-config=false" - - "--oidc-issuer=https://token.actions.githubusercontent.com" - - "--output-certificate=${certificate}" - - "--output-signature=${signature}" + - "--bundle=${signature}" - "${artifact}" - "--yes" artifacts: checksum diff --git a/.make/main.go b/.make/main.go index 58db41a96..73f398454 100644 --- a/.make/main.go +++ b/.make/main.go @@ -1,7 +1,6 @@ package main import ( - "os" "path/filepath" "runtime" "strconv" @@ -17,6 +16,12 @@ import ( "github.com/anchore/go-make/tasks/gotest" ) +// 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( // shared anchore tasks @@ -30,6 +35,7 @@ func main() { gotest.Name("unit"), gotest.ExcludeGlob("**/test/**"), gotest.CoverageThreshold(62), + gotest.Tags(buildTags), race(), ), @@ -53,7 +59,7 @@ func main() { raceFlag = " -race" } Run( - "go test -count=1 -timeout=30m"+raceFlag+" ./cmd/syft/internal/test/integration/...", + "go test -count=1 -timeout=30m -tags="+buildTags+raceFlag+" ./cmd/syft/internal/test/integration/...", run.Env("GODEBUG", "dontfreezetheworld=1"), ) }, @@ -67,7 +73,7 @@ func main() { Log("race detector disabled (RACE=false); skipping race smoke") return } - Run("go run -race cmd/syft/main.go anchore/test_images:grype-quality-dotnet-69f15d2") + Run("go run -race -tags="+buildTags+" cmd/syft/main.go anchore/test_images:grype-quality-dotnet-69f15d2") }, }, @@ -97,19 +103,20 @@ func main() { }, }, - // windows unit tests: deliberately not hooked into "test" (no RunsOn). The full unit suite leans on - // linux docker fixture images and posix-only assumptions, so this covers the packages that own - // host path handling (where windows bugs live) and skips the tests that need docker image fixtures. - // ponytail: curated package list, widen it as more of the suite is made windows-safe. + // containers-storage tests: build a fixture image with BUILDER (podman or buildah) into an isolated local store + // and scan it with the snapshot binary. Linux only, and not hooked into "test" since it needs the builder installed. Task{ - Name: "unit:windows", - Description: "run the windows-relevant subset of unit tests", + Name: "containers-storage-test", + Description: "Run containers-storage tests (BUILDER=podman|buildah)", Run: func() { - Run("go test -count=1"+ - ` -skip "Image|Squash|AllLayers|MixFileTypes|GivenCoordinates"`+ - " ./syft/internal/windows/... ./syft/internal/fileresolver/... ./syft/source/directorysource/... ./syft/source/filesource/..."+ - " ./syft/file ./syft/file/cataloger/filedigest/... ./syft/file/cataloger/filecontent/...", - run.Stdout(os.Stderr), + bin := snapshotBinPath() + if !file.Exists(bin) { + Log("snapshot binary not found at %s; building single-target snapshot", bin) + Run("make snapshot:single-target") + } + Run( + "bash test/containers-storage/source-test.sh "+config.Env("BUILDER", "podman"), + run.Env("SYFT_BINARY_LOCATION", bin), ) }, }, @@ -157,7 +164,7 @@ func main() { Task{ Name: "unit:syft", RunsOn: lang.List("unit"), - Dependencies: Deps("refresh-fixtures"), + Dependencies: unitFixtureDeps(), }, Task{ Name: "clean:syft", @@ -185,6 +192,15 @@ func raceEnabled() bool { return config.CI && !config.Windows } +// unitFixtureDeps skips the fixture cache refresh on windows: it needs binny-managed tools (binny ships +// no windows release) and linux docker, so fixture-backed tests there have to skip themselves instead. +func unitFixtureDeps() []string { + if config.Windows { + return nil + } + return Deps("refresh-fixtures") +} + // race applies raceEnabled() to a gotest suite. gotest exposes no functional option // for the race detector, but gotest.Config.Race is exported. func race() gotest.Option { diff --git a/README.md b/README.md index d864e1428..9ffd2affa 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,18 @@ syft -o cyclonedx-json syft -o spdx-json=./spdx.json -o cyclonedx-json=./cdx.json ``` +### Local containers-storage images + +On Linux, syft 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 +syft --from containers-storage localhost/myimage:latest + +# rootless stores must be read from inside the builder's user namespace +podman unshare syft --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`. > [!TIP] > **Check out the [Getting Started guide](https://oss.anchore.com/docs/guides/sbom/getting-started/)** to explore all of the capabilities and features. diff --git a/cmd/syft/internal/commands/convert.go b/cmd/syft/internal/commands/convert.go index a6189b847..83cba62c5 100644 --- a/cmd/syft/internal/commands/convert.go +++ b/cmd/syft/internal/commands/convert.go @@ -114,6 +114,10 @@ func RunConvert(ctx context.Context, opts *ConvertOptions, userInput string) err if err != nil { return err } + // output files are opened up front; release them if we bail out before writing + if c, ok := writer.(io.Closer); ok { + defer func() { _ = c.Close() }() + } } for _, output := range unchanged { diff --git a/cmd/syft/internal/commands/scan.go b/cmd/syft/internal/commands/scan.go index 7db17fb7d..7f92491ca 100644 --- a/cmd/syft/internal/commands/scan.go +++ b/cmd/syft/internal/commands/scan.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "os" "reflect" "strings" @@ -181,6 +182,10 @@ func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, use if err != nil { return err } + // output files are opened up front; release them if we bail out before writing + if c, ok := writer.(io.Closer); ok { + defer func() { _ = c.Close() }() + } sources := opts.From if len(sources) == 0 { diff --git a/cmd/syft/internal/options/cache_test.go b/cmd/syft/internal/options/cache_test.go index f92f658d0..a42f5f665 100644 --- a/cmd/syft/internal/options/cache_test.go +++ b/cmd/syft/internal/options/cache_test.go @@ -4,6 +4,7 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" "testing" "time" @@ -22,9 +23,10 @@ func Test_defaultDir(t *testing.T) { homeDir := filepath.Join(tmpDir, "fake-home") tests := []struct { - name string - env map[string]string - expected string + name string + env map[string]string + expected string + skipOnWindows bool }{ { name: "no-xdg", @@ -32,6 +34,8 @@ func Test_defaultDir(t *testing.T) { "HOME": homeDir, }, expected: homeDir, + // xdg falls back to LOCALAPPDATA on windows, not HOME + skipOnWindows: true, }, { name: "xdg-cache", @@ -59,6 +63,9 @@ func Test_defaultDir(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if test.skipOnWindows && runtime.GOOS == "windows" { + t.Skip("posix-only cache dir fallback") + } restoreCache(t) for k, v := range test.env { diff --git a/cmd/syft/internal/options/source.go b/cmd/syft/internal/options/source.go index 76cae4580..2c517946b 100644 --- a/cmd/syft/internal/options/source.go +++ b/cmd/syft/internal/options/source.go @@ -36,7 +36,7 @@ var _ clio.PostLoader = (*imageSource)(nil) func (o *sourceConfig) DescribeFields(descriptions clio.FieldDescriptionSet) { descriptions.Add(&o.File.Digests, `the file digest algorithms to use on the scanned file (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")`) descriptions.Add(&o.Image.DefaultPullSource, `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`) } type imageSource struct { @@ -74,7 +74,7 @@ func (c *imageSource) PostLoad() error { return checkDefaultSourceValues(c.DefaultPullSource) } -var validDefaultSourceValues = []string{"registry", "docker", "podman", ""} +var validDefaultSourceValues = []string{"registry", "docker", "podman", "containers-storage", ""} func checkDefaultSourceValues(source string) error { validValues := strset.New(validDefaultSourceValues...) diff --git a/cmd/syft/internal/options/writer.go b/cmd/syft/internal/options/writer.go index d53d3bfcc..f82530732 100644 --- a/cmd/syft/internal/options/writer.go +++ b/cmd/syft/internal/options/writer.go @@ -2,10 +2,11 @@ package options import ( "bytes" + "errors" "fmt" "io" "os" - "path" + "path/filepath" "sort" "strings" @@ -20,7 +21,10 @@ import ( "github.com/anchore/syft/syft/sbom" ) -var _ sbom.Writer = (*sbomMultiWriter)(nil) +var _ interface { + io.Closer + sbom.Writer +} = (*sbomMultiWriter)(nil) var _ interface { io.Closer @@ -164,6 +168,12 @@ func newSBOMMultiWriter(options ...sbomWriterDescription) (_ *sbomMultiWriter, e } out := &sbomMultiWriter{} + defer func() { + // don't leak the files already opened when a later output fails + if err != nil { + _ = out.Close() + } + }() for _, option := range options { switch len(option.Path) { @@ -172,8 +182,8 @@ func newSBOMMultiWriter(options ...sbomWriterDescription) (_ *sbomMultiWriter, e format: option.Format, }) default: - // create any missing subdirectories - dir := path.Dir(option.Path) + // create any missing subdirectories (option.Path is a host path, so this must be filepath) + dir := filepath.Dir(option.Path) if dir != "" { s, err := os.Stat(dir) if err != nil { @@ -210,6 +220,20 @@ func (m *sbomMultiWriter) Write(s sbom.SBOM) (errs error) { return errs } +// Close releases any files held by the writers. Write already closes each file it writes to, so this matters +// when the SBOM is never written (e.g. an error after the writer was created). +func (m *sbomMultiWriter) Close() error { + var errs error + for _, w := range m.writers { + if c, ok := w.(io.Closer); ok { + if err := c.Close(); err != nil && !errors.Is(err, os.ErrClosed) { + errs = multierror.Append(errs, err) + } + } + } + return errs +} + // sbomStreamWriter implements sbom.Writer for a given format and io.Writer, also providing a close function for cleanup type sbomStreamWriter struct { format sbom.FormatEncoder diff --git a/cmd/syft/internal/options/writer_test.go b/cmd/syft/internal/options/writer_test.go index 44d2dc213..b0ce9ad1b 100644 --- a/cmd/syft/internal/options/writer_test.go +++ b/cmd/syft/internal/options/writer_test.go @@ -202,9 +202,9 @@ func Test_newSBOMMultiWriter(t *testing.T) { if test.err { assert.Error(t, err) return - } else { - assert.NoError(t, err) } + require.NoError(t, err) + t.Cleanup(func() { _ = mw.Close() }) assert.Len(t, mw.writers, len(test.expected)) diff --git a/go.mod b/go.mod index 09b8346f3..996aa4ad6 100644 --- a/go.mod +++ b/go.mod @@ -18,16 +18,16 @@ require ( github.com/anchore/go-homedir v0.1.1 github.com/anchore/go-logger v0.2.0 github.com/anchore/go-macholibre v0.1.1 - github.com/anchore/go-rpmdb v0.2.0 + github.com/anchore/go-rpmdb v0.2.2-0.20261002172926-a488674d6c87 github.com/anchore/go-sync v0.1.2 github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b github.com/anchore/packageurl-go v0.2.0 - github.com/anchore/stereoscope v0.3.2 + github.com/anchore/stereoscope v0.3.4-0.20261002170525-59d3a69590a6 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be github.com/aquasecurity/go-pep440-version v0.0.1 github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb - github.com/bmatcuk/doublestar/v4 v4.10.0 + github.com/bmatcuk/doublestar/v4 v4.10.2 github.com/cespare/xxhash/v2 v2.3.0 github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/bubbletea v1.3.10 @@ -62,7 +62,7 @@ require ( github.com/jedib0t/go-pretty/v6 v6.8.3 github.com/jinzhu/copier v0.4.0 github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 - github.com/klauspost/compress v1.20.0 + github.com/klauspost/compress v1.20.1 github.com/magiconair/properties v1.18.12 github.com/mholt/archives v0.1.5 github.com/moby/sys/mountinfo v0.7.2 @@ -83,7 +83,7 @@ require ( github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.12.1 - github.com/ulikunitz/xz v0.5.16 + github.com/ulikunitz/xz v0.5.17 github.com/vbatts/go-mtree v0.7.0 github.com/vifraa/gopom v1.0.0 github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 @@ -115,6 +115,7 @@ require ( cloud.google.com/go/iam v1.12.0 // indirect cloud.google.com/go/monitoring v1.30.0 // indirect cloud.google.com/go/storage v1.65.1 // indirect + cyphar.com/go-pathrs v0.2.5 // indirect dario.cat/mergo v1.0.2 // indirect github.com/DataDog/zstd v1.5.5 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect @@ -122,9 +123,10 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect - github.com/Microsoft/hcsshim v0.15.0-rc.1 // indirect + github.com/Microsoft/hcsshim v0.15.0-rc.4 // indirect github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/STARRY-S/zip v0.2.3 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect github.com/agext/levenshtein v1.2.1 // indirect github.com/anchore/go-lzo v0.1.1 // indirect github.com/anchore/go-struct-converter v0.2.0-rc2 // indirect @@ -169,20 +171,25 @@ require ( github.com/cloudflare/circl v1.6.3 // indirect github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect github.com/containerd/cgroups/v3 v3.1.3 // indirect - github.com/containerd/containerd/api v1.11.1 // indirect - github.com/containerd/containerd/v2 v2.3.6 // indirect + github.com/containerd/containerd/api v1.12.0 // indirect + github.com/containerd/containerd/v2 v2.4.1 // indirect github.com/containerd/continuity v0.5.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect - github.com/containerd/log v0.1.0 // indirect + github.com/containerd/log v0.2.0 // indirect + github.com/containerd/log/otel v0.1.0 // indirect github.com/containerd/platforms v1.0.0-rc.5 // indirect github.com/containerd/plugin v1.1.0 // indirect - github.com/containerd/ttrpc v1.2.8 // indirect - github.com/containerd/typeurl/v2 v2.2.3 // indirect - github.com/cyphar/filepath-securejoin v0.6.1 // indirect - github.com/docker/cli v29.8.0+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.5 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect + github.com/containerd/ttrpc v1.2.9 // indirect + github.com/containerd/typeurl/v2 v2.3.0 // indirect + github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect + github.com/containers/ocicrypt v1.3.2 // indirect + github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect + github.com/cyphar/filepath-securejoin v0.7.0 // indirect + github.com/docker/cli v29.8.1+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 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect @@ -192,17 +199,17 @@ require ( github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fatih/color v1.19.0 // indirect github.com/felixge/fgprof v0.9.5 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect github.com/gkampitakis/ciinfo v0.3.4 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-jose/go-jose/v4 v4.1.5 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/go-intervals v0.0.2 // indirect github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.20 // indirect @@ -226,10 +233,13 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect - github.com/mattn/go-runewidth v0.0.21 // indirect + github.com/mattn/go-runewidth v0.0.27 // indirect + github.com/mattn/go-sqlite3 v1.14.48 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/miekg/pkcs11 v1.1.2 // indirect github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/minlz v1.0.1 // indirect + github.com/mistifyio/go-zfs/v4 v4.0.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect @@ -238,10 +248,10 @@ require ( github.com/moby/locker v1.0.1 // indirect github.com/moby/moby/api v1.56.0 // indirect github.com/moby/moby/client v0.6.0 // indirect - github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/signal v0.7.1 // indirect - github.com/moby/sys/user v0.4.0 // indirect - github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/sys/user v0.4.1 // indirect + github.com/moby/sys/userns v0.2.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect @@ -254,6 +264,7 @@ require ( github.com/olekukonko/ll v0.1.6 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.3.0 // indirect + github.com/opencontainers/selinux v1.15.1 // indirect github.com/pborman/indent v1.2.1 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pierrec/lz4/v4 v4.1.26 // indirect @@ -264,46 +275,59 @@ require ( github.com/pkg/xattr v0.4.12 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect + github.com/proglottis/gpgme v0.1.6 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.4 // 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/sirupsen/logrus v1.10.2 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect + github.com/smallstep/pkcs7 v0.2.1 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect + 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/squashfs v1.0.6 // indirect + github.com/tchap/go-patricia/v2 v2.3.3 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/tidwall/gjson v1.19.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect + github.com/vbatts/tar-split v0.12.3 // indirect + github.com/vbauerster/cupwriter v0.0.4 // indirect + github.com/vbauerster/mpb/v8 v8.14.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zclconf/go-cty v1.19.0 // indirect - go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect - go.opentelemetry.io/otel v1.45.0 // indirect - go.opentelemetry.io/otel/metric v1.45.0 // indirect - go.opentelemetry.io/otel/sdk v1.45.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect - go.opentelemetry.io/otel/trace v1.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect + go.opentelemetry.io/otel v1.46.0 // indirect + go.opentelemetry.io/otel/metric v1.46.0 // indirect + go.opentelemetry.io/otel/sdk v1.46.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.46.0 // indirect + go.opentelemetry.io/otel/trace v1.46.0 // indirect + go.podman.io/image/v5 v5.41.2 // indirect + go.podman.io/storage v1.64.1 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/crypto v0.57.0 // indirect @@ -316,8 +340,8 @@ require ( gonum.org/v1/gonum v0.17.0 // indirect google.golang.org/api v0.294.0 // indirect google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index b66b9b51a..b82ae0667 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ cloud.google.com/go/storage v1.65.1 h1:LRRpBJUTf+OXDPX9jZUKZ3mSLIsz3htG+qUpeNZov cloud.google.com/go/storage v1.65.1/go.mod h1:UsS9OgFg/XHOSYakQ8ZtLWWeyGkk1WnmD/GsGfN0BHM= cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E= cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0= +cyphar.com/go-pathrs v0.2.5 h1:SnX9FBvnoyn3lUs1dkMgZ52bAETpirNu3FTRh5HlRik= +cyphar.com/go-pathrs v0.2.5/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -99,8 +101,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 h1:0kQAzHq8vLs7Pptv+7TxjdETLf/nIqJpIB4oC6Ba4vY= github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= -github.com/Microsoft/hcsshim v0.15.0-rc.1 h1:FbbwtQmiD+BVHynGkx5S65JkLyhkEiiTP8nrpmg2SZw= -github.com/Microsoft/hcsshim v0.15.0-rc.1/go.mod h1:HWvvUPIy9HF6LotILj1G4VyS065rcLQ6tqj6tMUdOfI= +github.com/Microsoft/hcsshim v0.15.0-rc.4 h1:aZFX4LH0S20Lgjq0wG61StIClj7im4yzrxIClkaR8Z8= +github.com/Microsoft/hcsshim v0.15.0-rc.4/go.mod h1:BA9CBztgu4h/6Jsvo1O1M4qjWw09PoYpaEYgexPE578= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -108,6 +110,8 @@ github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67 github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE= @@ -136,8 +140,8 @@ github.com/anchore/go-lzo v0.1.1 h1:IwL/fvkdtlIrYIXck6WxZ3nb8WjjHziYYmGxlooyOnM= 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.0 h1:ixcyMg/guhBL9XjBVwhTv9CG48xO07Qkg2uDX2+zx/c= -github.com/anchore/go-rpmdb v0.2.0/go.mod h1:ATsRlpCXstnoYfzqBfhwGw0U2dolx1BBQ9rAU8jWBAw= +github.com/anchore/go-rpmdb v0.2.2-0.20261002172926-a488674d6c87 h1:aY7DcEkjgzMRbCCJsGHu8eQeL5I88V8G9ISEWtfrlCU= +github.com/anchore/go-rpmdb v0.2.2-0.20261002172926-a488674d6c87/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= @@ -146,8 +150,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/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.2 h1:A2FDMSU56m3rErO1e0CtWAVcDS8tc2iX4UXTw1hyPnI= -github.com/anchore/stereoscope v0.3.2/go.mod h1:/IUDVDka+4gufc8kDw+JO3gFl9PkpCjJ2r3bd9UWouM= +github.com/anchore/stereoscope v0.3.4-0.20261002170525-59d3a69590a6 h1:6l3YwREGEXX/w4tBhRcPb7efYWo4pf8+fmAy0mND4EY= +github.com/anchore/stereoscope v0.3.4-0.20261002170525-59d3a69590a6/go.mod h1:LxL4iOFtB8XMbA1MykUUTkmtQmDuYdbr4+TkBkCfljU= 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= @@ -225,8 +229,8 @@ github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef h1:TSFnfbbu2oAO github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= -github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= -github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.10.2 h1:eF7W7HWKg3z9NrWV9pTLnNeoXaqq3Tq9DNKXVMfoCnw= +github.com/bmatcuk/doublestar/v4 v4.10.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4= @@ -294,10 +298,10 @@ github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ= github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= -github.com/containerd/containerd/api v1.11.1 h1:h8nfoDW9+fNsC/9TwiAHj8B1GzXKtR4eFtkhi/X5RLU= -github.com/containerd/containerd/api v1.11.1/go.mod h1:CaQFRu+N1MtbgL6JDOJLUB1hCKESU1lD6MuTJhgtdlw= -github.com/containerd/containerd/v2 v2.3.6 h1:huoqZXaDW1x1kgPvFC4rMwxv92vUJnqgq7I5b1rSddM= -github.com/containerd/containerd/v2 v2.3.6/go.mod h1:qaUSWKk9EchqbudSbhv1MxI8dXKGYHLpZtMQdkEkVZ4= +github.com/containerd/containerd/api v1.12.0 h1:kuQm82SbDrCuO4n7hf2L8zsBtZLuympyq5X/VotfX2A= +github.com/containerd/containerd/api v1.12.0/go.mod h1:EBcSzoi9Vl18cdODaXUCskf3D2NT8lsSXeZJnU5jIUc= +github.com/containerd/containerd/v2 v2.4.1 h1:DUx/ZJN7cEu0WuzHClDB+68H/bqMEH5pWoEjf0ae4hc= +github.com/containerd/containerd/v2 v2.4.1/go.mod h1:vgLdtvl3prFk1d3ZVqQcsDD5Q9IKM+vAIdU54U85w+I= github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg= github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -306,23 +310,33 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc= +github.com/containerd/log v0.2.0/go.mod h1:/M7L7CXKcPTfNC74XzaK+5H5KbO5+4lJVpuVI6vRLoM= +github.com/containerd/log/otel v0.1.0 h1:Az5rFFo0+c4v2yC8ROR63sWsZpKl/vhtUnUhqLSpE6U= +github.com/containerd/log/otel v0.1.0/go.mod h1:65C5iYF2xIQByCyxzoO2KKKK1+/tGxD4XqhdlrTtvzE= github.com/containerd/platforms v1.0.0-rc.5 h1:vXd569rDrz8LeMXzAnBsy6LADV5YtsD8oyaRarxdmSU= github.com/containerd/platforms v1.0.0-rc.5/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A= github.com/containerd/plugin v1.1.0 h1:O+7lczNJVMy8rz0YNx3xGB8tTf5qY4i5abF041Ew19U= github.com/containerd/plugin v1.1.0/go.mod h1:qBTum+A8lJ6lO44A19Eo7y1OlcLj4OWFH1DA/vnHmcc= -github.com/containerd/ttrpc v1.2.8 h1:xbVu6D4qF2jihdh9rDVOKqUMiFBQk6YctTdo1zk087Y= -github.com/containerd/ttrpc v1.2.8/go.mod h1:wyZW2K79t4Hfcxl+GUvkZqRBzJlqFFvgEeeWXa42tyE= -github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= -github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw= +github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY= +github.com/containerd/ttrpc v1.2.9 h1:ha0ak962T0s3CA/RoZ6S6xiWZQF24GrBaEpiGX1uihg= +github.com/containerd/ttrpc v1.2.9/go.mod h1:jjtQRwXm4DL3KsHKW8vDiUOV6wO0hi6IPhmJhxU7aEs= +github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ= +github.com/containerd/typeurl/v2 v2.3.0/go.mod h1:Qk+PAdUYArVj41TnGi6rJ+48RF0PkcTc4i/taoBcK0w= +github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= +github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= +github.com/containers/ocicrypt v1.3.2 h1:MuqHSfiPpGzoAQdgDSX85FgixSgIm9mSDXTLTgugY5E= +github.com/containers/ocicrypt v1.3.2/go.mod h1:ntBZabYG0rlvstB/1rK/ba2laaU5EHE0pD5ioHoPTq4= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= -github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= +github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= +github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -339,10 +353,12 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/docker/cli v29.8.0+incompatible h1:ih0c2jq/nN7QfES8zIfwSzIhRScjs4ehER+kZ60aeSk= -github.com/docker/cli v29.8.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= -github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +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/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-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q= +github.com/docker/docker-credential-helpers v0.9.8/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go-connections v0.8.1 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M= github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -381,8 +397,8 @@ github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMD github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/erofs/go-erofs v0.3.0 h1:o/W5ABAA3sHYl97WL93dacKEfeDpJhdFf3c2snAti7I= -github.com/erofs/go-erofs v0.3.0/go.mod h1:XkSeN9MHszGd4+3gcEjadJLYHCQpWzJ7/8yznzMuzJs= +github.com/erofs/go-erofs v0.3.1 h1:Sux82Jq9yvyYhIoLgSHDp741p/+370HsOj9dAh1+VVs= +github.com/erofs/go-erofs v0.3.1/go.mod h1:XkSeN9MHszGd4+3gcEjadJLYHCQpWzJ7/8yznzMuzJs= github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= github.com/facebookincubator/nvdtools v0.1.5 h1:jbmDT1nd6+k+rlvKhnkgMokrCAzHoASWE5LtHbX2qFQ= github.com/facebookincubator/nvdtools v0.1.5/go.mod h1:Kh55SAWnjckS96TBSrXI99KrEKH4iB0OJby3N8GRJO4= @@ -396,13 +412,13 @@ github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0C github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -412,8 +428,8 @@ github.com/gkampitakis/ciinfo v0.3.4 h1:5eBSibVuSMbb/H6Elc0IIEFbkzCJi3lm94n0+U7Z github.com/gkampitakis/ciinfo v0.3.4/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= github.com/gkampitakis/go-snaps v0.5.23 h1:okh5QR48zpUjpWtu65AtqxdCY8huJq+dEDuUzd1PuKg= github.com/gkampitakis/go-snaps v0.5.23/go.mod h1:uy3lVzCCRRsAwYqSocyw5fY8xRLCYEfqoOJNxr8HonM= -github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= -github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= +github.com/glebarez/go-sqlite v1.23.0 h1:FyhIq4jqmgphQAUlY79zPldYGwISEZikaDfhiGWkkaI= +github.com/glebarez/go-sqlite v1.23.0/go.mod h1:IIYrOH3L0rHY3jb4IXOHoWdklNajSGUN2eJcvK8WrnI= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -427,8 +443,8 @@ github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuw github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= +github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -453,7 +469,6 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg= github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ= @@ -507,10 +522,13 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.22.1 h1:RZuuSYhTvlDvtsK+NkutoCZ//C0X2ebLK8X8l3ULs84= github.com/google/go-containerregistry v0.22.1/go.mod h1:bJR35SK8XgisYmhg/FMQ/5RK0S/XrOAqLBV5/LR2XE0= +github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM= +github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= @@ -543,6 +561,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.20 h1:t/xL64VUoN69MuMRQuJETqYGOw4Z9mSRJK9epIEtwFk= @@ -558,6 +577,8 @@ github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU= github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gpustack/gguf-parser-go v0.26.3 h1:F63PlUPIW56HGU5k4Crv0JAG9ojtz/TuwzgMYlb82Ec= github.com/gpustack/gguf-parser-go v0.26.3/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -645,8 +666,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF 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= @@ -692,8 +713,10 @@ github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsRe github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= -github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= +github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= +github.com/mattn/go-sqlite3 v1.14.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs= +github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -702,10 +725,14 @@ github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPp github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/pkcs11 v1.1.2 h1:/VxmeAX5qU6Q3EwafypogwWbYryHFmF2RpkJmw3m4MQ= +github.com/miekg/pkcs11 v1.1.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= +github.com/mistifyio/go-zfs/v4 v4.0.0 h1:sU0+5dX45tdDK5xNZ3HBi95nxUc48FS92qbIZEvpAg4= +github.com/mistifyio/go-zfs/v4 v4.0.0/go.mod h1:weotFtXTHvBwhr9Mv96KYnDkTPBOHFUbm9cBmQpesL0= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -727,16 +754,16 @@ github.com/moby/moby/api v1.56.0 h1:GQzua3NA599ASSIICx0iFgiJeO9YkdDARvQsm23ZZuQ= github.com/moby/moby/api v1.56.0/go.mod h1:sZ+THbVWkjOmBPPfbnzdD/G1LuIexWhqlSHHPTDQ1Uk= github.com/moby/moby/client v0.6.0 h1:AJjEB21QPbXSXjDsZorFBoDZPhMrfbpaPLgSMAW9Bgs= github.com/moby/moby/client v0.6.0/go.mod h1:OCo00wNRyA3m4lmJ228W3JbyCN4ZNNYjpOXiJydBdcQ= +github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= +github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= -github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= -github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0= +github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y= +github.com/moby/sys/userns v0.2.1 h1:4OvdM7BcPkASbuouHsbW3aeMJSFlYDldBRnXVZhaRk8= +github.com/moby/sys/userns v0.2.1/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -776,6 +803,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.15.1 h1:ERxeh5caJvCzNAKdI8WQbJmB1LDTn4BuaAg8wihLBpA= +github.com/opencontainers/selinux v1.15.1/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -814,6 +843,8 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/proglottis/gpgme v0.1.6 h1:8WpQ8VWggLdxkuTnW+sZ1r1t92XBNd8GZNDhQ4Rz+98= +github.com/proglottis/gpgme v0.1.6/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -826,8 +857,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY= github.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -853,6 +884,8 @@ github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7 github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg= github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= @@ -860,21 +893,31 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd7 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/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= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 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/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= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= +github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d h1:3VwvTjiRPA7cqtgOWddEL+JrcijMlXUmj99c/6YyZoY= github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d/go.mod h1:tAG61zBM1DYRaGIPloumExGvScf08oHuo0kFoOqdbT0= +github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA= +github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0= github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= @@ -904,6 +947,8 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -931,6 +976,8 @@ 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= github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= +github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= +github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/terminalstatic/go-xsd-validate v0.1.8 h1:UVrTCy1j3DhwaYTTUF+QYO/Nan13S0tf+Jwi+p45Bf0= github.com/terminalstatic/go-xsd-validate v0.1.8/go.mod h1:1kb47fi2c6onlf+B7UrrQ9VYraOhcYwFm3iG+J6F4Zo= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= @@ -947,10 +994,16 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.16 h1:ld6NyySjx5lowVKwJvMRLnW5nxKX/xnpSiFYZ/Lxur0= -github.com/ulikunitz/xz v0.5.16/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw= +github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4= +github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw= github.com/vbatts/go-mtree v0.7.0 h1:ytmOc3MTRidZiBi9VBCyZ2BHe4fZS47L5v7BVXDWW4E= github.com/vbatts/go-mtree v0.7.0/go.mod h1:EjdpFC+LZy1TXbRGNa1MKKgjQ+7ew3foMFJK8o4/TdY= +github.com/vbatts/tar-split v0.12.3 h1:Cd46rkGXI3Td4yrVNwU8ripbxFaQbmesqhjBUUYAJSw= +github.com/vbatts/tar-split v0.12.3/go.mod h1:sQOc6OlqGCr7HkGx/IDBeKiTIvqhmj8KffNhEXG4Nq0= +github.com/vbauerster/cupwriter v0.0.4 h1:9sBPe0uXWLZuWQU5lqVbhyFlxX6c09asST/YfatFAys= +github.com/vbauerster/cupwriter v0.0.4/go.mod h1:IFyzS6Xis5dnBH/rdAhrnuzg3c+KkUqEN6yE8lhJlDw= +github.com/vbauerster/mpb/v8 v8.14.0 h1:55SR80dptMfASxIG/oCEkBXgBhxeSu4GrVsjl16oKmA= +github.com/vbauerster/mpb/v8 v8.14.0/go.mod h1:HgpQPKfcWe3kbuGGPmi+jatHreMase5C3Fp5dpdAy0Q= github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0= github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o= github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA= @@ -972,6 +1025,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -994,31 +1049,33 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw= go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 h1:B2h3uqicet1CT2N5TOFhS+Gq++9i0/CLmaxvhmhtP5s= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0/go.mod h1:dylvB+ZiiwMvsDij9O84Uy7SijLgHMX4mbkncds+4Sw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU= +go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= +go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= -go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= -go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= +go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o= +go.opentelemetry.io/otel/metric/x v0.68.0 h1:TA/cBT23D3MnxYPwHL7YFOdYGdx0A0v+s7Mzotpd1dU= +go.opentelemetry.io/otel/metric/x v0.68.0/go.mod h1:agudOmvWhwUTjgibWDzxD2PoWYnpw5Ht5jISYOD2Hd4= +go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI= +go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM= +go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE= +go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= +go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= +go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.podman.io/image/v5 v5.41.2 h1:YnVaFrGLPWSBmipKfNvlOsGcBtk5bUxlPxQ+A6CS/uo= +go.podman.io/image/v5 v5.41.2/go.mod h1:T/bzxEFpdpAzyHabPmcq5K+my3wBsht22sFkFjJSKT4= +go.podman.io/storage v1.64.1 h1:DqbG8scW/bD3xHiSRzV7O5fzn46qVC+SwmUN6C6Fr5w= +go.podman.io/storage v1.64.1/go.mod h1:ft0DzCRMZs1yn0rszmLYXEEwgd7IIc66JRwSRPUDwwc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -1043,6 +1100,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1084,6 +1145,10 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1129,7 +1194,12 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1163,6 +1233,11 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1239,11 +1314,22 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1256,6 +1342,11 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1318,6 +1409,9 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1434,10 +1528,10 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= -google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4= -google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 h1:izFU9hz7aeLI/Mi1J0991ae+xcwRLr7hTqWnB/9aIIU= +google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5/go.mod h1:3LhxRw4YYkf+ylAfgaY9JlVLFKhokkCV8duhLLe7+t0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/install.sh b/install.sh index bf9222d06..88303e4f2 100755 --- a/install.sh +++ b/install.sh @@ -8,7 +8,6 @@ REPO="${PROJECT_NAME}" GITHUB_DOWNLOAD_PREFIX=https://github.com/${OWNER}/${REPO}/releases/download INSTALL_SH_BASE_URL=https://get.anchore.io/${PROJECT_NAME} LEGACY_INSTALL_SH_BASE_URL=https://raw.githubusercontent.com/${OWNER}/${PROJECT_NAME} -PROGRAM_ARGS=$@ # signature verification options @@ -19,6 +18,8 @@ VERIFY_SIGN=false VERIFY_SIGN_SUPPORTED_VERSION=v0.104.0 # this is the earliest tag in the repo where the -v flag was introduced to this install.sh script VERIFY_SIGN_FLAG_VERSION=v1.6.0 +# releases at or after this version are signed with a sigstore bundle (.sigstore.json); earlier releases ship a separate .sig and .pem +VERIFY_SIGN_BUNDLE_VERSION=v1.53.0 # do not change the name of this parameter (this must always be backwards compatible) DOWNLOAD_TAG_INSTALL_SCRIPT=${DOWNLOAD_TAG_INSTALL_SCRIPT:-true} @@ -386,8 +387,8 @@ download_github_release_checksums_files() ( complete_url=$(github_release_asset_url "${download_url}" "${name}" "${version}" "${filename}") output_path="${output_dir}/${complete_filename}" - http_download "${output_path}" "${complete_url}" "" - asset_file_exists "${output_path}" + http_download "${output_path}" "${complete_url}" "" || return 1 + asset_file_exists "${output_path}" || return 1 log_trace "download_github_release_checksums_files() returned '${output_path}' for file '${complete_filename}'" @@ -585,28 +586,31 @@ download_and_install_asset() ( install_asset "${asset_filepath}" "${install_path}" "${binary}" ) -# verify_sign [checksums-file-path] [certificate-reference] [signature-reference] [version] +# verify_sign [checksums-file-path] [verification-material-flags...] # # attempts verify the signature of the checksums file from the release workflow in Github Actions run against the main branch. +# the verification material is either "--bundle " or (for older releases) "--certificate --signature ". # verify_sign() { checksums_file=$1 - cert_reference=$2 - sig_reference=$3 + shift - log_trace "verifying artifact $1" + log_trace "verifying artifact ${checksums_file}" log_file=$(mktemp) ${COSIGN_BINARY} \ verify-blob "$checksums_file" \ - --certificate "$cert_reference" \ - --signature "$sig_reference" \ + "$@" \ --certificate-identity "https://github.com/${OWNER}/${REPO}/.github/workflows/release.yaml@refs/heads/main" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" > "${log_file}" 2>&1 if [ $? -ne 0 ]; then log_err "$(cat "${log_file}")" + # cosign older than v2.4.2 can't read the bundle's verification material and reports a missing cert + if [ "$1" = "--bundle" ] && grep -q "bundle does not contain cert" "${log_file}"; then + log_err "note: releases >= ${VERIFY_SIGN_BUNDLE_VERSION} are verified with a sigstore bundle, which requires cosign v2.5.0 or newer (check '${COSIGN_BINARY} version')" + fi rm -f "${log_file}" return 1 fi @@ -630,7 +634,10 @@ download_asset() ( log_trace "download_asset(url=${download_url}, destination=${destination}, name=${name}, os=${os}, arch=${arch}, version=${version}, format=${format})" - checksums_filepath=$(download_github_release_checksums "${download_url}" "${name}" "${version}" "${destination}") + if ! checksums_filepath=$(download_github_release_checksums "${download_url}" "${name}" "${version}" "${destination}"); then + log_err "unable to download checksums file" + return 1 + fi log_trace "checksums content:\n$(cat ${checksums_filepath})" @@ -642,13 +649,25 @@ download_asset() ( fi if [ "$VERIFY_SIGN" = true ]; then - checksum_sig_file_url=$(github_release_checksums_sig_url "${download_url}" "${name}" "${version}") - log_trace "checksums signature url: ${checksum_sig_file_url}" + if compare_semver "${version}" "${VERIFY_SIGN_BUNDLE_VERSION}"; then + if ! checksums_bundle_filepath=$(download_github_release_checksums_files "${download_url}" "${name}" "${version}" "${destination}" "checksums.txt.sigstore.json"); then + log_err "unable to download checksums signature bundle" + return 1 + fi + log_trace "checksums bundle: ${checksums_bundle_filepath}" - checksums_cert_file_url=$(github_release_checksums_cert_url "${download_url}" "${name}" "${version}") - log_trace "checksums certificate url: ${checksums_cert_file_url}" + set -- --bundle "${checksums_bundle_filepath}" + else + checksum_sig_file_url=$(github_release_checksums_sig_url "${download_url}" "${name}" "${version}") + log_trace "checksums signature url: ${checksum_sig_file_url}" - if ! verify_sign "${checksums_filepath}" "${checksums_cert_file_url}" "${checksum_sig_file_url}"; then + checksums_cert_file_url=$(github_release_checksums_cert_url "${download_url}" "${name}" "${version}") + log_trace "checksums certificate url: ${checksums_cert_file_url}" + + set -- --certificate "${checksums_cert_file_url}" --signature "${checksum_sig_file_url}" + fi + + if ! verify_sign "${checksums_filepath}" "$@"; then log_err "signature verification failed" return 1 fi @@ -657,9 +676,15 @@ download_asset() ( asset_url="${download_url}/${asset_filename}" asset_filepath="${destination}/${asset_filename}" - http_download "${asset_filepath}" "${asset_url}" "" + if ! http_download "${asset_filepath}" "${asset_url}" ""; then + log_err "unable to download asset '${asset_url}'" + return 1 + fi - hash_sha256_verify "${asset_filepath}" "${checksums_filepath}" + # this is what ties the asset to the (optionally signature verified) checksums file, so a mismatch must stop the install + if ! hash_sha256_verify "${asset_filepath}" "${checksums_filepath}"; then + return 1 + fi log_trace "download_asset_by_checksums_file() returned '${asset_filepath}'" @@ -765,7 +790,7 @@ prep_signature_verification() { fi } -main() ( +parse_install_args() { # parse arguments # note: never change default install directory (this must always be backwards compatible) @@ -805,6 +830,11 @@ EOF set +u tag=$1 + set -u +} + +main() ( + parse_install_args "$@" if [ -z "${tag}" ]; then log_info "checking github for the current release tag" @@ -812,7 +842,6 @@ EOF else log_info "checking github for release tag='${tag}'" fi - set -u if ! tag=$(get_release_tag "${OWNER}" "${REPO}" "${tag}"); then log_err "unable to find tag='${tag}'" @@ -843,7 +872,7 @@ EOF log_warn "failed to fetch from ${INSTALL_SH_BASE_URL}, trying fallback URL" install_script=$(http_copy "${LEGACY_INSTALL_SH_BASE_URL}/${tag}/install.sh" "") fi - echo "${install_script}" | sh -s -- ${PROGRAM_ARGS} + echo "${install_script}" | sh -s -- "$@" exit $? fi diff --git a/internal/cache/filesystem_test.go b/internal/cache/filesystem_test.go index 47c693042..046341434 100644 --- a/internal/cache/filesystem_test.go +++ b/internal/cache/filesystem_test.go @@ -6,6 +6,7 @@ import ( "net/url" "os" "path/filepath" + "runtime" "strings" "testing" "time" @@ -95,6 +96,9 @@ func Test_makeDiskKey(t *testing.T) { } func Test_errors(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("windows ignores unix permission bits on directories") + } tmp := t.TempDir() cache := filepath.Join(tmp, "cache") // make a non-writable directory diff --git a/internal/capabilities/generate/discover_catalogers.go b/internal/capabilities/generate/discover_catalogers.go index 3dc5419c7..789d7dd4e 100644 --- a/internal/capabilities/generate/discover_catalogers.go +++ b/internal/capabilities/generate/discover_catalogers.go @@ -212,7 +212,7 @@ func parseGenericCatalogerFunction(funcDecl *ast.FuncDecl, filePath, repoRoot st // extractPackageNameFromPath extracts the package name from a cataloger source file path // e.g., "syft/pkg/cataloger/swift/cataloger.go" -> "swift" func extractPackageNameFromPath(filePath string) string { - parts := strings.Split(filePath, string(filepath.Separator)) + parts := strings.Split(filepath.ToSlash(filePath), "/") // find the LAST occurrence of "cataloger" in the path // (to handle test fixtures with multiple "cataloger" segments) diff --git a/internal/capabilities/generate/discover_catalogers_test.go b/internal/capabilities/generate/discover_catalogers_test.go index 0672e9f6f..e953be131 100644 --- a/internal/capabilities/generate/discover_catalogers_test.go +++ b/internal/capabilities/generate/discover_catalogers_test.go @@ -4,6 +4,7 @@ import ( "go/ast" "go/parser" "go/token" + "path/filepath" "testing" "github.com/stretchr/testify/require" @@ -337,7 +338,7 @@ func TestResolveImportPath(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := resolveImportPath(tt.importPath, testRepoRoot) - require.Equal(t, tt.want, got) + require.Equal(t, filepath.FromSlash(tt.want), got) }) } } diff --git a/internal/capabilities/generate/merge.go b/internal/capabilities/generate/merge.go index b5d3b9b02..678c7eb29 100644 --- a/internal/capabilities/generate/merge.go +++ b/internal/capabilities/generate/merge.go @@ -502,6 +502,19 @@ func convertToJSONSchemaTypesFromMetadata(metadataTypes []string) []string { return result } +// packageTypeConstName renders a package type using its go constant name, which is what the detector +// "type" field reports (e.g. "BinaryPkg"). +// a switch rather than a derivation, since pkg.Type values don't reliably map back to their constant +// names (e.g. "java-archive" is JavaPkg); add an arm when a classifier declares a new type. +func packageTypeConstName(t pkg.Type) string { + switch t { + case pkg.CpanPkg: + return "CpanPkg" + default: + return "BinaryPkg" + } +} + // EnrichWithBinaryClassifier enriches an entry with binary classifier detectors if it's the binary-classifier-cataloger func (e *EnrichmentData) EnrichWithBinaryClassifier(catalogerName string, entry *capabilities.CatalogerEntry) { // special handling for binary-classifier-cataloger: auto-generate one detector per classifier @@ -523,7 +536,7 @@ func (e *EnrichmentData) EnrichWithBinaryClassifier(catalogerName string, entry Name: classifier.Package, PURL: purlStr, CPEs: cpeStrings, - Type: "BinaryPkg", + Type: packageTypeConstName(classifier.PackageType()), }, } diff --git a/internal/file/squashfs.go b/internal/file/squashfs.go index 303dde993..7aef361dc 100644 --- a/internal/file/squashfs.go +++ b/internal/file/squashfs.go @@ -4,7 +4,7 @@ import ( "errors" "io/fs" "os" - "path/filepath" + "path" "strings" "github.com/diskfs/go-diskfs/filesystem" @@ -25,7 +25,8 @@ func WalkDiskDir(fsys filesystem.FileSystem, root string, fn WalkDiskDirFunc) er } for _, info := range infos { - p := filepath.Join(root, info.Name()) + // squashfs paths are posix regardless of host os + p := path.Join(root, info.Name()) err = walkDiskDir(fsys, p, info, fn) if err != nil { if errors.Is(err, fs.SkipDir) { @@ -74,7 +75,7 @@ func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDir } for _, d1 := range dirs { - name1 := filepath.Join(name, d1.Name()) + name1 := path.Join(name, d1.Name()) if err := walkDiskDir(fsys, name1, d1, walkDirFn); err != nil { if errors.Is(err, fs.SkipDir) { break diff --git a/internal/file/squashfs_test.go b/internal/file/squashfs_test.go index a2be519dc..265919187 100644 --- a/internal/file/squashfs_test.go +++ b/internal/file/squashfs_test.go @@ -19,6 +19,8 @@ func createTestFS(t *testing.T) filesystem.FileSystem { filename := "test.squashfs" f, err := os.Create(filepath.Join(dir, filename)) require.NoError(t, err) + // windows can't remove the temp dir while the backing file is open + t.Cleanup(func() { _ = f.Close() }) b := file.New(f, false) fsys, err := squashfs.Create(b, 0, 0, 4096) diff --git a/internal/file/tar_file_traversal.go b/internal/file/tar_file_traversal.go index d4d496548..a0c083e4c 100644 --- a/internal/file/tar_file_traversal.go +++ b/internal/file/tar_file_traversal.go @@ -84,7 +84,8 @@ func ExtractGlobsFromTarToUniqueTempFile(ctx context.Context, archivePath, dir s func matchesAnyGlob(name string, globs ...string) bool { for _, glob := range globs { - if matches, err := doublestar.PathMatch(glob, name); err == nil && matches { + // tar entry names are always posix, so don't match with the host separator (PathMatch) + if matches, err := doublestar.Match(glob, name); err == nil && matches { return true } } diff --git a/internal/os/feature_detection_test.go b/internal/os/feature_detection_test.go index b4f2ed38d..15a8f6b44 100644 --- a/internal/os/feature_detection_test.go +++ b/internal/os/feature_detection_test.go @@ -16,6 +16,7 @@ import ( "github.com/anchore/syft/internal/os" "github.com/anchore/syft/internal/sbomsync" "github.com/anchore/syft/internal/task" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" @@ -84,6 +85,7 @@ func Test_EnvironmentTask(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) tarPath := imagetest.GetFixtureImageTarPath(t, test.name) // get the source diff --git a/internal/testutils/docker.go b/internal/testutils/docker.go new file mode 100644 index 000000000..8279a7723 --- /dev/null +++ b/internal/testutils/docker.go @@ -0,0 +1,30 @@ +package testutils + +import ( + "os/exec" + "runtime" + "strings" + "sync" + "testing" +) + +var dockerServerOS = sync.OnceValue(func() string { + out, err := exec.Command("docker", "version", "--format", "{{.Server.Os}}").Output() + if err != nil { + return "" + } + return strings.TrimSpace(string(out)) +}) + +// SkipWithoutLinuxContainers skips tests whose fixtures are linux container images (built or run via docker). +// This only kicks in on windows, where the docker daemon is often running windows containers (or is absent), +// so the linux fixture images can't be built. Everywhere else the fixture tooling is left to fail loudly. +func SkipWithoutLinuxContainers(t testing.TB) { + t.Helper() + if runtime.GOOS != "windows" { + return + } + if os := dockerServerOS(); os != "linux" { + t.Skipf("linux container fixtures unavailable (docker server os=%q)", os) + } +} diff --git a/internal/tmpdir/tmpdir_test.go b/internal/tmpdir/tmpdir_test.go index aeb208403..81142dfe5 100644 --- a/internal/tmpdir/tmpdir_test.go +++ b/internal/tmpdir/tmpdir_test.go @@ -265,8 +265,9 @@ func TestLazyCreation(t *testing.T) { // root dir is not created until needed assert.Equal(t, "", td.root) - _, _, err := td.NewFile("trigger-*") + f, _, err := td.NewFile("trigger-*") require.NoError(t, err) + require.NoError(t, f.Close()) assert.NotEmpty(t, td.root) diff --git a/syft/file/cataloger/filedigest/cataloger_test.go b/syft/file/cataloger/filedigest/cataloger_test.go index 6188bd066..2c63d3bdb 100644 --- a/syft/file/cataloger/filedigest/cataloger_test.go +++ b/syft/file/cataloger/filedigest/cataloger_test.go @@ -15,6 +15,7 @@ import ( stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/imagetest" intFile "github.com/anchore/syft/internal/file" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/internal/unknown" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/source" @@ -92,6 +93,7 @@ func TestDigestsCataloger(t *testing.T) { func TestDigestsCataloger_MixFileTypes(t *testing.T) { testImage := "image-file-type-mix" + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", testImage) src := stereoscopesource.New(img, stereoscopesource.ImageConfig{ @@ -162,6 +164,7 @@ func TestDigestsCataloger_MixFileTypes(t *testing.T) { func TestFileDigestCataloger_GivenCoordinates(t *testing.T) { testImage := "image-file-type-mix" + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", testImage) c := NewCataloger([]crypto.Hash{crypto.SHA256}) diff --git a/syft/file/cataloger/filemetadata/cataloger_test.go b/syft/file/cataloger/filemetadata/cataloger_test.go index fee17c0b1..3547a043a 100644 --- a/syft/file/cataloger/filemetadata/cataloger_test.go +++ b/syft/file/cataloger/filemetadata/cataloger_test.go @@ -10,6 +10,7 @@ import ( stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source/stereoscopesource" @@ -18,6 +19,7 @@ import ( func TestFileMetadataCataloger(t *testing.T) { testImage := "image-file-type-mix" + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", testImage) c := NewCataloger() @@ -157,6 +159,7 @@ func TestFileMetadataCataloger(t *testing.T) { func TestFileMetadataCataloger_GivenCoordinates(t *testing.T) { testImage := "image-file-type-mix" + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", testImage) c := NewCataloger() diff --git a/syft/file/cataloger/internal/all_regular_files_test.go b/syft/file/cataloger/internal/all_regular_files_test.go index cec05bf61..f421ba9c1 100644 --- a/syft/file/cataloger/internal/all_regular_files_test.go +++ b/syft/file/cataloger/internal/all_regular_files_test.go @@ -2,6 +2,7 @@ package internal import ( "context" + "runtime" "testing" "github.com/google/go-cmp/cmp" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source/directorysource" @@ -19,15 +21,16 @@ import ( func Test_allRegularFiles(t *testing.T) { tests := []struct { name string - setup func() file.Resolver + setup func(t *testing.T) file.Resolver wantRealPaths *strset.Set wantAccessPaths *strset.Set }{ { name: "image", - setup: func() file.Resolver { + setup: func(t *testing.T) file.Resolver { testImage := "image-file-type-mix" + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", testImage) s := stereoscopesource.New(img, stereoscopesource.ImageConfig{ @@ -44,7 +47,12 @@ func Test_allRegularFiles(t *testing.T) { }, { name: "directory", - setup: func() file.Resolver { + setup: func(t *testing.T) file.Resolver { + if runtime.GOOS == "windows" { + // file symlinks from a git checkout don't index as access paths on windows yet (same gap the + // fileresolver symlink fixtures skip for) + t.Skip("fixture relies on symlinks") + } s, err := directorysource.NewFromPath("testdata/symlinked-root/nested/link-root") require.NoError(t, err) r, err := s.FileResolver(source.SquashedScope) @@ -57,7 +65,7 @@ func Test_allRegularFiles(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - resolver := tt.setup() + resolver := tt.setup(t) locations := AllRegularFiles(context.Background(), resolver) realLocations := strset.New() virtualLocations := strset.New() diff --git a/syft/format/github/encoder_test.go b/syft/format/github/encoder_test.go index 865cce073..ce63d881c 100644 --- a/syft/format/github/encoder_test.go +++ b/syft/format/github/encoder_test.go @@ -2,6 +2,7 @@ package github import ( "flag" + "runtime" "testing" "github.com/anchore/syft/syft/format/internal/testutil" @@ -11,6 +12,10 @@ var updateSnapshot = flag.Bool("update-github", false, "update the *.golden file var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing") func TestGithubDirectoryEncoder(t *testing.T) { + if runtime.GOOS == "windows" { + // the golden snapshot embeds the posix source path (and the source ID derived from it) + t.Skip("directory snapshot is posix path specific") + } dir := t.TempDir() testutil.AssertEncoderAgainstGoldenSnapshot(t, testutil.EncoderSnapshotTestConfig{ diff --git a/syft/format/internal/testutil/image_input.go b/syft/format/internal/testutil/image_input.go index 881c0952a..08306a024 100644 --- a/syft/format/internal/testutil/image_input.go +++ b/syft/format/internal/testutil/image_input.go @@ -11,6 +11,7 @@ import ( "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" @@ -35,6 +36,7 @@ func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBO case true: img = imagetest.GetGoldenFixtureImage(t, testImage) default: + testutils.SkipWithoutLinuxContainers(t) img = imagetest.GetFixtureImage(t, "docker-archive", testImage) } diff --git a/syft/format/syftjson/encoder_test.go b/syft/format/syftjson/encoder_test.go index 4e6ce11ca..b8b4ad71b 100644 --- a/syft/format/syftjson/encoder_test.go +++ b/syft/format/syftjson/encoder_test.go @@ -5,6 +5,7 @@ import ( "context" "flag" "fmt" + "runtime" "strings" "testing" @@ -115,6 +116,10 @@ func TestEncode_sameFileInManyArchivesIsReproducible(t *testing.T) { } func TestDirectoryEncoder(t *testing.T) { + if runtime.GOOS == "windows" { + // the golden snapshot embeds the posix source path (and the source ID derived from it) + t.Skip("directory snapshot is posix path specific") + } cfg := DefaultEncoderConfig() cfg.Pretty = true enc, err := NewFormatEncoderWithConfig(cfg) diff --git a/syft/format/text/encoder_test.go b/syft/format/text/encoder_test.go index 2a11f1eb4..6ad39a31d 100644 --- a/syft/format/text/encoder_test.go +++ b/syft/format/text/encoder_test.go @@ -2,6 +2,7 @@ package text import ( "flag" + "runtime" "testing" "github.com/anchore/syft/syft/format/internal/testutil" @@ -11,6 +12,10 @@ var updateSnapshot = flag.Bool("update-text", false, "update the *.golden files var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing") func TestTextDirectoryEncoder(t *testing.T) { + if runtime.GOOS == "windows" { + // the golden snapshot embeds the posix source path (and the source ID derived from it) + t.Skip("directory snapshot is posix path specific") + } dir := t.TempDir() testutil.AssertEncoderAgainstGoldenSnapshot(t, testutil.EncoderSnapshotTestConfig{ diff --git a/syft/get_source_config.go b/syft/get_source_config.go index 7a163f1d1..735dba516 100644 --- a/syft/get_source_config.go +++ b/syft/get_source_config.go @@ -3,6 +3,7 @@ package syft import ( "crypto" "fmt" + "slices" "github.com/anchore/go-collections" "github.com/anchore/stereoscope/pkg/image" @@ -10,6 +11,11 @@ import ( "github.com/anchore/syft/syft/source/sourceproviders" ) +// GetSourceConfig controls which source providers are tried, and in what order, when resolving user input to a source. +// +// The "containers-storage" provider (local Buildah / Podman store) is never part of automatic resolution: it is only +// used when named in Sources or DefaultImagePullSource. It is also only functional when built with the +// containers_image_openpgp build tag (as syft's Linux release binaries are); otherwise it always returns an error. type GetSourceConfig struct { // SourceProviderConfig may optionally be provided to be used when constructing the default set of source providers, unused if All specified SourceProviderConfig *sourceproviders.Config @@ -64,6 +70,12 @@ func (c *GetSourceConfig) WithDefaultImagePullSource(defaultImagePullSource stri func (c *GetSourceConfig) getProviders(userInput string) ([]source.Provider, error) { providers := collections.TaggedValueSet[source.Provider]{}.Join(sourceproviders.All(userInput, c.SourceProviderConfig)...) + // opening a containers-storage store runs full graph driver init (mounts, locks, possible store writes), which is + // too invasive for an "is this image local?" probe on every plain image reference. Only use it when asked by name. + if !c.requestsSource(image.ContainersStorageSource) { + providers = providers.Remove(image.ContainersStorageSource) + } + // if the "default image pull source" is set, we move this as the first pull source if c.DefaultImagePullSource != "" { base := providers.Remove(sourceproviders.PullTag) @@ -84,6 +96,10 @@ func (c *GetSourceConfig) getProviders(userInput string) ([]source.Provider, err return providers.Values(), nil } +func (c *GetSourceConfig) requestsSource(name string) bool { + return c.DefaultImagePullSource == name || slices.Contains(c.Sources, name) +} + func DefaultGetSourceConfig() *GetSourceConfig { return &GetSourceConfig{ SourceProviderConfig: sourceproviders.DefaultConfig(), diff --git a/syft/get_source_config_test.go b/syft/get_source_config_test.go index 912131494..0d12c3cf7 100644 --- a/syft/get_source_config_test.go +++ b/syft/get_source_config_test.go @@ -1,9 +1,15 @@ package syft import ( + "slices" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/anchore/stereoscope" + "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source/sourceproviders" ) @@ -17,8 +23,9 @@ func TestGetProviders_DefaultImagePullSource(t *testing.T) { t.Errorf("Expected no error for DefaultImagePullSource parameter, got: %v", err) } - if len(providers) != len(allSourceProviders) { - t.Errorf("Expected %d providers, got %d", len(allSourceProviders), len(providers)) + // everything except containers-storage, which is only used when requested by name + if len(providers) != len(allSourceProviders)-1 { + t.Errorf("Expected %d providers, got %d", len(allSourceProviders)-1, len(providers)) } } @@ -36,3 +43,55 @@ func TestGetProviders_Sources(t *testing.T) { t.Errorf("Expected 2 providers, got %d", len(providers)) } } + +func TestGetProviders_ContainersStorageOnlyWhenRequested(t *testing.T) { + const storage = image.ContainersStorageSource + + tests := []struct { + name string + cfg *GetSourceConfig + wantFound bool + // if set, containers-storage must be ordered ahead of this provider + wantBefore string + }{ + { + name: "excluded from automatic resolution", + cfg: DefaultGetSourceConfig(), + }, + { + name: "excluded when selecting all pull sources", + cfg: DefaultGetSourceConfig().WithSources(sourceproviders.PullTag), + }, + { + name: "excluded when a different default pull source is set", + cfg: DefaultGetSourceConfig().WithDefaultImagePullSource(stereoscope.RegistryTag), + }, + { + name: "included when explicitly selected", + cfg: DefaultGetSourceConfig().WithSources(storage), + wantFound: true, + }, + { + name: "included ahead of other pull sources when it is the default pull source", + cfg: DefaultGetSourceConfig().WithDefaultImagePullSource(storage), + wantFound: true, + wantBefore: image.DockerDaemonSource, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + providers, err := tt.cfg.getProviders("localhost/myimage:latest") + require.NoError(t, err) + require.NotEmpty(t, providers) + + assert.Equal(t, tt.wantFound, providerIndex(providers, storage) >= 0) + if tt.wantBefore != "" { + assert.Less(t, providerIndex(providers, storage), providerIndex(providers, tt.wantBefore)) + } + }) + } +} + +func providerIndex(providers []source.Provider, name string) int { + return slices.IndexFunc(providers, func(p source.Provider) bool { return p.Name() == name }) +} diff --git a/syft/internal/fileresolver/chroot_context.go b/syft/internal/fileresolver/chroot_context.go index deb8cd000..8f7eef399 100644 --- a/syft/internal/fileresolver/chroot_context.go +++ b/syft/internal/fileresolver/chroot_context.go @@ -210,8 +210,13 @@ func (r ChrootContext) ToChrootPath(nativePath string) string { prefix = r.base } else { // we need to account for the cwd relative to the running process and the given root for the directory resolver - prefix = filepath.Clean(filepath.Join(r.cwd, r.cwdRelativeToRoot)) - prefix += string(filepath.Separator) + prefix = r.cwdRelativeToRoot + // on windows an absolute root stays absolute here (see ChangeDirectory), and joining it onto the cwd + // would nest one volume inside another (D:\cwd\C:\root) + if !filepath.IsAbs(prefix) { + prefix = filepath.Join(r.cwd, prefix) + } + prefix = filepath.Clean(prefix) + string(filepath.Separator) } responsePath = strings.TrimPrefix(responsePath, prefix) } diff --git a/syft/internal/fileresolver/chroot_context_windows_test.go b/syft/internal/fileresolver/chroot_context_windows_test.go new file mode 100644 index 000000000..1884c4219 --- /dev/null +++ b/syft/internal/fileresolver/chroot_context_windows_test.go @@ -0,0 +1,17 @@ +package fileresolver + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// windows CI runs from D: while t.TempDir() lives on C:, so the root and cwd don't share a volume +func TestChrootContext_ToChrootPath_rootOnAnotherVolume(t *testing.T) { + ctx := ChrootContext{ + root: `C:\scan\root`, + cwd: `D:\work`, + cwdRelativeToRoot: `C:\scan\root`, + } + assert.Equal(t, "dir/file.txt", ctx.ToChrootPath("/c/scan/root/dir/file.txt")) +} diff --git a/syft/internal/fileresolver/container_image_all_layers_test.go b/syft/internal/fileresolver/container_image_all_layers_test.go index 7d36baa8b..e8cc2750c 100644 --- a/syft/internal/fileresolver/container_image_all_layers_test.go +++ b/syft/internal/fileresolver/container_image_all_layers_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/file" ) @@ -91,6 +92,7 @@ func TestAllLayersResolver_FilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageAllLayers(img) @@ -201,6 +203,7 @@ func TestAllLayersResolver_FilesByGlob(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageAllLayers(img) @@ -249,6 +252,7 @@ func Test_imageAllLayersResolver_FilesByMIMEType(t *testing.T) { } for _, test := range tests { t.Run(test.fixtureName, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName) resolver, err := NewFromContainerImageAllLayers(img) @@ -266,6 +270,7 @@ func Test_imageAllLayersResolver_FilesByMIMEType(t *testing.T) { } func Test_imageAllLayersResolver_hasFilesystemIDInLocation(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path") resolver, err := NewFromContainerImageAllLayers(img) @@ -326,6 +331,7 @@ func TestAllLayersImageResolver_FilesContents(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageAllLayers(img) @@ -353,6 +359,7 @@ func TestAllLayersImageResolver_FilesContents(t *testing.T) { func TestAllLayersImageResolver_FilesContents_errorOnDirRequest(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageAllLayers(img) @@ -500,6 +507,7 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageAllLayers(img) @@ -514,6 +522,7 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { } func TestAllLayersResolver_AllLocations(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-files-deleted") resolver, err := NewFromContainerImageAllLayers(img) diff --git a/syft/internal/fileresolver/container_image_deep_squash_test.go b/syft/internal/fileresolver/container_image_deep_squash_test.go index c1a6945a3..f88afdf13 100644 --- a/syft/internal/fileresolver/container_image_deep_squash_test.go +++ b/syft/internal/fileresolver/container_image_deep_squash_test.go @@ -16,6 +16,7 @@ import ( "go.uber.org/goleak" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/file" ) @@ -115,6 +116,7 @@ func Test_ContainerImageDeepSquash_FilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageDeepSquash(img) @@ -231,6 +233,7 @@ func Test_ContainerImageDeepSquash_FilesByGlob(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageDeepSquash(img) @@ -288,6 +291,7 @@ func Test_ContainerImageDeepSquash_FilesByMIMEType(t *testing.T) { for _, test := range tests { t.Run(test.fixtureName, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName) resolver, err := NewFromContainerImageDeepSquash(img) @@ -305,6 +309,7 @@ func Test_ContainerImageDeepSquash_FilesByMIMEType(t *testing.T) { } func Test_ContainerImageDeepSquash_hasFilesystemIDInLocation(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path") resolver, err := NewFromContainerImageDeepSquash(img) @@ -365,6 +370,7 @@ func Test_ContainerImageDeepSquash_FilesContents(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageDeepSquash(img) @@ -389,6 +395,7 @@ func Test_ContainerImageDeepSquash_FilesContents(t *testing.T) { } func Test_ContainerImageDeepSquash_FilesContents_errorOnDirRequest(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageDeepSquash(img) @@ -557,6 +564,7 @@ func Test_ContainerImageDeepSquash_resolvesLinks(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageDeepSquash(img) @@ -571,6 +579,7 @@ func Test_ContainerImageDeepSquash_resolvesLinks(t *testing.T) { } func Test_ContainerImageDeepSquash_AllLocations(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-files-deleted") resolver, err := NewFromContainerImageDeepSquash(img) diff --git a/syft/internal/fileresolver/container_image_squash_test.go b/syft/internal/fileresolver/container_image_squash_test.go index b1ceaca0d..f9c8c45e8 100644 --- a/syft/internal/fileresolver/container_image_squash_test.go +++ b/syft/internal/fileresolver/container_image_squash_test.go @@ -16,6 +16,7 @@ import ( stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/file" ) @@ -75,6 +76,7 @@ func TestImageSquashResolver_FilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageSquash(img) @@ -184,6 +186,7 @@ func TestImageSquashResolver_FilesByGlob(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageSquash(img) @@ -241,6 +244,7 @@ func Test_imageSquashResolver_FilesByMIMEType(t *testing.T) { for _, test := range tests { t.Run(test.fixtureName, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName) resolver, err := NewFromContainerImageSquash(img) @@ -258,6 +262,7 @@ func Test_imageSquashResolver_FilesByMIMEType(t *testing.T) { } func Test_imageSquashResolver_hasFilesystemIDInLocation(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path") resolver, err := NewFromContainerImageSquash(img) @@ -316,6 +321,7 @@ func TestSquashImageResolver_FilesContents(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageSquash(img) @@ -341,6 +347,7 @@ func TestSquashImageResolver_FilesContents(t *testing.T) { func TestSquashImageResolver_FilesContents_errorOnDirRequest(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageSquash(img) @@ -499,6 +506,7 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") resolver, err := NewFromContainerImageSquash(img) @@ -545,6 +553,7 @@ func locationSorter(a, b file.Location) int { } func TestSquashResolver_AllLocations(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-files-deleted") resolver, err := NewFromContainerImageSquash(img) @@ -580,6 +589,7 @@ func TestSquashResolver_AllLocations(t *testing.T) { // content and metadata), so that image results are in parity with directory results (which cannot distinguish a // hardlink from a regular file). this must hold for both the squashed and all-layers resolvers. func TestImageResolvers_Hardlinks(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-hardlinks") resolvers := map[string]file.Resolver{} diff --git a/syft/internal/fileresolver/directory_indexer.go b/syft/internal/fileresolver/directory_indexer.go index e748a5015..3f6216d73 100644 --- a/syft/internal/fileresolver/directory_indexer.go +++ b/syft/internal/fileresolver/directory_indexer.go @@ -351,17 +351,12 @@ func (r directoryIndexer) addFileToIndex(p string, info os.FileInfo) error { func (r directoryIndexer) addSymlinkToIndex(p string, info os.FileInfo) (string, error) { linkTarget, err := os.Readlink(p) if err != nil { - isOnWindows := windows.HostRunningOnWindows() - if isOnWindows { - p = windows.FromPosix(p) - } - - linkTarget, err = filepath.EvalSymlinks(p) - - if isOnWindows { - p = windows.ToPosix(p) + nativePath := p + if windows.HostRunningOnWindows() { + nativePath = windows.FromPosix(p) } + linkTarget, err = filepath.EvalSymlinks(nativePath) if err != nil { return "", fmt.Errorf("unable to readlink for path=%q: %w", p, err) } @@ -414,16 +409,21 @@ func (r directoryIndexer) addSymlinkToIndex(p string, info os.FileInfo) (string, } } - ref, err := r.tree.AddSymLink(file.Path(p), file.Path(linkTarget)) - if err != nil { - return "", err - } - targetAbsPath := linkTarget if !filepath.IsAbs(targetAbsPath) { targetAbsPath = filepath.Clean(filepath.Join(path.Dir(p), linkTarget)) } + // the tree is posix (like p), but the joins above produce native separators on windows + if windows.HostRunningOnWindows() { + linkTarget = windows.ToPosix(linkTarget) + } + + ref, err := r.tree.AddSymLink(file.Path(p), file.Path(linkTarget)) + if err != nil { + return "", err + } + metadata := NewMetadataFromPath(p, info) metadata.LinkDestination = linkTarget r.index.Add(*ref, metadata) diff --git a/syft/internal/fileresolver/filetree_resolver.go b/syft/internal/fileresolver/filetree_resolver.go index 20c60b57a..d8a5f71d3 100644 --- a/syft/internal/fileresolver/filetree_resolver.go +++ b/syft/internal/fileresolver/filetree_resolver.go @@ -35,9 +35,19 @@ func nativeOSFileOpener(ref stereoscopeFile.Reference) (io.ReadCloser, error) { return stereoscopeFile.NewLazyReadCloser(filePath), nil } +// virtual reports whether the tree is not backed by the host filesystem (e.g. a squashfs), in which case there is +// no chroot and request/response paths are already the posix tree paths. Mapping them through the host (as the +// chroot does) would root them on the current volume on windows (/payload.txt -> /d/payload.txt). +func (r FiletreeResolver) virtual() bool { + return r.Chroot == ChrootContext{} +} + // requestPath converts a user (chroot) path into the path the file tree is keyed on. The tree is always posix // (windows paths are volume-encoded, e.g. /c/some/path), while ToNativePath yields a native host path. func (r *FiletreeResolver) requestPath(userPath string) (string, error) { + if r.virtual() { + return userPath, nil + } nativePath, err := r.Chroot.ToNativePath(userPath) if err != nil { return "", err @@ -50,6 +60,9 @@ func (r *FiletreeResolver) requestPath(userPath string) (string, error) { // responsePath takes a path from the underlying fs domain and converts it to a path that is relative to the root of the file resolver. func (r FiletreeResolver) responsePath(path string) string { + if r.virtual() { + return path + } return r.Chroot.ToChrootPath(path) } @@ -111,6 +124,9 @@ func (r FiletreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err // requestGlob is the glob equivalent of requestPath. func (r FiletreeResolver) requestGlob(pattern string) (string, error) { + if r.virtual() { + return pattern, nil + } glob, err := r.Chroot.ToNativeGlob(pattern) if err != nil { return "", err diff --git a/syft/pkg/cataloger/ai/parse_safetensors_test.go b/syft/pkg/cataloger/ai/parse_safetensors_test.go index c5d4457cb..0fbdd5508 100644 --- a/syft/pkg/cataloger/ai/parse_safetensors_test.go +++ b/syft/pkg/cataloger/ai/parse_safetensors_test.go @@ -8,6 +8,7 @@ import ( "io" "math" "os" + "path" "path/filepath" "slices" "strings" @@ -522,14 +523,15 @@ func TestSafeTensorsMergeProcessor(t *testing.T) { t.Run("dir scan: config.json _name_or_path beats the parent directory fallback", func(t *testing.T) { // When a sibling config.json carries _name_or_path - dir := t.TempDir() - require.NoError(t, os.WriteFile(filepath.Join(dir, "config.json"), + // (location paths are posix, so keep the temp dir slash-separated for windows) + dir := filepath.ToSlash(t.TempDir()) + require.NoError(t, os.WriteFile(path.Join(dir, "config.json"), []byte(`{"_name_or_path":"org/preferred-name"}`), 0o644)) - stPath := filepath.Join(dir, "weights.safetensors") + stPath := path.Join(dir, "weights.safetensors") p := dirPkg(stPath, pkg.SafeTensorsModelInfo{ Format: "safetensors", TensorCount: 1, MetadataHash: "abc", }) - resolver := file.NewMockResolverForPaths(filepath.Join(dir, "config.json")) + resolver := file.NewMockResolverForPaths(path.Join(dir, "config.json")) out, _, err := safeTensorsMergeProcessor(context.Background(), resolver, []pkg.Package{p}, nil, nil) require.NoError(t, err) require.Len(t, out, 1) diff --git a/syft/pkg/cataloger/arch/parse_alpm_db.go b/syft/pkg/cataloger/arch/parse_alpm_db.go index db7505be6..ac63a4141 100644 --- a/syft/pkg/cataloger/arch/parse_alpm_db.go +++ b/syft/pkg/cataloger/arch/parse_alpm_db.go @@ -114,7 +114,7 @@ func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, f func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) { // We only really do this to get any backup database entries from the files database - target := filepath.Join(base, "files") + target := path.Join(base, "files") loc, err := getLocation(target, resolver) if err != nil { @@ -395,7 +395,9 @@ func parseMtreeWithLimits(r io.Reader, maxSize int64, maxLines int) ([]pkg.AlpmF if ok := ignoredFiles[f.Name]; ok { continue } - path := fmt.Sprintf("/%s", f.Name) + // go-mtree runs filepath.Clean on entry names, which turns them into host paths on windows. + // ponytail: a vis-encoded backslash (\134) in a name gets mangled on windows only, unvis first if that matters + path := fmt.Sprintf("/%s", filepath.ToSlash(f.Name)) fileFields["path"] = path for _, kv := range f.Keywords { kw := string(kv.Keyword()) diff --git a/syft/pkg/cataloger/binary/capabilities.yaml b/syft/pkg/cataloger/binary/capabilities.yaml index e8d68b254..25fb3d7b8 100644 --- a/syft/pkg/cataloger/binary/capabilities.yaml +++ b/syft/pkg/cataloger/binary/capabilities.yaml @@ -155,10 +155,10 @@ catalogers: packages: - class: perl-binary name: perl - purl: pkg:generic/perl + purl: pkg:cpan/perl cpes: - cpe:2.3:a:perl:perl:*:*:*:*:*:*:*:* - type: BinaryPkg + type: CpanPkg - method: glob criteria: - '**/composer*' diff --git a/syft/pkg/cataloger/binary/classifier_cataloger_test.go b/syft/pkg/cataloger/binary/classifier_cataloger_test.go index 54cd04f3b..a9d0a676e 100644 --- a/syft/pkg/cataloger/binary/classifier_cataloger_test.go +++ b/syft/pkg/cataloger/binary/classifier_cataloger_test.go @@ -17,6 +17,7 @@ import ( "github.com/anchore/packageurl-go" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" @@ -460,8 +461,8 @@ func Test_Cataloger_PositiveCases(t *testing.T) { expected: pkg.Package{ Name: "perl", Version: "5.12.5", - Type: "binary", - PURL: "pkg:generic/perl@5.12.5", + Type: "cpan", + PURL: "pkg:cpan/perl@5.12.5", Locations: locations("perl"), Metadata: metadata("perl-binary"), }, @@ -473,8 +474,8 @@ func Test_Cataloger_PositiveCases(t *testing.T) { expected: pkg.Package{ Name: "perl", Version: "5.20.0", - Type: "binary", - PURL: "pkg:generic/perl@5.20.0", + Type: "cpan", + PURL: "pkg:cpan/perl@5.20.0", Locations: locations("perl"), Metadata: metadata("perl-binary"), }, @@ -486,8 +487,8 @@ func Test_Cataloger_PositiveCases(t *testing.T) { expected: pkg.Package{ Name: "perl", Version: "5.37.8", - Type: "binary", - PURL: "pkg:generic/perl@5.37.8", + Type: "cpan", + PURL: "pkg:cpan/perl@5.37.8", Locations: locations("perl"), Metadata: metadata("perl-binary"), }, @@ -3169,6 +3170,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) { t.Run(test.name, func(t *testing.T) { c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage) src := stereoscopesource.New(img, stereoscopesource.ImageConfig{ Reference: test.fixtureImage, @@ -3187,6 +3189,58 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) { } } +// Test_Cataloger_PackageTypes covers what assertPackagesAreEqual deliberately ignores: the package type and CPEs. +// The perl interpreter is reported as cpan so it lines up with the rest of the CPAN ecosystem, everything else +// keeps the default binary type. +func Test_Cataloger_PackageTypes(t *testing.T) { + tests := []struct { + logicalFixture string + wantType pkg.Type + wantPURL string + wantCPEs []string + }{ + { + logicalFixture: "perl/5.20.0/linux-amd64", + wantType: pkg.CpanPkg, + wantPURL: "pkg:cpan/perl@5.20.0", + wantCPEs: []string{"cpe:2.3:a:perl:perl:5.20.0:*:*:*:*:*:*:*"}, + }, + { + logicalFixture: "haproxy/1.5.14/linux-amd64", + wantType: pkg.BinaryPkg, + wantPURL: "pkg:generic/haproxy@1.5.14", + wantCPEs: []string{"cpe:2.3:a:haproxy:haproxy:1.5.14:*:*:*:*:*:*:*"}, + }, + } + + for _, test := range tests { + t.Run(test.logicalFixture, func(t *testing.T) { + c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) + + path := testutil.SnippetOrBinary(t, test.logicalFixture, *mustUseOriginalBinaries) + + src, err := directorysource.NewFromPath(path) + require.NoError(t, err) + + resolver, err := src.FileResolver(source.SquashedScope) + require.NoError(t, err) + + packages, _, err := c.Catalog(context.Background(), resolver) + require.NoError(t, err) + require.Len(t, packages, 1) + + var cpes []string + for _, c := range packages[0].CPEs { + cpes = append(cpes, c.Attributes.BindToFmtString()) + } + + assert.Equal(t, test.wantType, packages[0].Type) + assert.Equal(t, test.wantPURL, packages[0].PURL) + assert.Equal(t, test.wantCPEs, cpes) + }) + } +} + func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) { c := NewClassifierCataloger(DefaultClassifierCatalogerConfig()) diff --git a/syft/pkg/cataloger/binary/classifiers.go b/syft/pkg/cataloger/binary/classifiers.go index 26929ae2a..4e4afb478 100644 --- a/syft/pkg/cataloger/binary/classifiers.go +++ b/syft/pkg/cataloger/binary/classifiers.go @@ -5,6 +5,7 @@ import ( "github.com/anchore/packageurl-go" "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/binutils" ) @@ -251,8 +252,9 @@ func DefaultClassifiers() []binutils.Classifier { EvidenceMatcher: m.FileContentsVersionMatcher( `(?m)\/usr\/local\/lib\/perl\d\/(?P[0-9]+\.[0-9]+\.[0-9]+)`), Package: "perl", - PURL: mustPURL("pkg:generic/perl@version"), + PURL: mustPURL("pkg:cpan/perl@version"), CPEs: singleCPE("cpe:2.3:a:perl:perl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource), + Type: pkg.CpanPkg, }, { Class: "php-composer-binary", diff --git a/syft/pkg/cataloger/binary/internal/manager/internal/config/binary_from_image_test.go b/syft/pkg/cataloger/binary/internal/manager/internal/config/binary_from_image_test.go index 55bb3ee40..8d7153e16 100644 --- a/syft/pkg/cataloger/binary/internal/manager/internal/config/binary_from_image_test.go +++ b/syft/pkg/cataloger/binary/internal/manager/internal/config/binary_from_image_test.go @@ -1,6 +1,7 @@ package config import ( + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -123,10 +124,10 @@ func TestAllStorePaths(t *testing.T) { }, dest: "dest", expected: []string{ - "dest/test/1.0/platform1/test1", - "dest/test/1.0/platform1/test2", - "dest/test/1.0/platform2/test1", - "dest/test/1.0/platform2/test2", + filepath.FromSlash("dest/test/1.0/platform1/test1"), + filepath.FromSlash("dest/test/1.0/platform1/test2"), + filepath.FromSlash("dest/test/1.0/platform2/test1"), + filepath.FromSlash("dest/test/1.0/platform2/test2"), }, }, } diff --git a/syft/pkg/cataloger/binary/internal/manager/internal/list_entries.go b/syft/pkg/cataloger/binary/internal/manager/internal/list_entries.go index de35b3731..ce54e936e 100644 --- a/syft/pkg/cataloger/binary/internal/manager/internal/list_entries.go +++ b/syft/pkg/cataloger/binary/internal/manager/internal/list_entries.go @@ -4,6 +4,7 @@ import ( "fmt" "iter" "os" + "path" "path/filepath" "slices" "sort" @@ -167,7 +168,7 @@ func getLogicalKey(managedBinaryPath string) (*LogicalEntryKey, error) { OrgName: items[0], Version: items[1], Platform: items[2], - Filename: filepath.Join(items[3:]...), + Filename: path.Join(items[3:]...), // posix, since this is part of the logical key }, nil } diff --git a/syft/pkg/cataloger/binary/internal/manager/internal/list_entries_test.go b/syft/pkg/cataloger/binary/internal/manager/internal/list_entries_test.go index 82d430346..3294c19c2 100644 --- a/syft/pkg/cataloger/binary/internal/manager/internal/list_entries_test.go +++ b/syft/pkg/cataloger/binary/internal/manager/internal/list_entries_test.go @@ -64,8 +64,8 @@ func TestListAllBinaries(t *testing.T) { assert.Equal(t, Entries{ - LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: "testdata/bin/busybox/1.3.6/linux-amd64/busybox", SnippetPath: ""}, - LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: "testdata/bin/busybox/1.3.6/linux-arm64/busybox", SnippetPath: ""}, + LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: filepath.FromSlash("testdata/bin/busybox/1.3.6/linux-amd64/busybox"), SnippetPath: ""}, + LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: filepath.FromSlash("testdata/bin/busybox/1.3.6/linux-arm64/busybox"), SnippetPath: ""}, }, entries, ) @@ -105,10 +105,10 @@ func TestListAllEntries(t *testing.T) { assert.Equal(t, Entries{ - LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: "testdata/bin/busybox/1.3.6/linux-amd64/busybox", SnippetPath: "testdata/snippets/busybox/1.3.6/linux-amd64/busybox"}, - LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: "testdata/bin/busybox/1.3.6/linux-arm64/busybox", SnippetPath: "testdata/snippets/busybox/1.3.6/linux-arm64/busybox"}, + LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: filepath.FromSlash("testdata/bin/busybox/1.3.6/linux-amd64/busybox"), SnippetPath: filepath.FromSlash("testdata/snippets/busybox/1.3.6/linux-amd64/busybox")}, + LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: filepath.FromSlash("testdata/bin/busybox/1.3.6/linux-arm64/busybox"), SnippetPath: filepath.FromSlash("testdata/snippets/busybox/1.3.6/linux-arm64/busybox")}, // note the standalone snippet! - LogicalEntryKey{OrgName: "postgres", Version: "9.6.10", Platform: "linux-amd64", Filename: "postgres"}: EntryInfo{IsConfigured: false, BinaryPath: "", SnippetPath: "testdata/snippets/postgres/9.6.10/linux-amd64/postgres"}, + LogicalEntryKey{OrgName: "postgres", Version: "9.6.10", Platform: "linux-amd64", Filename: "postgres"}: EntryInfo{IsConfigured: false, BinaryPath: "", SnippetPath: filepath.FromSlash("testdata/snippets/postgres/9.6.10/linux-amd64/postgres")}, }, entries, ) diff --git a/syft/pkg/cataloger/binary/internal/manager/internal/utils_test.go b/syft/pkg/cataloger/binary/internal/manager/internal/utils_test.go index ab8d7a111..51f5f8e54 100644 --- a/syft/pkg/cataloger/binary/internal/manager/internal/utils_test.go +++ b/syft/pkg/cataloger/binary/internal/manager/internal/utils_test.go @@ -1,6 +1,7 @@ package internal import ( + "path/filepath" "strings" "testing" @@ -13,7 +14,7 @@ func TestSplitFilepath(t *testing.T) { path string expected []string }{ - {path: "a/b/c", expected: []string{"a", "b", "c"}}, + {path: filepath.Join("a", "b", "c"), expected: []string{"a", "b", "c"}}, } for _, test := range tests { diff --git a/syft/pkg/cataloger/binary/internal/manager/testutil/snippet_or_binary.go b/syft/pkg/cataloger/binary/internal/manager/testutil/snippet_or_binary.go index 9a4ef20fc..9d81f2847 100644 --- a/syft/pkg/cataloger/binary/internal/manager/testutil/snippet_or_binary.go +++ b/syft/pkg/cataloger/binary/internal/manager/testutil/snippet_or_binary.go @@ -3,7 +3,9 @@ package testutil import ( "fmt" "os" + "path" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" @@ -18,10 +20,11 @@ import ( // not specify the "bin" or "snippets" parent path... this is determined logically [snippets > binary unless told // otherwise]). Path should also be to the directory containing the binary or snippets of interest (not the binaries // or snippets itself). -func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string { +func SnippetOrBinary(t *testing.T, logicalPath string, requireBinary bool) string { t.Helper() - require.Len(t, internal.SplitFilepath(path), 3, "path must be a in the form //") + // logicalPath is a posix key, not a host path + require.Len(t, strings.Split(logicalPath, "/"), 3, "path must be a in the form //") // cd to testdata directory and load the config @@ -43,11 +46,11 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string { var fixturePath string for k, v := range entries.Sorted() { - if filepath.Dir(k.Path()) == path { + if path.Dir(k.Path()) == logicalPath { // prefer the snippet over the binary if !requireBinary { if v.SnippetPath != "" { - t.Logf("using snippet for %q", path) + t.Logf("using snippet for %q", logicalPath) require.NoError(t, validateSnippet(v.BinaryPath, v.SnippetPath)) fixturePath = v.SnippetPath break @@ -56,10 +59,10 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string { fixturePath = v.BinaryPath break } - t.Fatalf("no binary or snippet found for %q", path) + t.Fatalf("no binary or snippet found for %q", logicalPath) } if v.BinaryPath != "" { - t.Logf("forcing the use of the original binary for %q", path) + t.Logf("forcing the use of the original binary for %q", logicalPath) fixturePath = v.BinaryPath break } @@ -68,12 +71,12 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string { t.Skip("no binary found, but is covered by a snippet. Please add this case to the 'binary/testdata/config.yaml' and recreate the snippet") } - t.Fatalf("no binary found for %q", path) + t.Fatalf("no binary found for %q", logicalPath) } } if fixturePath == "" { - t.Fatalf("no fixture found for %q", path) + t.Fatalf("no fixture found for %q", logicalPath) } // this should be relative to the tests-fixtures directory and should be the directory containing the binary or @@ -101,6 +104,7 @@ func validateSnippet(binaryPath, snippetPath string) error { if err != nil { return err } + defer f.Close() expected, err := internal.Sha256SumFile(f) if err != nil { diff --git a/syft/pkg/cataloger/bitnami/cataloger.go b/syft/pkg/cataloger/bitnami/cataloger.go index 71f6518e2..2d845d6d1 100644 --- a/syft/pkg/cataloger/bitnami/cataloger.go +++ b/syft/pkg/cataloger/bitnami/cataloger.go @@ -5,7 +5,7 @@ package bitnami import ( "context" - "path/filepath" + "path" "strings" "github.com/anchore/syft/internal/log" @@ -73,7 +73,7 @@ func parseSBOM(_ context.Context, resolver file.Resolver, _ *generic.Environment // Bitnami packages reported in a SPDX file are shipped under the same directory // as the SPDX file itself. - metadata.Path = filepath.Dir(reader.RealPath) + metadata.Path = path.Dir(reader.RealPath) if p.ID() != mainPkgID { metadata.Files = packageFiles(s.Relationships, p, metadata.Path) secondaryPkgsFiles = append(secondaryPkgsFiles, metadata.Files...) diff --git a/syft/pkg/cataloger/bitnami/package.go b/syft/pkg/cataloger/bitnami/package.go index db29aec48..cb46d5453 100644 --- a/syft/pkg/cataloger/bitnami/package.go +++ b/syft/pkg/cataloger/bitnami/package.go @@ -3,7 +3,6 @@ package bitnami import ( "fmt" "path" - "path/filepath" "slices" "strings" @@ -73,7 +72,7 @@ func packageFiles(relationships []artifact.Relationship, p pkg.Package, baseDire // mainPkgFiles returns the files owned by the main package in the SPDX file. func mainPkgFiles(resolver file.Resolver, spdxFilePath string, secondaryPkgsFiles []string) ([]string, error) { - ownedPathGlob := fmt.Sprintf("%s/**", filepath.Dir(spdxFilePath)) + ownedPathGlob := fmt.Sprintf("%s/**", path.Dir(spdxFilePath)) ownedLocations, err := resolver.FilesByGlob(ownedPathGlob) if err != nil { return nil, err diff --git a/syft/pkg/cataloger/bitnami/parse_components.go b/syft/pkg/cataloger/bitnami/parse_components.go index 155a89831..d2eb6d592 100644 --- a/syft/pkg/cataloger/bitnami/parse_components.go +++ b/syft/pkg/cataloger/bitnami/parse_components.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "path/filepath" + "path" "sort" "strings" @@ -67,7 +67,7 @@ func parseComponentsJSON(_ context.Context, _ file.Resolver, _ *generic.Environm Revision: revision, Architecture: entry.Arch, Distro: entry.Distro, - Path: filepath.Join(filepath.Dir(reader.RealPath), name), + Path: path.Join(path.Dir(reader.RealPath), name), } p := pkg.Package{ diff --git a/syft/pkg/cataloger/dotnet/parse_packages_lock.go b/syft/pkg/cataloger/dotnet/parse_packages_lock.go index 3348d5a0f..685f60eed 100644 --- a/syft/pkg/cataloger/dotnet/parse_packages_lock.go +++ b/syft/pkg/cataloger/dotnet/parse_packages_lock.go @@ -4,8 +4,9 @@ import ( "context" "encoding/json" "fmt" + "maps" "slices" - "sort" + "strings" "github.com/anchore/packageurl-go" "github.com/anchore/syft/internal/log" @@ -18,6 +19,8 @@ import ( var _ generic.Parser = parseDotnetPackagesLock +var packagesLockTypePrecedence = []string{"Project", "Direct", "CentralTransitive", "Transitive"} + type dotnetPackagesLock struct { Version int `json:"version"` Dependencies map[string]map[string]dotnetPackagesLockDep `json:"dependencies"` @@ -31,11 +34,38 @@ type dotnetPackagesLockDep struct { Dependencies map[string]string `json:"dependencies,omitempty"` } -func parseDotnetPackagesLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { //nolint:funlen - var pkgs []pkg.Package - var pkgMap = make(map[string]pkg.Package) - var relationships []artifact.Relationship +type packagesLockEntry struct { + name string + dep dotnetPackagesLockDep +} +func (e packagesLockEntry) nameVersion() string { + return createNameAndVersion(e.name, e.dep.Resolved) +} + +// key identifies the package an entry describes. NuGet package IDs are case-insensitive, so two spellings of the +// same ID and version are one package. +func (e packagesLockEntry) key() string { + return createNameAndVersion(strings.ToLower(e.name), e.dep.Resolved) +} + +type packagesLockFramework struct { + name string + entries []packagesLockEntry + // byName is keyed by the lowercased package name, since NuGet package IDs are case-insensitive. + byName map[string]packagesLockEntry +} + +func (f packagesLockFramework) find(name string) (packagesLockEntry, bool) { + e, ok := f.byName[strings.ToLower(name)] + return e, ok +} + +type packagesLockEdge struct { + child, parent artifact.ID +} + +func parseDotnetPackagesLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { dec := json.NewDecoder(reader) // unmarshal file @@ -44,70 +74,21 @@ func parseDotnetPackagesLock(_ context.Context, _ file.Resolver, _ *generic.Envi return nil, nil, fmt.Errorf("failed to parse packages.lock.json file: %w", err) } - // collect all deps here - allDependencies := make(map[string]dotnetPackagesLockDep) - - var names []string - for _, dependencies := range lockFile.Dependencies { - for name, dep := range dependencies { - depNameVersion := createNameAndVersion(name, dep.Resolved) - - if slices.Contains(names, depNameVersion) { - continue - } - - names = append(names, depNameVersion) - allDependencies[depNameVersion] = dep - } - } - - // sort the names so that the order of the packages is deterministic - sort.Strings(names) + frameworks := newPackagesLockFrameworks(lockFile) // create artifact for each pkg - for _, nameVersion := range names { - name, _ := extractNameAndVersion(nameVersion) + var pkgs []pkg.Package + pkgMap := make(map[string]pkg.Package) - dep := allDependencies[nameVersion] - dotnetPkg := newDotnetPackagesLockPackage(name, dep, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) + for _, entry := range mergePackagesLockEntries(frameworks) { + dotnetPkg := newDotnetPackagesLockPackage(entry.name, entry.dep, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) if dotnetPkg != nil { pkgs = append(pkgs, *dotnetPkg) - pkgMap[nameVersion] = *dotnetPkg + pkgMap[entry.key()] = *dotnetPkg } } - // fill up relationships - for depNameVersion, dep := range allDependencies { - parentPkg, ok := pkgMap[depNameVersion] - if !ok { - log.Debugf("package \"%s\" not found in map of all packages", depNameVersion) - continue - } - - for childDepName, childDepVersion := range dep.Dependencies { - childDepNameVersion := createNameAndVersion(childDepName, childDepVersion) - - // try and find pkg for dependency with exact name and version - childPkg, ok := pkgMap[childDepNameVersion] - if !ok { - // no exact match found, lets match on name only, lockfile will contain other version of pkg - cpkg, ok := findPkgByName(childDepName, pkgMap) - if !ok { - log.Debugf("dependency \"%s\" of package \"%s\" not found in map of all packages", childDepNameVersion, depNameVersion) - continue - } - - childPkg = *cpkg - } - - rel := artifact.Relationship{ - From: childPkg, - To: parentPkg, - Type: artifact.DependencyOfRelationship, - } - relationships = append(relationships, rel) - } - } + relationships := packagesLockRelationships(frameworks, pkgMap) // sort the relationships for deterministic output relationship.Sort(relationships) @@ -115,6 +96,147 @@ func parseDotnetPackagesLock(_ context.Context, _ file.Resolver, _ *generic.Envi return pkgs, relationships, nil } +func newPackagesLockFrameworks(lockFile dotnetPackagesLock) []packagesLockFramework { + var frameworks []packagesLockFramework + + for _, frameworkName := range slices.Sorted(maps.Keys(lockFile.Dependencies)) { + deps := lockFile.Dependencies[frameworkName] + framework := packagesLockFramework{ + name: frameworkName, + byName: make(map[string]packagesLockEntry, len(deps)), + } + + for _, name := range slices.Sorted(maps.Keys(deps)) { + entry := packagesLockEntry{name: name, dep: deps[name]} + framework.entries = append(framework.entries, entry) + framework.byName[strings.ToLower(name)] = entry + } + + frameworks = append(frameworks, framework) + } + + return frameworks +} + +func mergePackagesLockEntries(frameworks []packagesLockFramework) []packagesLockEntry { + merged := make(map[string]packagesLockEntry) + + for _, framework := range frameworks { + for _, entry := range framework.entries { + key := entry.key() + if existing, ok := merged[key]; ok && !isMoreDirect(entry.dep.Type, existing.dep.Type) { + continue + } + merged[key] = entry + } + } + + var entries []packagesLockEntry + for _, key := range slices.Sorted(maps.Keys(merged)) { + entries = append(entries, merged[key]) + } + + return entries +} + +func isMoreDirect(a, b string) bool { + rankA, rankB := packagesLockTypeRank(a), packagesLockTypeRank(b) + if rankA != rankB { + return rankA < rankB + } + return a < b +} + +func packagesLockTypeRank(t string) int { + if i := slices.Index(packagesLockTypePrecedence, t); i >= 0 { + return i + } + return len(packagesLockTypePrecedence) +} + +func packagesLockRelationships(frameworks []packagesLockFramework, pkgMap map[string]pkg.Package) []artifact.Relationship { + var relationships []artifact.Relationship + + frameworksByName := make(map[string]packagesLockFramework, len(frameworks)) + for _, framework := range frameworks { + frameworksByName[framework.name] = framework + } + + seen := make(map[packagesLockEdge]struct{}) + for _, framework := range frameworks { + base, hasBase := frameworksByName[baseFrameworkName(framework.name)] + if !hasBase || base.name == framework.name { + base = packagesLockFramework{} + } + + for _, entry := range framework.entries { + parentPkg, ok := pkgMap[entry.key()] + if !ok { + log.Debugf("package %q not found in map of all packages", entry.nameVersion()) + continue + } + + relationships = append(relationships, packagesLockEntryRelationships(entry, parentPkg, framework, base, pkgMap, seen)...) + } + } + + return relationships +} + +func packagesLockEntryRelationships(entry packagesLockEntry, parentPkg pkg.Package, framework, base packagesLockFramework, pkgMap map[string]pkg.Package, seen map[packagesLockEdge]struct{}) []artifact.Relationship { + var relationships []artifact.Relationship + + for _, childName := range slices.Sorted(maps.Keys(entry.dep.Dependencies)) { + child, ok := findPackagesLockDependency(childName, framework, base) + if !ok { + log.Debugf("dependency %q of package %q not found under target framework %q", childName, entry.nameVersion(), framework.name) + continue + } + + childPkg, ok := pkgMap[child.key()] + if !ok { + log.Debugf("package %q not found in map of all packages", child.nameVersion()) + continue + } + + // a package listing itself (possibly under another casing) is not a dependency + if childPkg.ID() == parentPkg.ID() { + continue + } + + key := packagesLockEdge{child: childPkg.ID(), parent: parentPkg.ID()} + if _, exists := seen[key]; exists { + continue + } + seen[key] = struct{}{} + + relationships = append(relationships, artifact.Relationship{ + From: childPkg, + To: parentPkg, + Type: artifact.DependencyOfRelationship, + }) + } + + return relationships +} + +// findPackagesLockDependency finds the entry a dependency edge points at. The version on an edge is only the lower +// bound of a version range, so the edge resolves to whatever version its target framework resolved. Runtime-specific +// sections such as "net8.0/win-x64" may reference packages listed only in their base framework ("net8.0"). An edge +// that resolves nowhere is dropped rather than guessed from another target framework. +func findPackagesLockDependency(name string, framework, base packagesLockFramework) (packagesLockEntry, bool) { + if entry, ok := framework.find(name); ok { + return entry, true + } + return base.find(name) +} + +// baseFrameworkName returns the target framework of a runtime-specific section, such as "net8.0" for "net8.0/win-x64". +func baseFrameworkName(frameworkName string) string { + name, _, _ := strings.Cut(frameworkName, "/") + return name +} + func newDotnetPackagesLockPackage(name string, dep dotnetPackagesLockDep, locations ...file.Location) *pkg.Package { metadata := pkg.DotnetPackagesLockEntry{ Name: name, @@ -150,14 +272,3 @@ func packagesLockPackageURL(name, version string) string { "", ).ToString() } - -func findPkgByName(pkgName string, pkgMap map[string]pkg.Package) (*pkg.Package, bool) { - for pkgNameVersion, pkg := range pkgMap { - name, _ := extractNameAndVersion(pkgNameVersion) - if name == pkgName { - return &pkg, true - } - } - - return nil, false -} diff --git a/syft/pkg/cataloger/dotnet/parse_packages_lock_test.go b/syft/pkg/cataloger/dotnet/parse_packages_lock_test.go index 24ce33454..ffdf09892 100644 --- a/syft/pkg/cataloger/dotnet/parse_packages_lock_test.go +++ b/syft/pkg/cataloger/dotnet/parse_packages_lock_test.go @@ -3,6 +3,9 @@ package dotnet import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" @@ -197,3 +200,304 @@ func TestParseDotnetPackagesLock(t *testing.T) { pkgtest.TestFileParser(t, fixture, parseDotnetPackagesLock, expectedPkgs, expectedRelationships) } + +func TestParseDotnetPackagesLock_multipleTargetFrameworks(t *testing.T) { + fixture := "testdata/packages.lock-multi-framework.json" + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture)) + + myLibPkg := pkg.Package{ + Name: "MyLib", + Version: "1.0.0", + PURL: "pkg:nuget/MyLib@1.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetPackagesLockEntry{ + Name: "MyLib", + Version: "1.0.0", + ContentHash: "mylibhash==", + Type: "Direct", + }, + } + + log4net2Pkg := pkg.Package{ + Name: "log4net", + Version: "2.0.5", + PURL: "pkg:nuget/log4net@2.0.5", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetPackagesLockEntry{ + Name: "log4net", + Version: "2.0.5", + ContentHash: "log4net205hash==", + Type: "Transitive", + }, + } + + log4net1Pkg := pkg.Package{ + Name: "log4net", + Version: "1.2.15", + PURL: "pkg:nuget/log4net@1.2.15", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetPackagesLockEntry{ + Name: "log4net", + Version: "1.2.15", + ContentHash: "log4net1215hash==", + Type: "Transitive", + }, + } + + // resolved to the same version under both frameworks (spelled "newtonsoft.json" under one), but declared "Direct" by + // only one of them + newtonsoftPkg := pkg.Package{ + Name: "Newtonsoft.Json", + Version: "13.0.3", + PURL: "pkg:nuget/Newtonsoft.Json@13.0.3", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetPackagesLockEntry{ + Name: "Newtonsoft.Json", + Version: "13.0.3", + ContentHash: "newtonsoft1303hash==", + Type: "Direct", + }, + } + + // only listed under the runtime-specific "net8.0/win-x64" section, and depends on a package from "net8.0" + myLibNativePkg := pkg.Package{ + Name: "runtime.win-x64.MyLib.Native", + Version: "1.0.0", + PURL: "pkg:nuget/runtime.win-x64.MyLib.Native@1.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetPackagesLockEntry{ + Name: "runtime.win-x64.MyLib.Native", + Version: "1.0.0", + ContentHash: "mylibnativehash==", + Type: "Transitive", + }, + } + + // only listed under "net8.0", so the "netstandard2.0" edge to it must be dropped rather than guessed + serilogPkg := pkg.Package{ + Name: "Serilog", + Version: "3.1.1", + PURL: "pkg:nuget/Serilog@3.1.1", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetPackagesLockEntry{ + Name: "Serilog", + Version: "3.1.1", + ContentHash: "serilog311hash==", + Type: "Transitive", + }, + } + + expectedPkgs := []pkg.Package{ + myLibPkg, + newtonsoftPkg, + log4net1Pkg, + log4net2Pkg, + myLibNativePkg, + serilogPkg, + } + + // the same package is resolved to a different version per target framework, so both edges must be captured, while + // the Newtonsoft.Json edge declared by both frameworks must appear only once + expectedRelationships := []artifact.Relationship{ + { + From: log4net1Pkg, + To: myLibPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: log4net2Pkg, + To: myLibPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: log4net2Pkg, + To: myLibNativePkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: newtonsoftPkg, + To: myLibPkg, + Type: artifact.DependencyOfRelationship, + }, + } + + pkgtest.TestFileParser(t, fixture, parseDotnetPackagesLock, expectedPkgs, expectedRelationships) +} + +func TestParseDotnetPackagesLock_projectReference(t *testing.T) { + fixture := "testdata/packages.lock-project-reference.json" + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture)) + + newtonsoftPkg := pkg.Package{ + Name: "Newtonsoft.Json", + Version: "13.0.3", + PURL: "pkg:nuget/Newtonsoft.Json@13.0.3", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetPackagesLockEntry{ + Name: "Newtonsoft.Json", + Version: "13.0.3", + ContentHash: "newtonsoft1303hash==", + Type: "Transitive", + }, + } + + // a project reference has no resolved version or content hash + myLibPkg := pkg.Package{ + Name: "mylib", + PURL: "pkg:nuget/mylib", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetPackagesLockEntry{ + Name: "mylib", + Type: "Project", + }, + } + + expectedPkgs := []pkg.Package{ + newtonsoftPkg, + myLibPkg, + } + + // project references declare their dependencies as version ranges, which resolve to the version the target + // framework resolved + expectedRelationships := []artifact.Relationship{ + { + From: newtonsoftPkg, + To: myLibPkg, + Type: artifact.DependencyOfRelationship, + }, + } + + pkgtest.TestFileParser(t, fixture, parseDotnetPackagesLock, expectedPkgs, expectedRelationships) +} + +func Test_findPackagesLockDependency(t *testing.T) { + frameworks := newPackagesLockFrameworks(dotnetPackagesLock{ + Dependencies: map[string]map[string]dotnetPackagesLockDep{ + "net8.0": { + "log4net": {Resolved: "2.0.5"}, + }, + "net8.0/win-x64": { + "runtime.win-x64.Native": {Resolved: "1.0.0"}, + }, + "netstandard2.0": { + "log4net": {Resolved: "1.2.15"}, + "Newtonsoft.Json": {Resolved: "13.0.3"}, + }, + }, + }) + net8, rid, netstandard := frameworks[0], frameworks[1], frameworks[2] + + tests := []struct { + name string + depName string + framework packagesLockFramework + base packagesLockFramework + wantVersion string + wantFound bool + }{ + { + name: "resolves to the version pinned by its own target framework", + depName: "log4net", + framework: netstandard, + wantVersion: "1.2.15", + wantFound: true, + }, + { + name: "package names are matched case-insensitively", + depName: "LOG4NET", + framework: net8, + wantVersion: "2.0.5", + wantFound: true, + }, + { + name: "runtime-specific sections fall back to their base target framework", + depName: "log4net", + framework: rid, + base: net8, + wantVersion: "2.0.5", + wantFound: true, + }, + { + name: "a package absent from the target framework is not guessed from another one", + depName: "Newtonsoft.Json", + framework: net8, + wantFound: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, found := findPackagesLockDependency(tt.depName, tt.framework, tt.base) + require.Equal(t, tt.wantFound, found) + if !tt.wantFound { + return + } + assert.Equal(t, tt.wantVersion, got.dep.Resolved) + }) + } +} + +func Test_mergePackagesLockEntries_typePrecedence(t *testing.T) { + tests := []struct { + name string + types map[string]string // target framework -> type + wantType string + }{ + { + name: "direct wins over transitive", + types: map[string]string{"net8.0": "Transitive", "netstandard2.0": "Direct"}, + wantType: "Direct", + }, + { + name: "central transitive wins over transitive regardless of framework order", + types: map[string]string{"net8.0": "Transitive", "netstandard2.0": "CentralTransitive"}, + wantType: "CentralTransitive", + }, + { + name: "central transitive wins over transitive in the reverse framework order", + types: map[string]string{"net8.0": "CentralTransitive", "netstandard2.0": "Transitive"}, + wantType: "CentralTransitive", + }, + { + name: "direct wins over central transitive", + types: map[string]string{"net472": "CentralTransitive", "net8.0": "Direct", "netstandard2.0": "Transitive"}, + wantType: "Direct", + }, + { + name: "known types win over unknown ones", + types: map[string]string{"net8.0": "Unknown", "netstandard2.0": "Transitive"}, + wantType: "Transitive", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + lockFile := dotnetPackagesLock{Dependencies: map[string]map[string]dotnetPackagesLockDep{}} + for framework, depType := range tt.types { + lockFile.Dependencies[framework] = map[string]dotnetPackagesLockDep{ + "Newtonsoft.Json": {Type: depType, Resolved: "13.0.3"}, + } + } + + entries := mergePackagesLockEntries(newPackagesLockFrameworks(lockFile)) + require.Len(t, entries, 1) + assert.Equal(t, tt.wantType, entries[0].dep.Type) + }) + } +} diff --git a/syft/pkg/cataloger/dotnet/testdata/packages.lock-multi-framework.json b/syft/pkg/cataloger/dotnet/testdata/packages.lock-multi-framework.json new file mode 100644 index 000000000..e9d4dd200 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/testdata/packages.lock-multi-framework.json @@ -0,0 +1,67 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "MyLib": { + "type": "Direct", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "mylibhash==", + "dependencies": { + "log4net": "2.0.5", + "Newtonsoft.Json": "13.0.3" + } + }, + "log4net": { + "type": "Transitive", + "resolved": "2.0.5", + "contentHash": "log4net205hash==" + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "newtonsoft1303hash==" + }, + "Serilog": { + "type": "Transitive", + "resolved": "3.1.1", + "contentHash": "serilog311hash==" + } + }, + "net8.0/win-x64": { + "runtime.win-x64.MyLib.Native": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "mylibnativehash==", + "dependencies": { + "log4net": "2.0.0", + "RUNTIME.WIN-X64.MYLIB.NATIVE": "1.0.0" + } + } + }, + "netstandard2.0": { + "MyLib": { + "type": "Direct", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "mylibhash==", + "dependencies": { + "log4net": "1.2.15", + "newtonsoft.json": "13.0.3", + "Serilog": "3.1.1" + } + }, + "log4net": { + "type": "Transitive", + "resolved": "1.2.15", + "contentHash": "log4net1215hash==" + }, + "newtonsoft.json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "newtonsoft1303hash==" + } + } + } +} diff --git a/syft/pkg/cataloger/dotnet/testdata/packages.lock-project-reference.json b/syft/pkg/cataloger/dotnet/testdata/packages.lock-project-reference.json new file mode 100644 index 000000000..e09cfe2b0 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/testdata/packages.lock-project-reference.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "newtonsoft1303hash==" + }, + "mylib": { + "type": "Project", + "dependencies": { + "Newtonsoft.Json": "[13.0.3, )" + } + } + } + } +} diff --git a/syft/pkg/cataloger/golang/license_finder_test.go b/syft/pkg/cataloger/golang/license_finder_test.go index 8be2f8d1d..81976bbe9 100644 --- a/syft/pkg/cataloger/golang/license_finder_test.go +++ b/syft/pkg/cataloger/golang/license_finder_test.go @@ -1,6 +1,9 @@ package golang import ( + "path/filepath" + "runtime" + "strings" "testing" "github.com/spf13/afero" @@ -202,10 +205,23 @@ func TestFindAllLicenseCandidatesUpwards(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Create in-memory filesystem fs := afero.NewMemMapFs() - tt.setupFS(fs) + setupFS, startDir, stopAt, expectedFiles := afero.Fs(fs), tt.startDir, tt.stopAt, tt.expectedFiles + if runtime.GOOS == "windows" { + // the finder wants native absolute paths, so root the posix-style cases on a drive + setupFS = afero.NewBasePathFs(fs, `C:\`) + startDir, stopAt = winAbs(startDir), winAbs(stopAt) + expectedFiles = nil + if tt.expectedFiles != nil { + expectedFiles = []string{} + } + for _, f := range tt.expectedFiles { + expectedFiles = append(expectedFiles, winAbs(f)) + } + } + tt.setupFS(setupFS) // Run the function - result, err := findAllLicenseCandidatesUpwards(tt.startDir, tt.stopAt, fs) + result, err := findAllLicenseCandidatesUpwards(startDir, stopAt, fs) // Check error expectation if tt.expectedError { @@ -214,7 +230,15 @@ func TestFindAllLicenseCandidatesUpwards(t *testing.T) { } require.NoError(t, err, tt.description) - assert.Equal(t, tt.expectedFiles, result, tt.description) + assert.Equal(t, expectedFiles, result, tt.description) }) } } + +// winAbs roots posix absolute paths on C:\, leaving relative ones relative (some cases assert they're rejected) +func winAbs(p string) string { + if !strings.HasPrefix(p, "/") { + return p + } + return filepath.Join(`C:\`, filepath.FromSlash(p)) +} diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index 3f704c5d3..7e33b0155 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/internal/fileresolver" @@ -30,6 +31,8 @@ import ( // make will run the default make target for the given test fixture path func runMakeTarget(t *testing.T, fixtureName string) { + // the fixture binaries are built inside linux containers + testutils.SkipWithoutLinuxContainers(t) cwd, err := os.Getwd() require.NoError(t, err) fixtureDir := filepath.Join(cwd, "testdata/", fixtureName) diff --git a/syft/pkg/cataloger/golang/parse_go_mod.go b/syft/pkg/cataloger/golang/parse_go_mod.go index e9d021729..c4a727987 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod.go +++ b/syft/pkg/cataloger/golang/parse_go_mod.go @@ -20,6 +20,7 @@ import ( "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/internal/fileresolver" + "github.com/anchore/syft/syft/internal/windows" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" ) @@ -38,7 +39,12 @@ func newGoModCataloger(opts CatalogerConfig) *goModCataloger { // parseGoModFile takes a go.mod and tries to resolve and lists all packages discovered. func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) (pkgs []pkg.Package, relationships []artifact.Relationship, err error) { - modDir := filepath.Dir(string(reader.Location.Reference().RealPath)) + realPath := string(reader.Location.Reference().RealPath) + if windows.HostRunningOnWindows() { + // directory resolvers hand back volume-encoded posix paths (/c/...), the go toolchain needs the native path + realPath = windows.FromPosix(realPath) + } + modDir := filepath.Dir(realPath) digests, err := parseGoSumFile(resolver, reader) if err != nil { log.Debugf("unable to get go.sum: %v", err) diff --git a/syft/pkg/cataloger/golang/upx_test.go b/syft/pkg/cataloger/golang/upx_test.go index 05df518e2..928211874 100644 --- a/syft/pkg/cataloger/golang/upx_test.go +++ b/syft/pkg/cataloger/golang/upx_test.go @@ -10,6 +10,7 @@ import ( stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" ) // every case here is one the magic scan must reject, asserted on parseUPXInfo's errNotUPX result. @@ -114,6 +115,7 @@ func TestParseUPXInfo_ValidHeader(t *testing.T) { // ignores that padding if there is some. A build that needs none passes either way, which is how the bug // went unnoticed. The case without padding is covered by TestDecompressUPX_TailExtentsArePlacedPastTheLoader. func TestImageSmallUPXNeedsLoaderPadding(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", "image-small-upx") rc, err := img.OpenPathFromSquash(stereoscopeFile.Path("/run-me")) require.NoError(t, err) diff --git a/syft/pkg/cataloger/internal/binutils/classifier.go b/syft/pkg/cataloger/internal/binutils/classifier.go index b2c0ff5e6..05b62897e 100644 --- a/syft/pkg/cataloger/internal/binutils/classifier.go +++ b/syft/pkg/cataloger/internal/binutils/classifier.go @@ -50,6 +50,17 @@ type Classifier struct { // CPEs are the specific CPEs we want to include for this binary with updated version information CPEs []cpe.CPE `json:"cpes"` + + // Type optionally overrides the package type reported for matches; when unset pkg.BinaryPkg is used + Type pkg.Type `json:"-"` +} + +// PackageType is the package type reported for matches of this classifier, defaulting to pkg.BinaryPkg. +func (cfg Classifier) PackageType() pkg.Type { + if cfg.Type == "" { + return pkg.BinaryPkg + } + return cfg.Type } func (cfg Classifier) MarshalJSON() ([]byte, error) { diff --git a/syft/pkg/cataloger/internal/binutils/classifier_package.go b/syft/pkg/cataloger/internal/binutils/classifier_package.go index 42831ddae..e1c4393df 100644 --- a/syft/pkg/cataloger/internal/binutils/classifier_package.go +++ b/syft/pkg/cataloger/internal/binutils/classifier_package.go @@ -35,7 +35,7 @@ func NewClassifierPackage(classifier Classifier, location file.Location, matchMe Locations: file.NewLocationSet( location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), - Type: pkg.BinaryPkg, + Type: classifier.PackageType(), CPEs: cpes, FoundBy: catalogerName, Metadata: pkg.BinarySignature{ diff --git a/syft/pkg/cataloger/internal/binutils/classifier_test.go b/syft/pkg/cataloger/internal/binutils/classifier_test.go index c9d1c9e31..5bae45683 100644 --- a/syft/pkg/cataloger/internal/binutils/classifier_test.go +++ b/syft/pkg/cataloger/internal/binutils/classifier_test.go @@ -12,6 +12,7 @@ import ( "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/internal/unionreader" + "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source/directorysource" ) @@ -102,6 +103,35 @@ func Test_ClassifierCPEs(t *testing.T) { } } +func Test_ClassifierPackageType(t *testing.T) { + tests := []struct { + name string + classifier Classifier + want pkg.Type + }{ + { + name: "defaults to binary when unset", + classifier: Classifier{Class: "some-binary", Package: "some-app"}, + want: pkg.BinaryPkg, + }, + { + name: "honors an explicit type", + classifier: Classifier{Class: "perl-binary", Package: "perl", Type: pkg.CpanPkg}, + want: pkg.CpanPkg, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.want, test.classifier.PackageType()) + + p := NewClassifierPackage(test.classifier, file.NewLocation("/usr/bin/app"), map[string]string{"version": "1.2.3"}, "cataloger-name") + require.NotNil(t, p) + assert.Equal(t, test.want, p.Type) + }) + } +} + func TestClassifier_MarshalJSON(t *testing.T) { tests := []struct { diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json b/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json index baa74273d..5361f9932 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json +++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json @@ -3834,11 +3834,18 @@ "cpe:2.3:a:gaoxuyan_project:gaoxuyan:*:*:*:*:*:*:*:*" ], "gatsby-plugin-mdx": [ + "cpe:2.3:a:gatsbyjs:gatsby-plugin-mdx:*:*:*:*:*:node.js:*:*", "cpe:2.3:a:gatsbyjs:gatsby:*:*:*:*:*:node.js:*:*" ], + "gatsby-plugin-sharp": [ + "cpe:2.3:a:gatsbyjs:gatsby-plugin-sharp:*:*:*:*:*:node.js:*:*" + ], "gatsby-source-wordpress": [ "cpe:2.3:a:gatsbyjs:gatsby-source-wordpress:*:*:*:*:*:node.js:*:*" ], + "gatsby-transformer-remark": [ + "cpe:2.3:a:gatsbyjs:gatsby-transformer-remark:*:*:*:*:*:node.js:*:*" + ], "gedi": [ "cpe:2.3:a:gedi_project:gedi:*:*:*:*:*:node.js:*:*" ], @@ -6265,6 +6272,9 @@ "vega-functions": [ "cpe:2.3:a:vega-functions_project:vega-functions:*:*:*:*:*:node.js:*:*" ], + "vega-selections": [ + "cpe:2.3:a:vega_project:vega:*:*:*:*:*:node.js:*:*" + ], "velocityjs": [ "cpe:2.3:a:shepherdwind:velocity.js:*:*:*:*:*:node.js:*:*" ], @@ -6975,6 +6985,9 @@ "openlit": [ "cpe:2.3:a:openlit:openlit_software_development_kit:*:*:*:*:*:python:*:*" ], + "openml": [ + "cpe:2.3:a:openml:openml:*:*:*:*:*:python:*:*" + ], "openpyxl": [ "cpe:2.3:a:python:openpyxl:*:*:*:*:*:*:*:*" ], @@ -8796,6 +8809,9 @@ "adaptive-images": [ "cpe:2.3:a:nevma:adaptive_images:*:*:*:*:*:wordpress:*:*" ], + "adbuddy-adblocker-detection": [ + "cpe:2.3:a:netfunkdesign:adbuddy\\+_\\(adblocker_detection\\):*:*:*:*:*:wordpress:*:*" + ], "add-actions-and-filters": [ "cpe:2.3:a:add_shortcodes_actions_and_filters_project:add_shortcodes_actions_and_filters:*:*:*:*:*:wordpress:*:*" ], @@ -8948,7 +8964,8 @@ "cpe:2.3:a:niceit:advance_post_prefix:*:*:*:*:*:wordpress:*:*" ], "advance-search": [ - "cpe:2.3:a:advance_search_project:advance_search:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:advance_search_project:advance_search:*:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:mndpsingh287:advanced_search:*:*:*:*:*:wordpress:*:*" ], "advanced-access-manager": [ "cpe:2.3:a:vasyltech:advanced_access_manager:*:*:*:*:*:wordpress:*:*" @@ -9098,6 +9115,9 @@ "agp-font-awesome-collection": [ "cpe:2.3:a:profosbox:agp_font_awesome_collection:*:*:*:*:*:wordpress:*:*" ], + "ahathat": [ + "cpe:2.3:a:mitchelllevy:ahathat:*:*:*:*:*:wordpress:*:*" + ], "ai-assistant-by-10web": [ "cpe:2.3:a:10web:ai_assistant:*:*:*:*:*:wordpress:*:*" ], @@ -9524,6 +9544,9 @@ "asf-allow-svg-files": [ "cpe:2.3:a:allow_svg_files_project:allow_svg_files:*:*:*:*:*:wordpress:*:*" ], + "asgard": [ + "cpe:2.3:a:outself:asgard_security_scanner:*:*:*:*:*:wordpress:*:*" + ], "asgaros-forum": [ "cpe:2.3:a:asgaros:asgaros_forum:*:*:*:*:*:wordpress:*:*" ], @@ -9803,6 +9826,9 @@ "banner-effect-header": [ "cpe:2.3:a:banner_effect_header_project:banner_effect_header:*:*:*:*:*:wordpress:*:*" ], + "banner-garden": [ + "cpe:2.3:a:karacsi_maci:banner_garden:*:*:*:*:*:wordpress:*:*" + ], "banner-management-for-woocommerce": [ "cpe:2.3:a:dotstore:woocommerce_category_banner_management:*:*:*:*:*:wordpress:*:*", "cpe:2.3:a:multidots:banner_management_for_woocommerce:*:*:*:*:*:wordpress:*:*" @@ -10077,6 +10103,9 @@ "blue-admin": [ "cpe:2.3:a:blue-admin_project:blue-admin:*:*:*:*:*:wordpress:*:*" ], + "blue-triad-ezanalytics": [ + "cpe:2.3:a:jbahlquist:blue_triad_ezanalytics:*:*:*:*:*:wordpress:*:*" + ], "bluet-keywords-tooltip-generator": [ "cpe:2.3:a:tooltipy:tooltipy:*:*:*:*:*:wordpress:*:*" ], @@ -10334,6 +10363,9 @@ "bulk-image-resizer": [ "cpe:2.3:a:giuliopanda:bulk_images_optimizer:*:*:*:*:*:wordpress:*:*" ], + "bulk-me-now": [ + "cpe:2.3:a:ombu:bulk_me_now\\!:*:*:*:*:*:wordpress:*:*" + ], "bulk-noindex-nofollow-toolkit-by-mad-fish": [ "cpe:2.3:a:madfishdigital:bulk_noindex_\\\u0026_nofollow_toolkit:*:*:*:*:*:wordpress:*:*" ], @@ -10355,6 +10387,9 @@ "business": [ "cpe:2.3:a:snilesh:business:*:*:*:*:*:wordpress:*:*" ], + "business-card-by-esterox-100": [ + "cpe:2.3:a:esterox:business_card:*:*:*:*:*:wordpress:*:*" + ], "business-directory-plugin": [ "cpe:2.3:a:businessdirectoryplugin:business_directory:*:*:*:*:*:wordpress:*:*", "cpe:2.3:a:strategy11:business_directory_plugin_-_easy_listing_directories:*:*:*:*:*:wordpress:*:*" @@ -10542,7 +10577,8 @@ "cpe:2.3:a:vinojcardoza:ajax_post_search:*:*:*:*:*:wordpress:*:*" ], "cardoza-facebook-like-box": [ - "cpe:2.3:a:facebook_like_box_project:facebook_like_box:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:facebook_like_box_project:facebook_like_box:*:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:johnnash1975:easy_social_like_box_popup_sidebar_widget:*:*:*:*:*:wordpress:*:*" ], "cardoza-wordpress-poll": [ "cpe:2.3:a:cardozatechnologies:wordpress_poll:*:*:*:*:*:wordpress:*:*", @@ -11201,6 +11237,9 @@ "contact-list": [ "cpe:2.3:a:tammersoft:contact_list:*:*:*:*:*:wordpress:*:*" ], + "contact-us-by-lord-linus": [ + "cpe:2.3:a:lordlinus:contact_us:*:*:*:*:*:wordpress:*:*" + ], "contact-us-page-contact-people": [ "cpe:2.3:a:a3rev:contact_us_page_-_contact_people:*:*:*:*:*:wordpress:*:*" ], @@ -11371,6 +11410,9 @@ "countdown-builder": [ "cpe:2.3:a:edmonsoft:countdown_builder:*:*:*:*:*:wordpress:*:*" ], + "countdown-timer-block": [ + "cpe:2.3:a:flickdevs:countdown_timer_for_wordpress_block_editor:*:*:*:*:*:wordpress:*:*" + ], "countdown-timer-for-elementor": [ "cpe:2.3:a:flickdevs:countdown_timer_for_elementor:*:*:*:*:*:wordpress:*:*" ], @@ -11575,6 +11617,9 @@ "cpe:2.3:a:bestwebsoft:custom_admin:*:*:*:*:*:wordpress:*:*", "cpe:2.3:a:bestwebsoft:custom_admin_page:*:*:*:*:*:wordpress:*:*" ], + "custom-author-base": [ + "cpe:2.3:a:jfarthing:custom_author_base:*:*:*:*:*:wordpress:*:*" + ], "custom-banners": [ "cpe:2.3:a:goldplugins:custom_banners:*:*:*:*:*:wordpress:*:*" ], @@ -11651,6 +11696,9 @@ "custom-options-plus": [ "cpe:2.3:a:leocaseiro:custom_options_plus:*:*:*:*:*:wordpress:*:*" ], + "custom-order-statuses-for-woocommerce": [ + "cpe:2.3:a:nuggethon:custom_order_status_manager_for_woocommerce:*:*:*:*:*:wordpress:*:*" + ], "custom-permalinks": [ "cpe:2.3:a:samiahmedsiddiqui:custom_permalinks:*:*:*:*:*:wordpress:*:*" ], @@ -11882,6 +11930,9 @@ "demon-image-annotation": [ "cpe:2.3:a:superwhite:demon_image_annotation:*:*:*:*:*:wordpress:*:*" ], + "dental-optimizer-patient-generator-app": [ + "cpe:2.3:a:healthygrid:dental_optimizer_patient_generator_app:*:*:*:*:*:wordpress:*:*" + ], "deny-all-firewall": [ "cpe:2.3:a:deny_all_firewall_project:deny_all_firewall:*:*:*:*:*:wordpress:*:*" ], @@ -12028,6 +12079,9 @@ "donorbox-donation-form": [ "cpe:2.3:a:donorbox:donorbox:*:*:*:*:*:wordpress:*:*" ], + "dont-muck-my-markup": [ + "cpe:2.3:a:martynchamberlin:dont_muck_my_markup:*:*:*:*:*:wordpress:*:*" + ], "doofinder-for-woocommerce": [ "cpe:2.3:a:doofinder:doofinder:*:*:*:*:*:wordpress:*:*" ], @@ -12397,6 +12451,9 @@ "ecpay-ecommerce-for-woocommerce": [ "cpe:2.3:a:ecpay:ecpay_ecommerce_for_woocommerce:*:*:*:*:*:wordpress:*:*" ], + "ect-homepage-products": [ + "cpe:2.3:a:etemplates:ect_home_page_products:*:*:*:*:*:wordpress:*:*" + ], "ecwid-shopping-cart": [ "cpe:2.3:a:lightspeedhq:ecwid_ecommerce_shopping_cart:*:*:*:*:*:wordpress:*:*" ], @@ -13127,7 +13184,8 @@ "cpe:2.3:a:mehanoid:flatpm:*:*:*:*:*:wordpress:*:*" ], "flattr": [ - "cpe:2.3:a:fkrauthan:wp-mpdf:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:fkrauthan:wp-mpdf:*:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:flattr:flattr:*:*:*:*:*:wordpress:*:*" ], "flexi": [ "cpe:2.3:a:odude:flexi:*:*:*:*:*:wordpress:*:*" @@ -13160,6 +13218,9 @@ "flipping-cards": [ "cpe:2.3:a:info-d-74:flipping_cards:*:*:*:*:free:wordpress:*:*" ], + "float-block": [ + "cpe:2.3:a:computy:float_block:*:*:*:*:*:wordpress:*:*" + ], "float-menu": [ "cpe:2.3:a:wow-company:float_menu:*:*:*:*:*:wordpress:*:*" ], @@ -13422,6 +13483,9 @@ "full-customer": [ "cpe:2.3:a:full:full_-_customer:*:*:*:*:*:wordpress:*:*" ], + "full-screen-page-background-image-slideshow": [ + "cpe:2.3:a:mohsinrasool:full_screen_\\(page\\)_background_image_slideshow:*:*:*:*:*:wordpress:*:*" + ], "full-site-editing": [ "cpe:2.3:a:automattic:wordpress.com_editing_toolkit:*:*:*:*:*:wordpress:*:*" ], @@ -13558,6 +13622,9 @@ "gdpr-framework": [ "cpe:2.3:a:data443:gdpr_framework:*:*:*:*:*:wordpress:*:*" ], + "gearside-developer-dashboard": [ + "cpe:2.3:a:gearside:gearside_developer_dashboard:*:*:*:*:*:wordpress:*:*" + ], "gecka-terms-thumbnails": [ "cpe:2.3:a:gecka:terms_thumbnails:*:*:*:*:*:wordpress:*:*" ], @@ -13567,6 +13634,9 @@ "generate-pdf-using-contact-form-7": [ "cpe:2.3:a:zealousweb:generate_pdf_using_contact_form_7:*:*:*:*:*:wordpress:*:*" ], + "generateblocks": [ + "cpe:2.3:a:generatepress:generateblocks:*:*:*:*:free:wordpress:*:*" + ], "generatepress-premium": [ "cpe:2.3:a:generatepress:generatepress:*:*:*:*:premium:wordpress:*:*" ], @@ -13952,6 +14022,9 @@ "gtmetrix-for-wordpress": [ "cpe:2.3:a:gtmetrix:gtmetrix:*:*:*:*:*:wordpress:*:*" ], + "gtpayment-donation": [ + "cpe:2.3:a:harryhe:gtpayment_donations:*:*:*:*:*:wordpress:*:*" + ], "gtranslate": [ "cpe:2.3:a:gtranslate:gtranslate:*:*:*:*:enterprise:wordpress:*:*", "cpe:2.3:a:gtranslate:gtranslate:*:*:*:*:pro:wordpress:*:*", @@ -14693,6 +14766,9 @@ "iws-geo-form-fields": [ "cpe:2.3:a:iws-geo-form-fields_project:iws-geo-form-fields:*:*:*:*:*:wordpress:*:*" ], + "javo-core": [ + "cpe:2.3:a:javothemes:javo_core:*:*:*:*:*:wordpress:*:*" + ], "jayj-quicktag": [ "cpe:2.3:a:jayj_quicktag_project:jayj_quicktag:*:*:*:*:*:wordpress:*:*" ], @@ -14969,6 +15045,9 @@ "kwayy-html-sitemap": [ "cpe:2.3:a:kwayyinfotech:kwayy_html_sitemap:*:*:*:*:*:wordpress:*:*" ], + "l-squared-hub-wp-virtual-device": [ + "cpe:2.3:a:lsquared:l_squared_hub:*:*:*:*:*:wordpress:*:*" + ], "labtools": [ "cpe:2.3:a:creativityjuice:labtools:*:*:*:*:*:wordpress:*:*" ], @@ -15196,6 +15275,9 @@ "link-optimizer-lite": [ "cpe:2.3:a:link_optimizer_lite_project:link_optimizer_lite:*:*:*:*:*:wordpress:*:*" ], + "link-party": [ + "cpe:2.3:a:evanliewer:illi_link_party\\!:*:*:*:*:*:wordpress:*:*" + ], "link-to-bible": [ "cpe:2.3:a:bibleserver:link_to_bible:*:*:*:*:*:wordpress:*:*" ], @@ -15531,6 +15613,9 @@ "malinky-ajax-pagination": [ "cpe:2.3:a:malinky:malinky-ajax-pagination:*:*:*:*:*:wordpress:*:*" ], + "mambo-joomla-importer": [ + "cpe:2.3:a:misterpah:mambo_joomla_importer:*:*:*:*:*:wordpress:*:*" + ], "manage-notification-emails": [ "cpe:2.3:a:freeamigos:manage_notification_e-mails:*:*:*:*:*:wordpress:*:*" ], @@ -15592,6 +15677,9 @@ "marketing-and-seo-booster": [ "cpe:2.3:a:secretlab:marketing_and_seo_booster:*:*:*:*:*:wordpress:*:*" ], + "marketing-optimizer": [ + "cpe:2.3:a:marketingoptimizer:marketing_optimizer:*:*:*:*:*:wordpress:*:*" + ], "marketo-forms-and-tracking": [ "cpe:2.3:a:hutchhouse:marketo_forms_and_tracking:*:*:*:*:*:wordpress:*:*" ], @@ -16039,6 +16127,9 @@ "mq-woocommerce-products-price-bulk-edit": [ "cpe:2.3:a:mq-woocommerce-products-price-bulk-edit_project:mq-woocommerce-products-price-bulk-edit:*:*:*:*:*:wordpress:*:*" ], + "mrlegend-typedjs": [ + "cpe:2.3:a:mrlegend1235:typed_js:*:*:*:*:*:wordpress:*:*" + ], "ms-registration": [ "cpe:2.3:a:alphaefficiencyteam:custom_login_and_registration:*:*:*:*:*:wordpress:*:*" ], @@ -16290,6 +16381,9 @@ "news-kit-elementor-addons": [ "cpe:2.3:a:blazethemes:news_kit_elementor_addons:*:*:*:*:*:wordpress:*:*" ], + "news-list": [ + "cpe:2.3:a:mahinsha:newsticker:*:*:*:*:*:wordpress:*:*" + ], "newsletter": [ "cpe:2.3:a:thenewsletterplugin:newsletter:*:*:*:*:*:wordpress:*:*" ], @@ -16305,6 +16399,9 @@ "newsletter-popup": [ "cpe:2.3:a:mndpsingh287:newsletter_popup:*:*:*:*:*:wordpress:*:*" ], + "newsletter2go": [ + "cpe:2.3:a:newsletter2go:newsletter2go:*:*:*:*:*:wordpress:*:*" + ], "newsletters-lite": [ "cpe:2.3:a:tribulant:newsletters:*:*:*:*:*:wordpress:*:*", "cpe:2.3:a:tribulant:newsletters:*:*:*:*:free:wordpress:*:*" @@ -16324,7 +16421,8 @@ ], "nextend-facebook-connect": [ "cpe:2.3:a:nextendweb:facebook_connect:*:*:*:*:*:wordpress:*:*", - "cpe:2.3:a:nextendweb:nextend_facebook_connect:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:nextendweb:nextend_facebook_connect:*:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:nextendweb:nextend_social_login:*:*:*:*:free:wordpress:*:*" ], "nextend-twitter-connect": [ "cpe:2.3:a:nextendweb:nextend_twitter_connect:*:*:*:*:*:wordpress:*:*" @@ -16364,6 +16462,9 @@ "ninja-job-board": [ "cpe:2.3:a:wpmanageninja:ninja_job_board:*:*:*:*:*:wordpress:*:*" ], + "ninja-page-categories-and-tags": [ + "cpe:2.3:a:ninja_pages_project:ninja_pages:*:*:*:*:*:wordpress:*:*" + ], "ninja-tables": [ "cpe:2.3:a:wpmanageninja:ninja_tables:*:*:*:*:*:wordpress:*:*" ], @@ -16407,6 +16508,9 @@ "nofollow-links": [ "cpe:2.3:a:nofollow_links_project:nofollow_links:*:*:*:*:*:wordpress:*:*" ], + "nokaut-offers-box": [ + "cpe:2.3:a:nokautpl:nokaut_offers_box:*:*:*:*:*:wordpress:*:*" + ], "nokia-mapsplaces": [ "cpe:2.3:a:nokia_maps_\\\u0026_places_project:nokia_maps_\\\u0026_places:*:*:*:*:*:wordpress:*:*" ], @@ -16456,6 +16560,9 @@ "ns-woocommerce-watermark": [ "cpe:2.3:a:nsthemes:ns_watermark_for_woocommerce:*:*:*:*:*:wordpress:*:*" ], + "ntzantispam": [ + "cpe:2.3:a:ionutstaicu:ntz_atispam:*:*:*:*:*:wordpress:*:*" + ], "oauth-client": [ "cpe:2.3:a:miniorange:oauth_2.0_client_for_sso:*:*:*:*:*:wordpress:*:*" ], @@ -17618,6 +17725,9 @@ "pushbiz": [ "cpe:2.3:a:csimplifyit:pushbiz:*:*:*:*:*:wordpress:*:*" ], + "pvn-auth-popup": [ + "cpe:2.3:a:freebiesdownload:pvn_auth_popup:*:*:*:*:*:wordpress:*:*" + ], "pwa-for-wp": [ "cpe:2.3:a:magazine3:pwa_for_wp_\\\u0026_amp:*:*:*:*:*:wordpress:*:*" ], @@ -18054,6 +18164,9 @@ "responsive-vector-maps": [ "cpe:2.3:a:thinkupthemes:responsive_vector_maps:*:*:*:*:*:wordpress:*:*" ], + "responsive-video": [ + "cpe:2.3:a:kirstyburgoine:responsive_video:*:*:*:*:*:wordpress:*:*" + ], "responsive-video-embed": [ "cpe:2.3:a:lepileppanen:responsive_video_embed:*:*:*:*:*:wordpress:*:*" ], @@ -18069,6 +18182,9 @@ "restaurant-reservations": [ "cpe:2.3:a:fivestarplugins:five_star_restaurant_reservations:*:*:*:*:*:wordpress:*:*" ], + "restaurant-solutions-checklist": [ + "cpe:2.3:a:josephlopreste:restaurant_solutions_-_checklist:*:*:*:*:*:wordpress:*:*" + ], "restrict-content": [ "cpe:2.3:a:liquidweb:restrict_content:*:*:*:*:*:wordpress:*:*" ], @@ -18590,6 +18706,9 @@ "shortcode-addons": [ "cpe:2.3:a:oxilab:shortcode_addons:*:*:*:*:*:wordpress:*:*" ], + "shortcode-cleaner-lite": [ + "cpe:2.3:a:jozoor:shortcode_cleaner_lite:*:*:*:*:*:wordpress:*:*" + ], "shortcode-factory": [ "cpe:2.3:a:wpmadeasy:shortcode_factory:*:*:*:*:*:wordpress:*:*" ], @@ -18972,12 +19091,18 @@ "simple-video-embedder": [ "cpe:2.3:a:simple_video_embedder_project:simple_video_embedder:*:*:*:*:*:wordpress:*:*" ], + "simple-video-management-system": [ + "cpe:2.3:a:maximize:simple_video_management_system:*:*:*:*:*:wordpress:*:*" + ], "simple-yearly-archive": [ "cpe:2.3:a:simple_yearly_archive_project:simple_yearly_archive:*:*:*:*:*:wordpress:*:*" ], "simple-youtube-responsive": [ "cpe:2.3:a:simple_youtube_responsive_project:simple_youtube_responsive:*:*:*:*:*:wordpress:*:*" ], + "simplebooklet": [ + "cpe:2.3:a:simplebooklet:simplebooklet_pdf_viewer_and_embedder:*:*:*:*:*:wordpress:*:*" + ], "simplemap": [ "cpe:2.3:a:simplemap-plugin:simplemap_store_locator:*:*:*:*:*:wordpress:*:*" ], @@ -19448,6 +19573,9 @@ "spiffy-calendar": [ "cpe:2.3:a:spiffyplugins:spiffy_calendar:*:*:*:*:*:wordpress:*:*" ], + "spiritual-gifts-survey": [ + "cpe:2.3:a:mynamedia:spiritual_gifts_survey_\\(and_optional_s.h.a.p.e_survey\\):*:*:*:*:*:wordpress:*:*" + ], "splash-header": [ "cpe:2.3:a:zeesweb:splash_header:*:*:*:*:*:wordpress:*:*" ], @@ -20258,6 +20386,9 @@ "ttv-easy-embed-player": [ "cpe:2.3:a:streamweasels:twitch_player:*:*:*:*:*:wordpress:*:*" ], + "tube-video-ads-lite": [ + "cpe:2.3:a:gualdoni:tube_video_ads_lite:*:*:*:*:*:wordpress:*:*" + ], "tubepress": [ "cpe:2.3:a:tubepress:tubepress:*:*:*:*:*:wordpress:*:*" ], @@ -20416,6 +20547,9 @@ "cpe:2.3:a:ultimatemember:ultimate_member:*:*:*:*:*:wordpress:*:*", "cpe:2.3:a:ultimatemember:user_profile_\\\u0026_membership:*:*:*:*:*:wordpress:*:*" ], + "ultimate-noindex-nofollow-tool": [ + "cpe:2.3:a:jonkemp:ultimate_noindex_nofollow_tool:*:*:*:*:*:wordpress:*:*" + ], "ultimate-noindex-nofollow-tool-ii": [ "cpe:2.3:a:ultimate_noindex_nofollow_tool_ii_project:ultimate_noindex_nofollow_tool_ii:*:*:*:*:*:wordpress:*:*" ], @@ -20551,6 +20685,9 @@ "url-media-uploader": [ "cpe:2.3:a:apprhyme:url_media_uploader:*:*:*:*:*:wordpress:*:*" ], + "url-params": [ + "cpe:2.3:a:jeremyshapiro:url_params:*:*:*:*:*:wordpress:*:*" + ], "url-shortify": [ "cpe:2.3:a:kaizencoders:url_shortify:*:*:*:*:*:wordpress:*:*" ], @@ -20678,6 +20815,9 @@ "vayu-blocks": [ "cpe:2.3:a:themehunk:vayu_blocks:*:*:*:*:*:wordpress:*:*" ], + "vc-addons-by-bit14": [ + "cpe:2.3:a:pagebuilderaddons:web_and_woocommerce_addons_for_wpbakery_builder:*:*:*:*:*:wordpress:*:*" + ], "vc-tabs": [ "cpe:2.3:a:oxilab:responsive_tabs:*:*:*:*:*:wordpress:*:*" ], @@ -20780,6 +20920,9 @@ "visual-portfolio": [ "cpe:2.3:a:visualportfolio:visual_portfolio\\,_photo_gallery_\\\u0026_post_grid:*:*:*:*:*:wordpress:*:*" ], + "visual-sound": [ + "cpe:2.3:a:freakingwildchild:visual_sound:*:*:*:*:*:wordpress:*:*" + ], "visualcomposer": [ "cpe:2.3:a:visualcomposer:visual_composer_website_builder:*:*:*:*:*:wordpress:*:*" ], @@ -20950,6 +21093,9 @@ "web-application-firewall": [ "cpe:2.3:a:miniorange:web_application_firewall:*:*:*:*:*:wordpress:*:*" ], + "web-cam": [ + "cpe:2.3:a:murtuzamakda52:web-cam:*:*:*:*:*:wordpress:*:*" + ], "web-directory-free": [ "cpe:2.3:a:salephpscripts:web_directory_free:*:*:*:*:*:wordpress:*:*" ], @@ -21015,6 +21161,9 @@ "website-article-monetization-by-magenet": [ "cpe:2.3:a:magenet:website_article_monetization:*:*:*:*:*:wordpress:*:*" ], + "website-contact-form-with-file-upload": [ + "cpe:2.3:a:najeebmedia:website_contact_form_with_file_upload:*:*:*:*:*:wordpress:*:*" + ], "website-file-changes-monitor": [ "cpe:2.3:a:melapress:melapress_file_monitor:*:*:*:*:*:wordpress:*:*" ], @@ -21120,6 +21269,9 @@ "widgets-on-pages": [ "cpe:2.3:a:widgets-on-pages_project:widgets-on-pages:*:*:*:*:*:wordpress:*:*" ], + "widgets-reset": [ + "cpe:2.3:a:justintadlock:widgets_reset:*:*:*:*:*:wordpress:*:*" + ], "winterlock": [ "cpe:2.3:a:wp_system_log_project:wp_system_log:*:*:*:*:*:wordpress:*:*" ], @@ -21127,7 +21279,11 @@ "cpe:2.3:a:themeinprogress:wip_custom_login:*:*:*:*:*:wordpress:*:*" ], "wise-chat": [ - "cpe:2.3:a:kaine:wise_chat:*:*:*:*:*:wordpress:*:*" + "cpe:2.3:a:kaine:wise_chat:*:*:*:*:*:wordpress:*:*", + "cpe:2.3:a:kainex:wise_chat:*:*:*:*:*:wordpress:*:*" + ], + "wise-forms": [ + "cpe:2.3:a:kainex:wise_forms:*:*:*:*:*:wordpress:*:*" ], "wiseagentleadform": [ "cpe:2.3:a:wiseagent:wise_agent_capture_forms:*:*:*:*:*:wordpress:*:*" @@ -21227,6 +21383,9 @@ "woo-ecommerce-tracking-for-google-and-facebook": [ "cpe:2.3:a:multidots:enhanced_ecommerce_google_analytics_for_woocommerce:*:*:*:*:*:wordpress:*:*" ], + "woo-edit-templates": [ + "cpe:2.3:a:ioannup:edit_woocommerce_templates:*:*:*:*:*:wordpress:*:*" + ], "woo-esto": [ "cpe:2.3:a:rebing:woocommerce_esto:*:*:*:*:*:wordpress:*:*" ], @@ -21539,6 +21698,9 @@ "wordpress-access-control": [ "cpe:2.3:a:brandonwamboldt:wordpress_access_control:*:*:*:*:*:wordpress:*:*" ], + "wordpress-activity-o-meter": [ + "cpe:2.3:a:erwinwolff:wordpress_activity-o-meter:*:*:*:*:*:wordpress:*:*" + ], "wordpress-backup-to-dropbox": [ "cpe:2.3:a:wordpress_backup_to_dropbox_project:wordpress_backup_to_dropbox:*:*:*:*:*:wordpress:*:*" ], @@ -21598,6 +21760,9 @@ "wordpress-tooltips": [ "cpe:2.3:a:tooltips:wordpress_tooltips:*:*:*:*:free:wordpress:*:*" ], + "wordpress-twitterbot": [ + "cpe:2.3:a:corbyboy:marketing_twitter_bot:*:*:*:*:*:wordpress:*:*" + ], "wordpress-users": [ "cpe:2.3:a:jonathonkemp:wordpress_users:*:*:*:*:*:wordpress:*:*" ], @@ -21610,6 +21775,9 @@ "wow-carousel-for-divi-lite": [ "cpe:2.3:a:elegantthemes:divi_carousel:*:*:*:*:free:wordpress:*:*" ], + "wow-entrance-effects-wee": [ + "cpe:2.3:a:darkosxrc:wow_entrance_effects_\\(wee\\!\\):*:*:*:*:*:wordpress:*:*" + ], "wow-moodboard-lite": [ "cpe:2.3:a:wownmedia:wow_moodboard:*:*:*:*:lite:wordpress:*:*" ], @@ -21632,6 +21800,9 @@ "wp-accessibility-helper": [ "cpe:2.3:a:volkov:wp_accessibility_helper:*:*:*:*:*:wordpress:*:*" ], + "wp-action-network": [ + "cpe:2.3:a:actionnetwork:action_network:*:*:*:*:*:wordpress:*:*" + ], "wp-admin-logo-changer": [ "cpe:2.3:a:wp_admin_logo_changer_project:wp_admin_logo_changer:*:*:*:*:*:wordpress:*:*" ], @@ -21641,6 +21812,9 @@ "wp-airbnb-review-slider": [ "cpe:2.3:a:ljapps:wp_airbnb_review_slider:*:*:*:*:*:wordpress:*:*" ], + "wp-ajax-contact-form": [ + "cpe:2.3:a:sportsdanka:wp_ajax_contact_form:*:*:*:*:*:wordpress:*:*" + ], "wp-all-export": [ "cpe:2.3:a:soflyy:export_any_wordpress_data_to_xml\\/csv:*:*:*:*:*:wordpress:*:*", "cpe:2.3:a:soflyy:wp_all_export:*:*:*:*:pro:wordpress:*:*" @@ -22034,6 +22208,9 @@ "wp-email-capture": [ "cpe:2.3:a:winwar:wp_email_capture:*:*:*:*:*:wordpress:*:*" ], + "wp-email-newsletter": [ + "cpe:2.3:a:megamindstechnologies:wordpress_email_newsletter:*:*:*:*:*:wordpress:*:*" + ], "wp-email-users": [ "cpe:2.3:a:techspawn:wp-email-users:*:*:*:*:*:wordpress:*:*" ], @@ -22168,6 +22345,9 @@ "wp-gdpr-core": [ "cpe:2.3:a:appsaloon:wp-gdpr:*:*:*:*:*:wordpress:*:*" ], + "wp-geshi-highlight": [ + "cpe:2.3:a:jgehrcke:wp-geshi-highlight:*:*:*:*:*:wordpress:*:*" + ], "wp-githuber-md": [ "cpe:2.3:a:terryl:wp_githuber_md:*:*:*:*:*:wordpress:*:*" ], @@ -22261,6 +22441,9 @@ "wp-image-slideshow": [ "cpe:2.3:a:gopiplus:wp_image_slideshow:*:*:*:*:*:wordpress:*:*" ], + "wp-image-uploader": [ + "cpe:2.3:a:ivanm:wp_image_uploader:*:*:*:*:*:wordpress:*:*" + ], "wp-image-zoooom": [ "cpe:2.3:a:silkypress:wp_image_zoom:*:*:*:*:*:wordpress:*:*" ], @@ -22460,6 +22643,9 @@ "wp-multi-store-locator": [ "cpe:2.3:a:wpexperts:wp_multi_store_locator:*:*:*:*:*:wordpress:*:*" ], + "wp-multitasking": [ + "cpe:2.3:a:ngothang:wp_multitasking:*:*:*:*:*:wordpress:*:*" + ], "wp-nested-pages": [ "cpe:2.3:a:kylephillips:nested_pages:*:*:*:*:*:wordpress:*:*" ], @@ -22472,6 +22658,9 @@ "wp-olivecart": [ "cpe:2.3:a:wp-olivecart_project:wp-olivecart:*:*:*:*:*:wordpress:*:*" ], + "wp-online-users-stats": [ + "cpe:2.3:a:hk1993:wp_online_users_stats:*:*:*:*:*:wordpress:*:*" + ], "wp-open-graph": [ "cpe:2.3:a:custom4web:wp_open_graph:*:*:*:*:*:wordpress:*:*" ], @@ -22903,6 +23092,9 @@ "wp-symposium-pro": [ "cpe:2.3:a:wpsymposiumpro:wp_symposium_pro:*:*:*:*:*:wordpress:*:*" ], + "wp-syntax": [ + "cpe:2.3:a:connections-pro:wp-syntax:*:*:*:*:*:wordpress:*:*" + ], "wp-t-wap": [ "cpe:2.3:a:wp-t-wap_project:wp-t-wap:*:*:*:*:*:wordpress:*:*" ], @@ -22948,6 +23140,9 @@ "wp-timed-popup": [ "cpe:2.3:a:timed_popup_project:timed_popup:*:*:*:*:*:wordpress:*:*" ], + "wp-tithely": [ + "cpe:2.3:a:jordangillman:tithe.ly_giving_button:*:*:*:*:*:wordpress:*:*" + ], "wp-todo": [ "cpe:2.3:a:delower:wp_to_do:*:*:*:*:*:wordpress:*:*" ], @@ -23570,6 +23765,9 @@ "z-url-preview": [ "cpe:2.3:a:z-url_preview_project:z-url_preview:*:*:*:*:*:wordpress:*:*" ], + "zalomeni": [ + "cpe:2.3:a:kybernaut:zalomeni:*:*:*:*:*:wordpress:*:*" + ], "zarinpal-paid-downloads": [ "cpe:2.3:a:amini7:zarinpal_paid_download:*:*:*:*:*:wordpress:*:*" ], diff --git a/syft/pkg/cataloger/internal/cpegenerate/filter.go b/syft/pkg/cataloger/internal/cpegenerate/filter.go index 8423c320b..2fffe67a8 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/filter.go +++ b/syft/pkg/cataloger/internal/cpegenerate/filter.go @@ -14,6 +14,7 @@ type filterFn func(cpe cpe.Attributes, p pkg.Package) bool var cpeFilters = []filterFn{ disallowJiraClientServerMismatch, + disallowTomcatServerCPEForEmbeddedEL, disallowJenkinsServerCPEForPluginPackage, disallowJenkinsCPEsNotAssociatedWithJenkins, disallowNonParseableCPEs, @@ -71,3 +72,18 @@ func disallowJiraClientServerMismatch(c cpe.Attributes, p pkg.Package) bool { } return false } + +// disallowTomcatServerCPEForEmbeddedEL prevents the server-level Tomcat CPE from being +// assigned to the EL-only embedded artifact. Keep its artifact-specific CPE candidates. +func disallowTomcatServerCPEForEmbeddedEL(c cpe.Attributes, p pkg.Package) bool { + if c.Vendor != "apache" || c.Product != "tomcat" { + return false + } + + metadata, ok := p.Metadata.(pkg.JavaArchive) + if !ok || metadata.PomProperties == nil { + return false + } + + return metadata.PomProperties.GroupID == "org.apache.tomcat.embed" && metadata.PomProperties.ArtifactID == "tomcat-embed-el" +} diff --git a/syft/pkg/cataloger/internal/cpegenerate/generate_test.go b/syft/pkg/cataloger/internal/cpegenerate/generate_test.go index 9a463b6ad..ae03d205b 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/generate_test.go +++ b/syft/pkg/cataloger/internal/cpegenerate/generate_test.go @@ -963,6 +963,39 @@ func TestGeneratePackageCPEs(t *testing.T) { } } +func TestTomcatEmbeddedCPEs(t *testing.T) { + packageFor := func(artifactID string) pkg.Package { + return pkg.Package{ + Name: artifactID, + Version: "11.0.20", + Language: pkg.Java, + Type: pkg.JavaPkg, + Metadata: pkg.JavaArchive{ + PomProperties: &pkg.JavaPomProperties{ + GroupID: "org.apache.tomcat.embed", + ArtifactID: artifactID, + }, + }, + } + } + + hasCPE := func(cpes []cpe.CPE, vendor, product string) bool { + for _, generated := range cpes { + if generated.Attributes.Vendor == vendor && generated.Attributes.Product == product { + return true + } + } + return false + } + + elCPEs := FromPackageAttributes(packageFor("tomcat-embed-el")) + assert.False(t, hasCPE(elCPEs, "apache", "tomcat"), "EL-only module should not inherit the server CPE") + assert.True(t, hasCPE(elCPEs, "apache", "tomcat-embed-el"), "keep the artifact-specific CPE candidate") + + coreCPEs := FromPackageAttributes(packageFor("tomcat-embed-core")) + assert.True(t, hasCPE(coreCPEs, "apache", "tomcat"), "core module should retain the Tomcat server CPE") +} + func TestCandidateProducts(t *testing.T) { tests := []struct { name string diff --git a/syft/pkg/cataloger/internal/dotnet/pe/pe_test.go b/syft/pkg/cataloger/internal/dotnet/pe/pe_test.go index b612e7bc0..ca518ac03 100644 --- a/syft/pkg/cataloger/internal/dotnet/pe/pe_test.go +++ b/syft/pkg/cataloger/internal/dotnet/pe/pe_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source/stereoscopesource" @@ -199,6 +200,7 @@ func fixtureResolvers(t *testing.T, fixtures ...string) map[string]file.Resolver continue } + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", fixture) s := stereoscopesource.New(img, stereoscopesource.ImageConfig{ diff --git a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go index e74a3a06e..22042fcf1 100644 --- a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go +++ b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go @@ -24,6 +24,7 @@ import ( "github.com/anchore/syft/internal/cmptest" "github.com/anchore/syft/internal/licenses" "github.com/anchore/syft/internal/relationship" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/internal/tmpdir" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" @@ -150,9 +151,11 @@ func (p *CatalogTester) FromFile(t *testing.T, path string) *CatalogTester { fixture, err := os.Open(path) require.NoError(t, err) + // parsers don't own the reader (the generic cataloger closes it), so close it here + t.Cleanup(func() { _ = fixture.Close() }) p.reader = file.LocationReadCloser{ - Location: file.NewVirtualLocationFromDirectory(fixture.Name(), fixture.Name(), *stereofile.NewFileReference(stereofile.Path(absPath))), + Location: file.NewVirtualLocationFromDirectory(filepath.ToSlash(fixture.Name()), filepath.ToSlash(fixture.Name()), *stereofile.NewFileReference(stereofile.Path(absPath))), ReadCloser: fixture, } return p @@ -201,6 +204,7 @@ func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *Cat return p } + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", fixtureName) s := stereoscopesource.New(img, stereoscopesource.ImageConfig{ diff --git a/syft/pkg/cataloger/java/archive_parser_test.go b/syft/pkg/cataloger/java/archive_parser_test.go index 84167c5ef..c586a421e 100644 --- a/syft/pkg/cataloger/java/archive_parser_test.go +++ b/syft/pkg/cataloger/java/archive_parser_test.go @@ -6,6 +6,7 @@ import ( "io" "os" "os/exec" + "path" "path/filepath" "strings" "syscall" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" @@ -1605,6 +1607,9 @@ func generateJavaBuildFixture(t *testing.T, fixturePath string) { return } + // the fixtures are built with maven/gradle inside linux containers + testutils.SkipWithoutLinuxContainers(t) + makeTask := strings.TrimPrefix(fixturePath, "testdata/java-builds/") t.Log(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask)) @@ -1630,7 +1635,8 @@ func generateJavaMetadataJarFixture(t *testing.T, fixtureName string, fileExtens return fixturePath } - makeTask := filepath.Join("cache", fixtureName+"."+fileExtension) + // make targets are posix paths regardless of host + makeTask := path.Join("cache", fixtureName+"."+fileExtension) t.Log(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask)) cwd, err := os.Getwd() diff --git a/syft/pkg/cataloger/java/internal/maven/config_test.go b/syft/pkg/cataloger/java/internal/maven/config_test.go index 1a6ab4c50..80de474bd 100644 --- a/syft/pkg/cataloger/java/internal/maven/config_test.go +++ b/syft/pkg/cataloger/java/internal/maven/config_test.go @@ -47,6 +47,8 @@ func Test_defaultMavenLocalRepoDir(t *testing.T) { home, err := filepath.Abs(test.home) require.NoError(t, err) t.Setenv("HOME", home) + // os.UserHomeDir reads USERPROFILE on windows + t.Setenv("USERPROFILE", home) } got := defaultMavenLocalRepoDir() require.Equal(t, test.expected, got) diff --git a/syft/pkg/cataloger/java/internal/maven/test/mock_repo.go b/syft/pkg/cataloger/java/internal/maven/test/mock_repo.go index 6ab462f6b..a2ed64895 100644 --- a/syft/pkg/cataloger/java/internal/maven/test/mock_repo.go +++ b/syft/pkg/cataloger/java/internal/maven/test/mock_repo.go @@ -31,7 +31,7 @@ func MockRepo(t *testing.T, dir string) (url string) { t.Cleanup(server.Close) - matches, err := doublestar.Glob(os.DirFS(dir), filepath.Join("**", "*.pom")) + matches, err := doublestar.Glob(os.DirFS(dir), "**/*.pom") require.NoError(t, err) for _, match := range matches { diff --git a/syft/pkg/cataloger/perl/cataloger_test.go b/syft/pkg/cataloger/perl/cataloger_test.go index 78f9551b7..6fad8926a 100644 --- a/syft/pkg/cataloger/perl/cataloger_test.go +++ b/syft/pkg/cataloger/perl/cataloger_test.go @@ -8,6 +8,7 @@ import ( "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/internal/packagemetadata" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" @@ -523,6 +524,7 @@ func TestCpanCatalogers_metadataTypes(t *testing.T) { func imageResolver(t *testing.T, fixture string) file.Resolver { t.Helper() + testutils.SkipWithoutLinuxContainers(t) img := imagetest.GetFixtureImage(t, "docker-archive", fixture) resolver, err := stereoscopesource.New(img, stereoscopesource.ImageConfig{Reference: fixture}). FileResolver(source.SquashedScope) diff --git a/syft/pkg/cataloger/python/parse_wheel_egg.go b/syft/pkg/cataloger/python/parse_wheel_egg.go index 0c4cf1563..33d2d0161 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "path" - "path/filepath" "strings" "github.com/scylladb/go-set/strset" @@ -53,7 +52,7 @@ func fetchInstalledFiles(resolver file.Resolver, metadataLocation file.Location, // to reconcile the installed-files.txt path to the same layer (or the next adjacent lower layer). // find the installed-files.txt file relative to the directory where the METADATA file resides (in path AND layer structure) - installedFilesPath := filepath.Join(filepath.Dir(metadataLocation.RealPath), "installed-files.txt") + installedFilesPath := path.Join(path.Dir(metadataLocation.RealPath), "installed-files.txt") installedFilesRef := resolver.RelativeFileByPath(metadataLocation, installedFilesPath) if installedFilesRef != nil { @@ -83,7 +82,7 @@ func fetchRecordFiles(resolver file.Resolver, metadataLocation file.Location) (f // to reconcile the RECORD path to the same layer (or the next adjacent lower layer). // find the RECORD file relative to the directory where the METADATA file resides (in path AND layer structure) - recordPath := filepath.Join(filepath.Dir(metadataLocation.RealPath), "RECORD") + recordPath := path.Join(path.Dir(metadataLocation.RealPath), "RECORD") recordRef := resolver.RelativeFileByPath(metadataLocation, recordPath) if recordRef != nil { @@ -107,8 +106,8 @@ func fetchRecordFiles(resolver file.Resolver, metadataLocation file.Location) (f // fetchTopLevelPackages finds a corresponding top_level.txt file for the given python package metadata file and returns the set of package names contained. func fetchTopLevelPackages(resolver file.Resolver, metadataLocation file.Location) (pkgs []string, sources []file.Location, err error) { // a top_level.txt file specifies the python top-level packages (provided by this python package) installed into site-packages - parentDir := filepath.Dir(metadataLocation.RealPath) - topLevelPath := filepath.Join(parentDir, "top_level.txt") + parentDir := path.Dir(metadataLocation.RealPath) + topLevelPath := path.Join(parentDir, "top_level.txt") topLevelLocation := resolver.RelativeFileByPath(metadataLocation, topLevelPath) if topLevelLocation == nil { @@ -157,8 +156,8 @@ type vcsInfo struct { } func fetchDirectURLData(resolver file.Resolver, metadataLocation file.Location) (d *pkg.PythonDirectURLOriginInfo, sources []file.Location, err error) { - parentDir := filepath.Dir(metadataLocation.RealPath) - directURLPath := filepath.Join(parentDir, "direct_url.json") + parentDir := path.Dir(metadataLocation.RealPath) + directURLPath := path.Join(parentDir, "direct_url.json") directURLLocation := resolver.RelativeFileByPath(metadataLocation, directURLPath) if directURLLocation == nil { @@ -269,7 +268,7 @@ func findLicenses(ctx context.Context, resolver file.Resolver, m parsedData) pkg continue } - if licenses.IsLicenseFile(filepath.Base(f.Path)) { + if licenses.IsLicenseFile(path.Base(f.Path)) { candidatePaths.Add(path.Join(m.SitePackagesRootPath, f.Path)) } } diff --git a/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go b/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go index 5b0572e3a..05c49f5f4 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go @@ -2,7 +2,7 @@ package python import ( "fmt" - "path/filepath" + "path" "strings" "github.com/bmatcuk/doublestar/v4" @@ -48,13 +48,13 @@ func parseWheelOrEggMetadata(locationReader file.LocationReadCloser) (parsedData } // add additional metadata not stored in the egg/wheel metadata file - path := locationReader.Path() + metadataPath := locationReader.Path() - pd.SitePackagesRootPath = determineSitePackagesRootPath(path) + pd.SitePackagesRootPath = determineSitePackagesRootPath(metadataPath) if pd.Licenses != "" || pd.LicenseExpression != "" { - pd.LicenseFilePath = path + pd.LicenseFilePath = metadataPath } else if pd.LicenseFile != "" { - pd.LicenseFilePath = filepath.Join(filepath.Dir(path), pd.LicenseFile) + pd.LicenseFilePath = path.Join(path.Dir(metadataPath), pd.LicenseFile) } pd.DistInfoLocation = locationReader.Location @@ -144,18 +144,18 @@ func getFieldType(key, in string) any { // isEggRegularFile determines if the specified path is the regular file variant // of egg metadata (as opposed to a directory that contains more metadata // files). -func isEggRegularFile(path string) bool { - return doublestar.MatchUnvalidated(eggInfoGlob, path) +func isEggRegularFile(p string) bool { + return doublestar.MatchUnvalidated(eggInfoGlob, p) } // determineSitePackagesRootPath returns the path of the site packages root, // given the egg metadata file or directory specified in the path. -func determineSitePackagesRootPath(path string) string { - if isEggRegularFile(path) { - return filepath.Clean(filepath.Dir(path)) +func determineSitePackagesRootPath(p string) string { + if isEggRegularFile(p) { + return path.Clean(path.Dir(p)) } - return filepath.Clean(filepath.Dir(filepath.Dir(path))) + return path.Clean(path.Dir(path.Dir(p))) } // handleFieldBodyContinuation returns the updated value for the specified field after processing the specified line. diff --git a/syft/pkg/cataloger/python/parse_wheel_egg_record.go b/syft/pkg/cataloger/python/parse_wheel_egg_record.go index 94982f10b..12c983673 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg_record.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg_record.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "path" "path/filepath" "strings" @@ -87,11 +88,13 @@ func parseInstalledFiles(reader io.Reader, location, sitePackagesRootPath string } if location != "" && sitePackagesRootPath != "" { - joinedPath := filepath.Join(filepath.Dir(location), line) + joinedPath := path.Join(path.Dir(location), line) + // the path package has no Rel, and filepath.Rel hands back host separators on windows line, err = filepath.Rel(sitePackagesRootPath, joinedPath) if err != nil { return nil, err } + line = filepath.ToSlash(line) } installedFile := pkg.PythonFileRecord{ diff --git a/syft/pkg/cataloger/swift/parse_package_resolved_test.go b/syft/pkg/cataloger/swift/parse_package_resolved_test.go index 494a164c1..98803e43d 100644 --- a/syft/pkg/cataloger/swift/parse_package_resolved_test.go +++ b/syft/pkg/cataloger/swift/parse_package_resolved_test.go @@ -122,8 +122,7 @@ func TestParsePackageResolved_empty(t *testing.T) { dir := t.TempDir() fixture = filepath.Join(dir, "Package.resolved") - _, err := os.Create(fixture) - require.NoError(t, err) + require.NoError(t, os.WriteFile(fixture, nil, 0600)) pkgtest.TestFileParser(t, fixture, parsePackageResolved, nil, nil) } diff --git a/syft/source/snapsource/snap_source_test.go b/syft/source/snapsource/snap_source_test.go index 6a4b23e4e..bf03a59a4 100644 --- a/syft/source/snapsource/snap_source_test.go +++ b/syft/source/snapsource/snap_source_test.go @@ -30,6 +30,10 @@ import ( ) func TestNewFromLocal(t *testing.T) { + // the request is resolved with filepath.Abs, which adds a volume on windows + localSnap, err := filepath.Abs("/test/local.snap") + require.NoError(t, err) + tests := []struct { name string cfg Config @@ -40,13 +44,13 @@ func TestNewFromLocal(t *testing.T) { { name: "local file exists", cfg: Config{ - Request: "/test/local.snap", + Request: localSnap, DigestAlgorithms: []crypto.Hash{crypto.SHA256}, }, setup: func(fs afero.Fs) { - require.NoError(t, createMockSquashfsFile(fs, "/test/local.snap")) + require.NoError(t, createMockSquashfsFile(fs, localSnap)) }, - wantRequest: "/test/local.snap", + wantRequest: localSnap, }, { name: "resolve home dir exists", @@ -65,18 +69,18 @@ func TestNewFromLocal(t *testing.T) { { name: "local file with architecture specified", cfg: Config{ - Request: "/test/local.snap", + Request: localSnap, Platform: &image.Platform{ Architecture: "arm64", }, }, setup: func(fs afero.Fs) { - require.NoError(t, createMockSquashfsFile(fs, "/test/local.snap")) + require.NoError(t, createMockSquashfsFile(fs, localSnap)) }, wantErr: func(t assert.TestingT, err error, msgAndArgs ...any) bool { return assert.Error(t, err, msgAndArgs...) && assert.Contains(t, err.Error(), "architecture cannot be specified for local snap files", msgAndArgs...) }, - wantRequest: "/test/local.snap", + wantRequest: localSnap, }, } for _, tt := range tests { @@ -288,6 +292,8 @@ func writeSquashfs(t *testing.T, manifest string) string { require.NoError(t, err) _, err = w.Write([]byte(contents)) require.NoError(t, err) + // close before finalizing: windows directory listings can report a stale (zero) size for open files + require.NoError(t, w.Close()) } // note: go-diskfs wants paths relative to the image root, without a leading slash diff --git a/syft/source/snapsource/snap_test.go b/syft/source/snapsource/snap_test.go index e6b3ac450..a55d4e664 100644 --- a/syft/source/snapsource/snap_test.go +++ b/syft/source/snapsource/snap_test.go @@ -145,7 +145,9 @@ func TestNewSnapFromFile(t *testing.T) { DigestAlgorithms: []crypto.Hash{crypto.SHA256}, }, setup: func() string { - path := "/test/valid.snap" + // newSnapFromFile resolves the request with filepath.Abs, which adds a volume on windows + path, err := filepath.Abs("/test/valid.snap") + require.NoError(t, err) require.NoError(t, createMockSquashfsFile(fs, path)) return path }, @@ -179,7 +181,9 @@ func TestNewSnapFromFile(t *testing.T) { name: "path is directory", cfg: Config{}, setup: func() string { - path := "/test/directory" + // newSnapFromFile resolves the request with filepath.Abs, which adds a volume on windows + path, err := filepath.Abs("/test/directory") + require.NoError(t, err) require.NoError(t, fs.MkdirAll(path, 0755)) return path }, @@ -471,7 +475,9 @@ func TestGetSnapFileInfo(t *testing.T) { { name: "path is directory", setup: func() string { - path := "/test/directory" + // newSnapFromFile resolves the request with filepath.Abs, which adds a volume on windows + path, err := filepath.Abs("/test/directory") + require.NoError(t, err) require.NoError(t, fs.MkdirAll(path, 0755)) return path }, diff --git a/syft/source/stereoscopesource/image_source_test.go b/syft/source/stereoscopesource/image_source_test.go index 4b8f709af..fc42d12a4 100644 --- a/syft/source/stereoscopesource/image_source_test.go +++ b/syft/source/stereoscopesource/image_source_test.go @@ -10,6 +10,7 @@ import ( "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/internal/testutils" "github.com/anchore/syft/syft/internal/testutil" "github.com/anchore/syft/syft/source" ) @@ -78,6 +79,7 @@ func Test_StereoscopeImage_Exclusions(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { + testutils.SkipWithoutLinuxContainers(t) imageName := strings.SplitN(imagetest.PrepareFixtureImage(t, "docker-archive", test.input), ":", 2)[1] img, err := stereoscope.GetImage(context.TODO(), imageName) diff --git a/test/containers-storage/source-test.sh b/test/containers-storage/source-test.sh new file mode 100755 index 000000000..cf4d98607 --- /dev/null +++ b/test/containers-storage/source-test.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# build an image with podman or buildah into an isolated containers-storage store, then verify syft can scan it out +# of that store (for both the vfs and overlay drivers) and that plain image references never touch the store. +# +# usage: SYFT_BINARY_LOCATION=/path/to/syft 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 + +syft_binary="${SYFT_BINARY_LOCATION:?expected SYFT_BINARY_LOCATION to point at a linux syft binary built with containers-storage support}" +if [[ ! -f "$syft_binary" ]]; then + printf 'syft binary not found at %s\n' "$syft_binary" >&2 + exit 1 +fi +chmod +x "$syft_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 syft under " unshare". Root opens the store directly. +run_syft() { + if [[ "$(id -u)" -eq 0 ]]; then + "$syft_binary" "$@" + else + "$builder" unshare "$syft_binary" "$@" + fi +} + +build_context="$test_dir/context" +mkdir -p "$build_context" +cat > "$build_context/package.json" <<'EOF' +{"name":"syft-cs-fixture","version":"1.2.3"} +EOF +cat > "$build_context/Containerfile" <<'EOF' +FROM scratch +COPY package.json /app/node_modules/syft-cs-fixture/package.json +EOF + +# note: the image name deliberately avoids the string "containers-storage" so stderr greps below are unambiguous +image_ref="localhost/syft-cs-fixture:latest" +missing_ref="localhost/syft-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" < "$out.json" 2> "$out.stderr" + ;; + scheme) + run_syft -vv "containers-storage:$image_ref" --output json > "$out.json" 2> "$out.stderr" + ;; + default-pull-source) + SYFT_SOURCE_IMAGE_DEFAULT_PULL_SOURCE=containers-storage \ + run_syft -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.metadata.userInput == $image_ref and + any(.artifacts[]; .name == "syft-cs-fixture" and .version == "1.2.3" 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_syft "$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_syft --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 diff --git a/test/install/2_download_release_asset_test.sh b/test/install/2_download_release_asset_test.sh index 003477149..4ea173e15 100755 --- a/test/install/2_download_release_asset_test.sh +++ b/test/install/2_download_release_asset_test.sh @@ -43,3 +43,9 @@ run_test_case test_download_release_asset "${release}" "linux" "amd64" "deb" "ap run_test_case test_download_release_asset "${release}" "linux" "arm64" "tar.gz" "application/gzip" run_test_case test_download_release_asset "${release}" "linux" "arm64" "rpm" "application/x-rpm" run_test_case test_download_release_asset "${release}" "linux" "arm64" "deb" "application/vnd.debian.binary-package" + +# releases before VERIFY_SIGN_BUNDLE_VERSION are signed with a separate .sig and .pem instead of a sigstore bundle, +# so keep exercising that path against a fixed release +legacy_release="v1.52.0" + +run_test_case test_download_release_asset "${legacy_release}" "linux" "amd64" "tar.gz" "application/gzip" diff --git a/test/install/4_prep_signature_verification_test.sh b/test/install/4_prep_signature_verification_test.sh index 401364ac0..545aaf4b9 100755 --- a/test/install/4_prep_signature_verification_test.sh +++ b/test/install/4_prep_signature_verification_test.sh @@ -87,3 +87,61 @@ test_prep_signature_verification() { } run_test_case test_prep_signature_verification + +# ensure the verification material is passed through to cosign and the old-cosign hint only shows when warranted +test_verify_sign() { + # verify_sign [checksums-file-path] [verification-material-flags...] + + OG_COSIGN_BINARY=${COSIGN_BINARY} + + tmpdir=$(mktemp -d) + args_file="${tmpdir}/args" + hint="requires cosign v2.5.0 or newer" + + # stub cosign that records its arguments, then prints STUB_OUTPUT and exits with STUB_EXIT + COSIGN_BINARY="${tmpdir}/cosign" + cat > "${COSIGN_BINARY}" < "${args_file}" +echo "\${STUB_OUTPUT}" +exit "\${STUB_EXIT}" +STUB + chmod +x "${COSIGN_BINARY}" + + identity="--certificate-identity https://github.com/${OWNER}/${REPO}/.github/workflows/release.yaml@refs/heads/main --certificate-oidc-issuer https://token.actions.githubusercontent.com" + + # bundle verification succeeds + STUB_OUTPUT="Verified OK" STUB_EXIT=0 verify_sign "checksums.txt" --bundle "checksums.txt.sigstore.json" >/dev/null 2>&1 + assertEquals "0" "$?" "bundle verification should succeed" + assertEquals "verify-blob checksums.txt --bundle checksums.txt.sigstore.json ${identity}" "$(cat "${args_file}")" "unexpected cosign args for bundle" + + # legacy verification succeeds + STUB_OUTPUT="Verified OK" STUB_EXIT=0 verify_sign "checksums.txt" --certificate "c.pem" --signature "c.sig" >/dev/null 2>&1 + assertEquals "0" "$?" "legacy verification should succeed" + assertEquals "verify-blob checksums.txt --certificate c.pem --signature c.sig ${identity}" "$(cat "${args_file}")" "unexpected cosign args for legacy" + + # cosign too old to read the bundle: fail with the hint (this is what cosign v2.2.4 through v2.4.1 print) + output=$(STUB_OUTPUT="Error: bundle does not contain cert for verification, please provide public key" STUB_EXIT=1 verify_sign "checksums.txt" --bundle "b.json" 2>&1) + assertEquals "1" "$?" "old cosign should fail verification" + assertContains "${output}" "${hint}" "old cosign should get the version hint" + + # bad signature (e.g. tampered checksums): fail without the hint + output=$(STUB_OUTPUT="Error: invalid signature when validating ASN.1 encoded signature" STUB_EXIT=1 verify_sign "checksums.txt" --bundle "b.json" 2>&1) + assertEquals "1" "$?" "bad signature should fail verification" + assertNotContains "${output}" "${hint}" "bad signature should not get the version hint" + + # unparseable bundle (e.g. a 404 body): fail without the hint + output=$(STUB_OUTPUT="Error: invalid character 'N' looking for beginning of value" STUB_EXIT=1 verify_sign "checksums.txt" --bundle "b.json" 2>&1) + assertEquals "1" "$?" "unparseable bundle should fail verification" + assertNotContains "${output}" "${hint}" "unparseable bundle should not get the version hint" + + # legacy failure never gets the bundle hint, even with the same output + output=$(STUB_OUTPUT="Error: bundle does not contain cert for verification, please provide public key" STUB_EXIT=1 verify_sign "checksums.txt" --certificate "c.pem" --signature "c.sig" 2>&1) + assertEquals "1" "$?" "legacy failure should fail verification" + assertNotContains "${output}" "${hint}" "legacy failure should not get the version hint" + + COSIGN_BINARY=${OG_COSIGN_BINARY} + rm -rf -- "${tmpdir}" +} + +run_test_case test_verify_sign diff --git a/test/install/5_download_asset_failures_test.sh b/test/install/5_download_asset_failures_test.sh new file mode 100755 index 000000000..edc51234f --- /dev/null +++ b/test/install/5_download_asset_failures_test.sh @@ -0,0 +1,84 @@ +. test_harness.sh + +# serve release assets from a local fixture dir instead of the network (a missing file is a failed download) +http_download() { + src="${FIXTURE_DIR}/${2##*/}" + [ -f "${src}" ] || return 1 + cp "${src}" "$1" +} + +# ensure every missing or mismatched piece of a release stops download_asset before anything is installed +test_download_asset_failures() { + # download_asset [release-url-prefix] [download-path] [name] [os] [arch] [version] [format] + + # we are expecting error messages, which is confusing to look at in passing tests... disable logging for now + log_set_priority -1 + + OG_COSIGN_BINARY=${COSIGN_BINARY} + + tmpdir=$(mktemp -d) + FIXTURE_DIR="${tmpdir}/release" + dest="${tmpdir}/dest" + args_file="${tmpdir}/cosign-args" + mkdir -p "${FIXTURE_DIR}" "${dest}" + + # stub cosign that records it was called and always verifies + COSIGN_BINARY="${tmpdir}/cosign" + printf '#!/bin/sh\necho "$@" > "%s"\n' "${args_file}" > "${COSIGN_BINARY}" + chmod +x "${COSIGN_BINARY}" + + asset="syft_1.54.0_linux_amd64.tar.gz" + echo "the real asset" > "${FIXTURE_DIR}/${asset}" + echo "$(hash_sha256 "${FIXTURE_DIR}/${asset}") ${asset}" > "${FIXTURE_DIR}/syft_1.54.0_checksums.txt" + echo "{}" > "${FIXTURE_DIR}/syft_1.54.0_checksums.txt.sigstore.json" + + run() { + rm -rf "${dest:?}"/* "${args_file}" + download_asset "https://example.invalid/v1.54.0" "${dest}" "syft" "linux" "amd64" "1.54.0" "tar.gz" + } + + # happy path, verified and unverified + VERIFY_SIGN=false + asset_path=$(run) + assertEquals "0" "$?" "complete release should download" + assertEquals "${dest}/${asset}" "${asset_path}" "unexpected asset path" + + VERIFY_SIGN=true + run >/dev/null + assertEquals "0" "$?" "complete release should verify" + assertContains "$(cat "${args_file}")" "--bundle ${dest}/syft_1.54.0_checksums.txt.sigstore.json" "bundle should be passed to cosign" + + # missing bundle: fail before cosign is ever called + mv "${FIXTURE_DIR}/syft_1.54.0_checksums.txt.sigstore.json" "${tmpdir}/bundle" + run >/dev/null + assertEquals "1" "$?" "missing bundle should fail" + assertFilesDoesNotExist "${args_file}" "cosign should not be called without a bundle" + mv "${tmpdir}/bundle" "${FIXTURE_DIR}/syft_1.54.0_checksums.txt.sigstore.json" + + for VERIFY_SIGN in true false; do + # asset does not match the checksums file + echo "a tampered asset" > "${FIXTURE_DIR}/${asset}" + run >/dev/null + assertEquals "1" "$?" "checksum mismatch should fail (verify=${VERIFY_SIGN})" + echo "the real asset" > "${FIXTURE_DIR}/${asset}" + + # missing asset + mv "${FIXTURE_DIR}/${asset}" "${tmpdir}/asset" + run >/dev/null + assertEquals "1" "$?" "missing asset should fail (verify=${VERIFY_SIGN})" + mv "${tmpdir}/asset" "${FIXTURE_DIR}/${asset}" + + # missing checksums file + mv "${FIXTURE_DIR}/syft_1.54.0_checksums.txt" "${tmpdir}/checksums" + run >/dev/null + assertEquals "1" "$?" "missing checksums should fail (verify=${VERIFY_SIGN})" + mv "${tmpdir}/checksums" "${FIXTURE_DIR}/syft_1.54.0_checksums.txt" + done + + COSIGN_BINARY=${OG_COSIGN_BINARY} + rm -rf -- "${tmpdir}" + + log_set_priority 0 +} + +run_test_case test_download_asset_failures diff --git a/test/install/5_release_script_args_test.sh b/test/install/5_release_script_args_test.sh new file mode 100755 index 000000000..1093b7f66 --- /dev/null +++ b/test/install/5_release_script_args_test.sh @@ -0,0 +1,41 @@ +. test_harness.sh + +# make certain main defers to the (stubbed) tagged release script regardless of the caller's env +DOWNLOAD_TAG_INSTALL_SCRIPT=true + +get_release_tag() { + printf '%s\n' "$3" +} + +prep_signature_verification() { + return 0 +} + +# the "tagged release script" records each argument it receives, one bracketed arg per line +http_copy() { + cat <<'SCRIPT' +printf '[%s]\n' "$@" > "$INSTALL_ARG_CAPTURE" +SCRIPT +} + +test_release_script_receives_original_args() { + test_dir=$(mktemp -d) + INSTALL_ARG_CAPTURE="${test_dir}/actual" + export INSTALL_ARG_CAPTURE + + # note: paths are read here instead of passed to run_test_case, which word-splits its arguments + while IFS= read -r install_path; do + main -b "${install_path}" -d v0.80.0 + + printf '[%s]\n' -b "${install_path}" -d v0.80.0 > "${test_dir}/expected" + assertFilesEqual "${test_dir}/expected" "${INSTALL_ARG_CAPTURE}" "args forwarded to the release script should match the original args (path='${install_path}')" + done <<'PATHS' +/tmp/bin with spaces +/tmp/bin* +/tmp/it's "quoted" +PATHS + + rm -rf -- "${test_dir}" +} + +run_test_case test_release_script_receives_original_args diff --git a/test/install/environments/Dockerfile-alpine-3.6 b/test/install/environments/Dockerfile-alpine-3.6 index 69df678a6..31cfbae30 100644 --- a/test/install/environments/Dockerfile-alpine-3.6 +++ b/test/install/environments/Dockerfile-alpine-3.6 @@ -1,5 +1,9 @@ FROM alpine:3.6 RUN apk update && apk add python3 wget curl unzip make ca-certificates jq -RUN curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" && \ - mv cosign-linux-amd64 /usr/local/bin/cosign && \ +# cosign is pinned (and checksum verified) so the install tests always run with a cosign that can verify +# sigstore bundles (releases >= v1.53.0 need v2.5.0 or newer) +ARG COSIGN_VERSION=v3.1.3 +ARG COSIGN_SHA256=4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71 +RUN curl -sSfL -o /usr/local/bin/cosign "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64" && \ + echo "${COSIGN_SHA256} /usr/local/bin/cosign" | sha256sum -c - && \ chmod +x /usr/local/bin/cosign diff --git a/test/install/environments/Dockerfile-busybox-1.36 b/test/install/environments/Dockerfile-busybox-1.36 index e2cf2d722..2f9747f90 100644 --- a/test/install/environments/Dockerfile-busybox-1.36 +++ b/test/install/environments/Dockerfile-busybox-1.36 @@ -5,17 +5,22 @@ RUN apk update && apk add ca-certificates # so let docker pull the image that matches the hosts architecture first and then pull the correct asset FROM busybox:1.36.1-musl +# cosign is pinned (and checksum verified) so the install tests always run with a cosign that can verify +# sigstore bundles (releases >= v1.53.0 need v2.5.0 or newer) +ARG COSIGN_VERSION=v3.1.3 +ARG COSIGN_SHA256_AMD64=4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71 +ARG COSIGN_SHA256_ARM64=c5d324e091826b0d7a78eb16fef316450b4eb9aaec045611c08ba06f5e73220a RUN ARCH=$(uname -m) && \ if [ "$ARCH" = "x86_64" ]; then \ - COSIGN_ARCH="amd64"; \ + COSIGN_ARCH="amd64"; COSIGN_SHA256="${COSIGN_SHA256_AMD64}"; \ elif [ "$ARCH" = "aarch64" ]; then \ - COSIGN_ARCH="arm64"; \ + COSIGN_ARCH="arm64"; COSIGN_SHA256="${COSIGN_SHA256_ARM64}"; \ else \ echo "Unsupported architecture: $ARCH" && exit 1; \ fi && \ - echo "Downloading cosign for $COSIGN_ARCH" && \ - wget https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-${COSIGN_ARCH} && \ - mv cosign-linux-${COSIGN_ARCH} /bin/cosign && \ + echo "Downloading cosign ${COSIGN_VERSION} for $COSIGN_ARCH" && \ + wget -O /bin/cosign https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-${COSIGN_ARCH} && \ + echo "${COSIGN_SHA256} /bin/cosign" | sha256sum -c - && \ chmod +x /bin/cosign COPY --from=certs /etc/ssl/certs /etc/ssl/certs diff --git a/test/install/environments/Dockerfile-ubuntu-20.04 b/test/install/environments/Dockerfile-ubuntu-20.04 index 87c09e66f..967610232 100644 --- a/test/install/environments/Dockerfile-ubuntu-20.04 +++ b/test/install/environments/Dockerfile-ubuntu-20.04 @@ -1,5 +1,9 @@ FROM --platform=linux/amd64 ubuntu:20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e3b2026b4fc2faba RUN apt update -y && apt install make python3 curl unzip jq -y -RUN LATEST_VERSION=$(curl https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d : -f2 | tr -d "v\", ") && \ - curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign_${LATEST_VERSION}_amd64.deb" && \ - dpkg -i cosign_${LATEST_VERSION}_amd64.deb +# cosign is pinned (and checksum verified) so the install tests always run with a cosign that can verify +# sigstore bundles (releases >= v1.53.0 need v2.5.0 or newer) +ARG COSIGN_VERSION=v3.1.3 +ARG COSIGN_SHA256=4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71 +RUN curl -sSfL -o /usr/local/bin/cosign "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64" && \ + echo "${COSIGN_SHA256} /usr/local/bin/cosign" | sha256sum -c - && \ + chmod +x /usr/local/bin/cosign diff --git a/test/install/test_harness.sh b/test/install/test_harness.sh index 8db34de36..e38b8b092 100644 --- a/test/install/test_harness.sh +++ b/test/install/test_harness.sh @@ -56,6 +56,31 @@ assertFileExists() { fi } +assertContains() { + haystack=$1 + needle=$2 + msg=$3 + case "$haystack" in + *"$needle"*) ;; + *) + echo "assertContains failed: '$needle' not found in '$haystack' $msg" + exit 2 + ;; + esac +} + +assertNotContains() { + haystack=$1 + needle=$2 + msg=$3 + case "$haystack" in + *"$needle"*) + echo "assertNotContains failed: '$needle' found in '$haystack' $msg" + exit 2 + ;; + esac +} + assertFilesEqual() { want=$1 got=$2