mirror of
https://github.com/anchore/syft.git
synced 2026-10-23 03:22:21 +02:00
Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83dfbcc24a | ||
|
|
68da404bd7 | ||
|
|
35bf33bcf0 | ||
|
|
7f73d6a603 | ||
|
|
ef40eec38d | ||
|
|
80212d87c2 | ||
|
|
b14123ecc1 | ||
|
|
e00aeb16e4 | ||
|
|
fa6f6d70f0 | ||
|
|
0975d80dc0 | ||
|
|
4d823eaaa7 | ||
|
|
138e42b428 | ||
|
|
ecd8fb7740 | ||
|
|
dd639c09b2 | ||
|
|
31a352d030 | ||
|
|
08e913a2ce | ||
|
|
5ef5b1badc | ||
|
|
b236f8c1f7 | ||
|
|
476ce60768 | ||
|
|
16223e6dd7 | ||
|
|
1286689419 | ||
|
|
295454945c | ||
|
|
86baeeb481 | ||
|
|
2dcf5163b8 | ||
|
|
12b8ba47fb | ||
|
|
138d9ce2a0 | ||
|
|
8a229b16c4 | ||
|
|
9af0098a68 | ||
|
|
2840ea6653 | ||
|
|
b38e40e3e6 | ||
|
|
a6eedecd31 | ||
|
|
691b1357b2 | ||
|
|
c890e7f17f | ||
|
|
29fd7d0dec | ||
|
|
289137ff3c | ||
|
|
22ffd5209d | ||
|
|
a0852ebf98 | ||
|
|
d380ad08b5 | ||
|
|
ae9534203d | ||
|
|
dc1ef98237 | ||
|
|
36a3af41d9 | ||
|
|
3e2bc6ed09 |
+1
-1
@@ -16,7 +16,7 @@ tools:
|
||||
# used to upload test fixture cache
|
||||
- name: oras
|
||||
version:
|
||||
want: v1.3.2
|
||||
want: v1.3.3
|
||||
method: github-release
|
||||
with:
|
||||
repo: oras-project/oras
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
if: ${{ github.event.inputs.phase == 'all' }}
|
||||
permissions:
|
||||
contents: read # required for fetching tags
|
||||
uses: anchore/workflows/.github/workflows/check-version-available.yaml@b0c30a80409130d329aaa356fd64a34d8c0b3375 # v0.7.2
|
||||
uses: anchore/workflows/.github/workflows/check-version-available.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
|
||||
with:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
packages: write # required for publishing release artifacts to GitHub packages
|
||||
id-token: write # required for keyless signing (cosign/sigstore OIDC)
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: true
|
||||
@@ -83,14 +83,19 @@ jobs:
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
# dockers_v2 builds multi-platform manifests in a single buildx invocation, which requires a
|
||||
# docker-container driver builder (the default builder on the runner cannot build multi-platform).
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 #v4.4.0
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 #v4.5.1
|
||||
with:
|
||||
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
|
||||
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 #v4.4.0
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 #v4.5.1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -20,12 +20,12 @@ jobs:
|
||||
contents: read
|
||||
security-events: write # for uploading SARIF results
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run zizmor"
|
||||
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
|
||||
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
|
||||
with:
|
||||
# there is a pass/fail gate as a repo ruleset (if there is no ruleset configured then the action will pass by default)
|
||||
advanced-security: true
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -213,7 +213,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
+52
-9
@@ -3,8 +3,10 @@ package main
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
. "github.com/anchore/go-make"
|
||||
"github.com/anchore/go-make/config"
|
||||
"github.com/anchore/go-make/file"
|
||||
"github.com/anchore/go-make/lang"
|
||||
"github.com/anchore/go-make/run"
|
||||
@@ -27,22 +29,43 @@ func main() {
|
||||
gotest.Name("unit"),
|
||||
gotest.ExcludeGlob("**/test/**"),
|
||||
gotest.CoverageThreshold(62),
|
||||
race(),
|
||||
),
|
||||
|
||||
// integration tests: native go-make Task. The race-detector smoke against a
|
||||
// real image stays bundled here (RunsOn integration) so `make integration`
|
||||
// behaves like the Taskfile version did.
|
||||
gotest.Tasks(
|
||||
gotest.Name("integration"),
|
||||
gotest.IncludeGlob("./cmd/syft/internal/test/integration/..."),
|
||||
gotest.Verbose(),
|
||||
gotest.NoCoverage(),
|
||||
),
|
||||
// integration tests: run `go test` directly instead of via gotest.Tasks(), which
|
||||
// has no way to set a timeout. The suite is a single package of ~36 sequential
|
||||
// tests over 18 docker fixture images, so with a cold fixture cache (every image
|
||||
// built + saved inline, under -race) it runs well past `go test`'s default 10m
|
||||
// timeout -- which is exactly the case when regenerating the fixture cache from
|
||||
// scratch. -count=1 keeps the go test cache from short-circuiting a run whose
|
||||
// side effect (the built fixtures) is the thing we're after.
|
||||
//
|
||||
// The race-detector smoke against a real image stays bundled here (RunsOn
|
||||
// integration) so `make integration` behaves like the Taskfile version did.
|
||||
Task{
|
||||
Name: "integration",
|
||||
Description: "run integration tests",
|
||||
RunsOn: lang.List("test"),
|
||||
Run: func() {
|
||||
raceFlag := ""
|
||||
if raceEnabled() {
|
||||
raceFlag = " -race"
|
||||
}
|
||||
Run(
|
||||
"go test -count=1 -timeout=30m -v"+raceFlag+" ./cmd/syft/internal/test/integration/...",
|
||||
run.Env("GODEBUG", "dontfreezetheworld=1"),
|
||||
)
|
||||
},
|
||||
},
|
||||
Task{
|
||||
Name: "integration:race-smoke",
|
||||
Description: "exercise the CLI with the race detector",
|
||||
RunsOn: lang.List("integration"),
|
||||
Run: func() {
|
||||
if !raceEnabled() {
|
||||
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")
|
||||
},
|
||||
},
|
||||
@@ -132,6 +155,26 @@ func main() {
|
||||
)
|
||||
}
|
||||
|
||||
// raceEnabled is the single switch for the race detector across every test suite.
|
||||
// Unset it and we keep go-make's behavior (on in CI, off locally and on windows);
|
||||
// set RACE=false to turn it off everywhere -- worth doing when rebuilding the test
|
||||
// fixture cache from scratch, where every suite is dominated by building docker
|
||||
// fixtures and the race detector only adds wall clock. RACE=true forces it on.
|
||||
func raceEnabled() bool {
|
||||
if enabled, err := strconv.ParseBool(config.Env("RACE", "")); err == nil {
|
||||
return enabled
|
||||
}
|
||||
return config.CI && !config.Windows
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return func(c *gotest.Config) {
|
||||
c.Race = raceEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
// snapshotBinPath replicates the SNAPSHOT_BIN computation from the prior Taskfile:
|
||||
// <repoRoot>/snapshot/<os>-build_<os>_<arch>/syft, where arch maps amd64->amd64_v1
|
||||
// and arm64->arm64_v8.0 to match goreleaser's per-target output directory naming.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/go-make/config"
|
||||
"github.com/anchore/go-make/tasks/gotest"
|
||||
)
|
||||
|
||||
func TestRaceEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
env string // "" means RACE is unset
|
||||
ci bool
|
||||
want bool
|
||||
}{
|
||||
{name: "default in CI", ci: true, want: true},
|
||||
{name: "default locally", ci: false, want: false},
|
||||
{name: "explicitly off in CI", env: "false", ci: true, want: false},
|
||||
{name: "explicitly on locally", env: "true", ci: false, want: true},
|
||||
{name: "unparseable falls back to default", env: "yes-please", ci: true, want: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// always go through t.Setenv (it registers the restore) so an ambient
|
||||
// RACE from the caller's environment can't leak into the unset cases
|
||||
t.Setenv("RACE", tt.env)
|
||||
if tt.env == "" {
|
||||
os.Unsetenv("RACE")
|
||||
}
|
||||
orig := config.CI
|
||||
config.CI = tt.ci
|
||||
t.Cleanup(func() { config.CI = orig })
|
||||
|
||||
if got := raceEnabled(); got != tt.want {
|
||||
t.Errorf("raceEnabled() = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
// the gotest suites get the same answer through the functional option
|
||||
var cfg gotest.Config
|
||||
race()(&cfg)
|
||||
if cfg.Race != tt.want {
|
||||
t.Errorf("race() set Race = %v, want %v", cfg.Race, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,7 @@ tasks:
|
||||
desc: Run tests for pipeline utils
|
||||
cmds:
|
||||
- cmd: .github/scripts/labeler_test.py
|
||||
- cmd: go test -C .make ./...
|
||||
|
||||
snapshot-smoke-test:
|
||||
desc: Run a smoke test on the snapshot builds + docker images
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// TestMultiPlatformOCIImageSelection verifies that syft can load a single platform out of a
|
||||
// multi-platform OCI image on disk (both --from oci-dir and --from oci-archive) by selecting the
|
||||
// image that matches the requested platform.
|
||||
func TestMultiPlatformOCIImageSelection(t *testing.T) {
|
||||
remoteImage := "docker.io/library/busybox:1.38.0"
|
||||
|
||||
// Per-platform image config digests within the multi-platform index, obtained from the OCI layout
|
||||
// (see anchore/stereoscope integration tests: TestPlatformSelectionWithOciLocalSources).
|
||||
expectedDigest := map[string]string{
|
||||
"arm64": "sha256:e0e8b3cbfed68a90084781e2962f9c0deead51c5a3f11a488eef0283a4284bc2",
|
||||
"s390x": "sha256:0cf160e720a8e4f20883b72276a6eaded83b79539f3f1d39e35a3336154b9960",
|
||||
"amd64": "sha256:c6348fa86ba0fb2108c9334f5fe913ddc6d853313e655891f133a0127c30099f",
|
||||
"ppc64le": "sha256:b144fc0e06537d07956cde340b878a81a717e394edb736d3110858a12a6635cb",
|
||||
}
|
||||
|
||||
// syft --from source tag -> stereoscope OCI source used to prepare the local fixture
|
||||
sources := map[string]image.Source{
|
||||
"oci-dir": image.OciDirectorySource,
|
||||
"oci-archive": image.OciTarballSource,
|
||||
}
|
||||
|
||||
for from, imageSource := range sources {
|
||||
t.Run(from, func(t *testing.T) {
|
||||
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, remoteImage)
|
||||
for _, arch := range []string{"amd64", "arm64", "s390x", "ppc64le"} {
|
||||
t.Run(fmt.Sprintf("linux/%s", arch), func(t *testing.T) {
|
||||
platform, err := image.NewPlatform("linux/" + arch)
|
||||
require.NoError(t, err)
|
||||
|
||||
src, err := syft.GetSource(
|
||||
context.Background(),
|
||||
localPath,
|
||||
syft.DefaultGetSourceConfig().WithSources(from).WithPlatform(platform),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
meta, ok := src.Describe().Metadata.(source.ImageMetadata)
|
||||
require.True(t, ok, "expected image metadata, got %T", src.Describe().Metadata)
|
||||
|
||||
// The raw config of the selected image must match the requested platform...
|
||||
assertConfigPlatform(t, meta.RawConfig, "linux", arch)
|
||||
// ...and it must be the exact per-platform image from the multi-platform index.
|
||||
assert.Equal(t, expectedDigest[arch], meta.ID)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiPlatformOCIImageSelection_UnavailablePlatform verifies that requesting a platform not present
|
||||
// in the multi-platform OCI image results in an error rather than silently selecting the wrong image.
|
||||
func TestMultiPlatformOCIImageSelection_UnavailablePlatform(t *testing.T) {
|
||||
remoteImage := "docker.io/library/busybox:1.38.0"
|
||||
|
||||
// windows/amd64 is not present in this linux-only multi-platform image
|
||||
platform, err := image.NewPlatform("windows/amd64")
|
||||
require.NoError(t, err)
|
||||
|
||||
sources := map[string]image.Source{
|
||||
"oci-dir": image.OciDirectorySource,
|
||||
"oci-archive": image.OciTarballSource,
|
||||
}
|
||||
|
||||
for from, imageSource := range sources {
|
||||
t.Run(from, func(t *testing.T) {
|
||||
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, remoteImage)
|
||||
|
||||
_, err := syft.GetSource(
|
||||
context.Background(),
|
||||
localPath,
|
||||
syft.DefaultGetSourceConfig().WithSources(from).WithPlatform(platform),
|
||||
)
|
||||
require.ErrorContains(t, err, "windows/amd64")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiPlatformOCIImageSelection_DefaultPlatform verifies that not specifying a platform results
|
||||
// in the current platform being selected.
|
||||
func TestMultiPlatformOCIImageSelection_DefaultPlatform(t *testing.T) {
|
||||
remoteImage := "docker.io/library/busybox:1.38.0"
|
||||
|
||||
sources := map[string]image.Source{
|
||||
"oci-dir": image.OciDirectorySource,
|
||||
"oci-archive": image.OciTarballSource,
|
||||
}
|
||||
|
||||
for from, imageSource := range sources {
|
||||
t.Run(from, func(t *testing.T) {
|
||||
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, remoteImage)
|
||||
|
||||
src, err := syft.GetSource(
|
||||
context.Background(),
|
||||
localPath,
|
||||
syft.DefaultGetSourceConfig().WithSources(from),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
meta, ok := src.Describe().Metadata.(source.ImageMetadata)
|
||||
require.True(t, ok, "expected image metadata, got %T", src.Describe().Metadata)
|
||||
|
||||
// The raw config of the selected image must match the current platform
|
||||
assertConfigPlatform(t, meta.RawConfig, "linux", runtime.GOARCH)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// assertConfigPlatform asserts the os/architecture recorded in a raw OCI image config document.
|
||||
func assertConfigPlatform(t *testing.T, rawConfig []byte, os, architecture string) {
|
||||
t.Helper()
|
||||
require.NotEmpty(t, rawConfig)
|
||||
|
||||
var cfg struct {
|
||||
OS string `json:"os"`
|
||||
Architecture string `json:"architecture"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rawConfig, &cfg))
|
||||
|
||||
assert.Equal(t, os, cfg.OS)
|
||||
assert.Equal(t, architecture, cfg.Architecture)
|
||||
}
|
||||
@@ -7,4 +7,6 @@
|
||||
!image-pkg-coverage/pkgs/java/*.hpi
|
||||
|
||||
**/go.sum
|
||||
!image-go-bin-arch-coverage/go.sum
|
||||
!image-go-bin-arch-coverage/go.sum
|
||||
|
||||
cache/
|
||||
@@ -18,11 +18,11 @@ require (
|
||||
github.com/anchore/go-homedir v0.1.1
|
||||
github.com/anchore/go-logger v0.1.1
|
||||
github.com/anchore/go-macholibre v0.1.1
|
||||
github.com/anchore/go-rpmdb v0.1.0
|
||||
github.com/anchore/go-rpmdb v0.2.0
|
||||
github.com/anchore/go-sync v0.1.1
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.2.0
|
||||
github.com/anchore/stereoscope v0.2.2
|
||||
github.com/anchore/stereoscope v0.3.0
|
||||
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
|
||||
@@ -34,14 +34,14 @@ require (
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/dave/jennifer v1.7.1
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/diskfs/go-diskfs v1.9.3
|
||||
github.com/diskfs/go-diskfs v1.9.4
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/elliotchance/phpserialize v1.4.0
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.7.0
|
||||
github.com/gkampitakis/go-snaps v0.5.22
|
||||
github.com/go-git/go-billy/v5 v5.9.0
|
||||
github.com/gkampitakis/go-snaps v0.5.23
|
||||
github.com/go-git/go-billy/v5 v5.9.1
|
||||
github.com/go-git/go-git/v5 v5.19.1
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
@@ -52,18 +52,18 @@ require (
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/color v1.6.1
|
||||
github.com/gpustack/gguf-parser-go v0.24.1
|
||||
github.com/gpustack/gguf-parser-go v0.25.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/hashicorp/go-getter v1.8.6
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/hcl/v2 v2.24.0
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/invopop/jsonschema v0.14.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.1
|
||||
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.19.0
|
||||
github.com/magiconair/properties v1.8.10
|
||||
github.com/klauspost/compress v1.19.1
|
||||
github.com/magiconair/properties v1.18.11
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/moby/sys/mountinfo v0.7.2
|
||||
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
|
||||
@@ -83,7 +83,7 @@ require (
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/ulikunitz/xz v0.5.16
|
||||
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
|
||||
@@ -93,12 +93,12 @@ require (
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
||||
golang.org/x/mod v0.37.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/mod v0.38.0
|
||||
golang.org/x/net v0.57.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.47.0
|
||||
golang.org/x/tools v0.48.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.53.0
|
||||
modernc.org/sqlite v1.55.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -118,7 +118,7 @@ require (
|
||||
cloud.google.com/go/storage v1.61.3 // 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.31.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
@@ -168,10 +168,10 @@ require (
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // 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.2 // indirect
|
||||
github.com/containerd/containerd/v2 v2.3.3 // 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
|
||||
@@ -183,14 +183,14 @@ require (
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v29.5.3+incompatible // indirect
|
||||
github.com/docker/cli v29.6.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
github.com/docker/go-connections v0.7.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.5 // indirect
|
||||
@@ -238,8 +238,8 @@ require (
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/moby/api v1.54.2 // indirect
|
||||
github.com/moby/moby/client v0.4.1 // indirect
|
||||
github.com/moby/moby/api v1.55.0 // indirect
|
||||
github.com/moby/moby/client v0.5.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.1 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
@@ -257,7 +257,7 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.3.0 // indirect
|
||||
github.com/pborman/indent v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||
github.com/piprate/json-gold v0.7.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
@@ -299,7 +299,7 @@ require (
|
||||
github.com/zclconf/go-cty v1.16.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.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.43.0 // indirect
|
||||
@@ -309,22 +309,22 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // 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.53.0 // indirect
|
||||
golang.org/x/crypto v0.54.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/term v0.45.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
gonum.org/v1/gonum v0.17.0 // indirect
|
||||
google.golang.org/api v0.271.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
google.golang.org/grpc v1.82.1 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
modernc.org/libc v1.73.4 // indirect
|
||||
modernc.org/libc v1.74.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
@@ -82,8 +82,8 @@ github.com/CycloneDX/cyclonedx-go v0.11.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByX
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
|
||||
@@ -136,8 +136,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.1.0 h1:Q8dc208/HYzCqhx0L1zurfm1UPil24hlo9NjkdFmLdE=
|
||||
github.com/anchore/go-rpmdb v0.1.0/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
|
||||
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-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.1 h1:91SZ+YqUIIHmf2jPAYZPuHqM/ZqnK1pVJDWtET6+AJE=
|
||||
@@ -146,8 +146,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.2.2 h1:SGTLGoF6GHmKEn9Bb6LYpzzgz9nhvMZgN/c4fTSQjYY=
|
||||
github.com/anchore/stereoscope v0.2.2/go.mod h1:ylJXJKebLctP7u9ewguB1d0zUETJxvAA7r2DiuljDTM=
|
||||
github.com/anchore/stereoscope v0.3.0 h1:6mGGRxm4zRKj/jNiSM7g6XGwWihrnkQRJj11hcULDqY=
|
||||
github.com/anchore/stereoscope v0.3.0/go.mod h1:QIBWxa5WCrjtBqXH0+RIuECATrffcbaNdMWquZ83+OI=
|
||||
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=
|
||||
@@ -290,14 +290,14 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||
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.2 h1:eLven1YxRMkeiKu7IcMrPKE+gn8sGR1DqHbbshMEvWM=
|
||||
github.com/containerd/containerd/v2 v2.3.2/go.mod h1:rHKGm3VW6wNrINb3x8mNT+w7qYXFVElTt/8HTuxVhD4=
|
||||
github.com/containerd/containerd/v2 v2.3.3 h1:MUNBVVBTBpPll7KPh5GTvkC3cfG03PQLAHVdsUoue9k=
|
||||
github.com/containerd/containerd/v2 v2.3.3/go.mod h1:rHKGm3VW6wNrINb3x8mNT+w7qYXFVElTt/8HTuxVhD4=
|
||||
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=
|
||||
@@ -333,14 +333,14 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||
github.com/diskfs/go-diskfs v1.9.3 h1:cLciNCeZ4QAXVxyPJDr1ZJ9N9CCG3rQlQ/z/Cs/cNDM=
|
||||
github.com/diskfs/go-diskfs v1.9.3/go.mod h1:TePJORO83Adh5pb2SqsxAwaP0fofFxKLkxctiS/9OQc=
|
||||
github.com/diskfs/go-diskfs v1.9.4 h1:0j2d7eG4IjyxL6+ChWbDPocdBCF6HQ4HBWU2WDYWVnc=
|
||||
github.com/diskfs/go-diskfs v1.9.4/go.mod h1:TePJORO83Adh5pb2SqsxAwaP0fofFxKLkxctiS/9OQc=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
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.5.3+incompatible h1:nbEFfz774vBwQ5KRYv7c/AghjReqnGISvrRhzjV0evs=
|
||||
github.com/docker/cli v29.5.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v29.6.1+incompatible h1:oO7F4nn3Ovr/5TlfTUWFbMwBSS/B7Xs6Epv26gBrUP8=
|
||||
github.com/docker/cli v29.6.1+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/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
@@ -371,14 +371,14 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
|
||||
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||
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=
|
||||
@@ -410,16 +410,16 @@ github.com/github/go-spdx/v2 v2.7.0 h1:GzfXx4wFdlilARxmFRXW/mgUy3A4vSqZocCMFV6XF
|
||||
github.com/github/go-spdx/v2 v2.7.0/go.mod h1:Ftc45YYG1WzpzwEPKRVm9Jv8vDqOrN4gWoCkK+bHer0=
|
||||
github.com/gkampitakis/ciinfo v0.3.4 h1:5eBSibVuSMbb/H6Elc0IIEFbkzCJi3lm94n0+U7Z0KY=
|
||||
github.com/gkampitakis/ciinfo v0.3.4/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-snaps v0.5.22 h1:xg9omphRnbDnimMCl1KqznC4krlxOGpkB0vDSfX2P7M=
|
||||
github.com/gkampitakis/go-snaps v0.5.22/go.mod h1:uy3lVzCCRRsAwYqSocyw5fY8xRLCYEfqoOJNxr8HonM=
|
||||
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/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=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||
github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
|
||||
github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
|
||||
@@ -558,8 +558,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/gpustack/gguf-parser-go v0.24.1 h1:nTYtL8HFK6ZhB90RKBu4oX2b3ZHpJLrMmKRfL9w9Cyc=
|
||||
github.com/gpustack/gguf-parser-go v0.24.1/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
|
||||
github.com/gpustack/gguf-parser-go v0.25.0 h1:1AMBhMKtI24nTtn588Bq53FqNiOvEw1x9Nb4HbRrThs=
|
||||
github.com/gpustack/gguf-parser-go v0.25.0/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 h1:vTCWu1wbdYo7PEZFem/rlr01+Un+wwVmI7wiegFdRLk=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc=
|
||||
@@ -624,8 +624,8 @@ github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8i
|
||||
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.1 h1:0fkCNhjrX0zPpwkWaDYU5VMrygg41Tu197mWILIJoqQ=
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.1/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.3 h1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ=
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.3/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
@@ -645,8 +645,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.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
|
||||
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
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=
|
||||
@@ -670,8 +670,8 @@ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQ
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/magiconair/properties v1.18.11 h1:j5ozYZl0zCjG7ahMDH0GWIobOvvUzT0BdAguG0ViKy0=
|
||||
github.com/magiconair/properties v1.18.11/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/maruel/natural v1.3.0 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg=
|
||||
github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||
@@ -723,10 +723,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
|
||||
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
|
||||
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
|
||||
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
||||
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc=
|
||||
github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s=
|
||||
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=
|
||||
@@ -786,8 +786,8 @@ github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrp
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
|
||||
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/piprate/json-gold v0.7.0 h1:bEMirgA5y8Z2loTQfxyIFfY+EflxH1CTP6r/KIlcJNw=
|
||||
@@ -947,8 +947,8 @@ 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.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/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/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/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
|
||||
@@ -998,8 +998,8 @@ 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.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
|
||||
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=
|
||||
@@ -1040,8 +1040,8 @@ 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.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1081,8 +1081,8 @@ 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.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1127,8 +1127,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1160,8 +1160,8 @@ 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.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1236,13 +1236,13 @@ 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.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1253,8 +1253,8 @@ 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.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1315,8 +1315,8 @@ 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.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1431,10 +1431,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-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
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=
|
||||
@@ -1462,8 +1462,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -1513,20 +1513,20 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
|
||||
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
||||
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
|
||||
modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc=
|
||||
modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
|
||||
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA=
|
||||
modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
||||
modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ=
|
||||
modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -1535,8 +1535,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M=
|
||||
modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=
|
||||
modernc.org/sqlite v1.55.0 h1:hIFh0MCH0rGinQ/4KYb5/UbCkRkb+UP+OkLCVWa5MTM=
|
||||
modernc.org/sqlite v1.55.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -152,6 +152,31 @@ func NewLocationFromImage(accessPath string, ref file.Reference, img *image.Imag
|
||||
}
|
||||
}
|
||||
|
||||
// NewVirtualLocationFromImage creates a new Location surfaced at realPath but whose contents and metadata are
|
||||
// described by the given reference. This is used to present a hardlink as the underlying type it points to (at the
|
||||
// hardlink's own path) so that image results are in parity with directory results, which cannot distinguish a
|
||||
// hardlink from a regular file. Note that RealPath (the hardlink's own path) intentionally differs from
|
||||
// ref.RealPath (the target's path); callers must not assume the two agree for these locations.
|
||||
func NewVirtualLocationFromImage(realPath, accessPath string, ref file.Reference, img *image.Image) Location {
|
||||
// the FileSystemID comes from the target ref's layer. a hardlink and its target are always materialized in the
|
||||
// same layer tar (a tar hardlink entry can only reference a file within the same archive), so the target's layer
|
||||
// digest is also the hardlink path's layer.
|
||||
layer := img.FileCatalog.Layer(ref)
|
||||
return Location{
|
||||
LocationData: LocationData{
|
||||
Coordinates: Coordinates{
|
||||
RealPath: realPath,
|
||||
FileSystemID: layer.Metadata.Digest,
|
||||
},
|
||||
AccessPath: accessPath,
|
||||
ref: ref,
|
||||
},
|
||||
LocationMetadata: LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory.
|
||||
func NewLocationFromDirectory(responsePath string, fd string, ref file.Reference) Location {
|
||||
return Location{
|
||||
|
||||
@@ -53,8 +53,20 @@ func (r *ContainerImageAllLayers) HasPath(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqueFileIDs stereoscopeFile.ReferenceSet, layerIdx int) ([]stereoscopeFile.Reference, error) {
|
||||
uniqueFiles := make([]stereoscopeFile.Reference, 0)
|
||||
func (r *ContainerImageAllLayers) locationsByRef(ref stereoscopeFile.Reference, accessPath string, uniqueFileIDs stereoscopeFile.ReferenceSet, layerPos int) ([]file.Location, error) {
|
||||
uniqueLocations := make([]file.Location, 0)
|
||||
|
||||
// if the access path is itself a hardlink, surface it as the underlying type it points to, at its own path
|
||||
// (bound to the target's content), so that image results are in parity with directory results (which cannot
|
||||
// tell a hardlink from a regular file). the path-based lookup is required because the search that produced ref
|
||||
// already followed the basename link, collapsing the hardlink onto its target.
|
||||
if ownRef, targetRef, ok := r.hardLinkAtPath(accessPath, r.layers[layerPos]); ok {
|
||||
if !uniqueFileIDs.Contains(ownRef) {
|
||||
uniqueFileIDs.Add(ownRef)
|
||||
uniqueLocations = append(uniqueLocations, file.NewVirtualLocationFromImage(string(ownRef.RealPath), accessPath, targetRef, r.img))
|
||||
}
|
||||
return uniqueLocations, nil
|
||||
}
|
||||
|
||||
// since there is potentially considerable work for each symlink/hardlink that needs to be resolved, let's check to see if this is a symlink/hardlink first
|
||||
entry, err := r.img.FileCatalog.Get(ref)
|
||||
@@ -65,22 +77,41 @@ func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqu
|
||||
if entry.Type == stereoscopeFile.TypeHardLink || entry.Type == stereoscopeFile.TypeSymLink {
|
||||
// a link may resolve in this layer or higher, assuming a squashed tree is used to search
|
||||
// we should search all possible resolutions within the valid source
|
||||
for _, subLayerIdx := range r.layers[layerIdx:] {
|
||||
for _, subLayerIdx := range r.layers[layerPos:] {
|
||||
resolvedRef, err := r.img.ResolveLinkByLayerSquash(ref, subLayerIdx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve link from layer (layer=%d ref=%+v): %w", subLayerIdx, ref, err)
|
||||
}
|
||||
if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) {
|
||||
uniqueFileIDs.Add(*resolvedRef.Reference)
|
||||
uniqueFiles = append(uniqueFiles, *resolvedRef.Reference)
|
||||
uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(accessPath, *resolvedRef.Reference, r.img))
|
||||
}
|
||||
}
|
||||
} else if !uniqueFileIDs.Contains(ref) {
|
||||
uniqueFileIDs.Add(ref)
|
||||
uniqueFiles = append(uniqueFiles, ref)
|
||||
uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(accessPath, ref, r.img))
|
||||
}
|
||||
|
||||
return uniqueFiles, nil
|
||||
return uniqueLocations, nil
|
||||
}
|
||||
|
||||
// hardLinkAtPath returns the hardlink's own reference and its resolved target reference when the basename of path is a
|
||||
// hardlink within the given layer. ok is false when path does not exist there or is not a hardlink. The lookup does
|
||||
// not follow the basename link so that the hardlink's own path is preserved. This adds a tree walk per
|
||||
// matched ref (path x layer); if it shows up in profiles, fold the hardlink check into the existing search resolution.
|
||||
func (r *ContainerImageAllLayers) hardLinkAtPath(path string, layerIdx int) (stereoscopeFile.Reference, stereoscopeFile.Reference, bool) {
|
||||
var own stereoscopeFile.Reference
|
||||
// use the squashed-to-layer view (not just this layer's additions) so a hardlink added in a lower layer is still
|
||||
// detected when the path is searched at a higher layer; otherwise it would collapse onto its target there.
|
||||
exists, resolution, err := r.img.Layers[layerIdx].SquashedTree.File(stereoscopeFile.Path(path))
|
||||
if err != nil || !exists || !resolution.HasReference() {
|
||||
return own, own, false
|
||||
}
|
||||
target, ok := r.resolveHardLinkTarget(*resolution.Reference, layerIdx)
|
||||
if !ok {
|
||||
return own, own, false
|
||||
}
|
||||
return *resolution.Reference, target, true
|
||||
}
|
||||
|
||||
// FilesByPath returns all file.References that match the given paths from any layer in the image.
|
||||
@@ -112,14 +143,13 @@ func (r *ContainerImageAllLayers) FilesByPath(paths ...string) ([]file.Location,
|
||||
}
|
||||
}
|
||||
|
||||
results, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
|
||||
locations, err := r.locationsByRef(*ref.Reference, path, uniqueFileIDs, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, result := range results {
|
||||
l := file.NewLocationFromImage(path, result, r.img)
|
||||
r.annotateLocation(&l)
|
||||
uniqueLocations = append(uniqueLocations, l)
|
||||
for i := range locations {
|
||||
r.annotateLocation(&locations[i])
|
||||
uniqueLocations = append(uniqueLocations, locations[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,14 +188,13 @@ func (r *ContainerImageAllLayers) FilesByGlob(patterns ...string) ([]file.Locati
|
||||
}
|
||||
}
|
||||
|
||||
refResults, err := r.fileByRef(*result.Reference, uniqueFileIDs, idx)
|
||||
locations, err := r.locationsByRef(*result.Reference, string(result.RequestPath), uniqueFileIDs, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, refResult := range refResults {
|
||||
l := file.NewLocationFromImage(string(result.RequestPath), refResult, r.img)
|
||||
r.annotateLocation(&l)
|
||||
uniqueLocations = append(uniqueLocations, l)
|
||||
for i := range locations {
|
||||
r.annotateLocation(&locations[i])
|
||||
uniqueLocations = append(uniqueLocations, locations[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,14 +262,13 @@ func (r *ContainerImageAllLayers) FilesByMIMEType(types ...string) ([]file.Locat
|
||||
continue
|
||||
}
|
||||
|
||||
refResults, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
|
||||
locations, err := r.locationsByRef(*ref.Reference, string(ref.RequestPath), uniqueFileIDs, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, refResult := range refResults {
|
||||
l := file.NewLocationFromImage(string(ref.RequestPath), refResult, r.img)
|
||||
r.annotateLocation(&l)
|
||||
uniqueLocations = append(uniqueLocations, l)
|
||||
for i := range locations {
|
||||
r.annotateLocation(&locations[i])
|
||||
uniqueLocations = append(uniqueLocations, locations[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,6 +284,11 @@ func (r *ContainerImageAllLayers) AllLocations(ctx context.Context) <-chan file.
|
||||
tree := r.img.Layers[layerIdx].Tree
|
||||
for _, ref := range tree.AllFiles(stereoscopeFile.AllTypes()...) {
|
||||
l := file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
|
||||
// surface a hardlink as the underlying type it points to (at its own path) so image results match
|
||||
// directory results, which cannot distinguish a hardlink from a regular file.
|
||||
if targetRef, ok := r.resolveHardLinkTarget(ref, layerIdx); ok {
|
||||
l = file.NewVirtualLocationFromImage(string(ref.RealPath), string(ref.RealPath), targetRef, r.img)
|
||||
}
|
||||
r.annotateLocation(&l)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -273,6 +306,21 @@ func (r *ContainerImageAllLayers) FileMetadataByLocation(location file.Location)
|
||||
return fileMetadataByLocation(r.img, location)
|
||||
}
|
||||
|
||||
// resolveHardLinkTarget returns the reference of a hardlink's underlying target (resolved relative to the given layer)
|
||||
// when ref is a hardlink; ok is false otherwise. No resolution is performed for non-hardlinks (symlinks keep their
|
||||
// existing resolution semantics).
|
||||
func (r *ContainerImageAllLayers) resolveHardLinkTarget(ref stereoscopeFile.Reference, layerIdx int) (stereoscopeFile.Reference, bool) {
|
||||
metadata, err := r.img.FileCatalog.Get(ref)
|
||||
if err != nil || metadata.Type != stereoscopeFile.TypeHardLink {
|
||||
return ref, false
|
||||
}
|
||||
resolved, err := r.img.ResolveLinkByLayerSquash(ref, layerIdx)
|
||||
if err != nil || !resolved.HasReference() {
|
||||
return ref, false
|
||||
}
|
||||
return *resolved.Reference, true
|
||||
}
|
||||
|
||||
func (r *ContainerImageAllLayers) annotateLocation(l *file.Location) {
|
||||
if !r.markVisibility || l == nil {
|
||||
return
|
||||
@@ -282,26 +330,24 @@ func (r *ContainerImageAllLayers) annotateLocation(l *file.Location) {
|
||||
annotation := file.VisibleAnnotation
|
||||
|
||||
// if we find a location for a path that matches the query (e.g. **/node_modules) but is not present in the squashed tree, skip it
|
||||
ref, err := r.img.SquashedSearchContext.SearchByPath(l.RealPath, filetree.DoNotFollowDeadBasenameLinks)
|
||||
if err != nil || !ref.HasReference() {
|
||||
annotation = file.HiddenAnnotation
|
||||
} else if ref.ID() != givenRef.ID() {
|
||||
// we may have the path in the squashed tree, but this must not be in the same layer
|
||||
if !r.pathResolvesToRef(l.RealPath, givenRef) {
|
||||
annotation = file.HiddenAnnotation
|
||||
}
|
||||
|
||||
// not only should the real path to the file exist, but the way we took to get there should also exist
|
||||
// (e.g. if we are looking for /etc/passwd, but the real path is /etc/passwd -> /etc/passwd-1, then we should
|
||||
// make certain that /etc/passwd-1 exists)
|
||||
if annotation == file.VisibleAnnotation && l.AccessPath != "" {
|
||||
ref, err := r.img.SquashedSearchContext.SearchByPath(l.AccessPath, filetree.DoNotFollowDeadBasenameLinks)
|
||||
if err != nil || !ref.HasReference() {
|
||||
annotation = file.HiddenAnnotation
|
||||
} else if ref.ID() != givenRef.ID() {
|
||||
// we may have the path in the squashed tree, but this must not be in the same layer
|
||||
annotation = file.HiddenAnnotation
|
||||
}
|
||||
if annotation == file.VisibleAnnotation && l.AccessPath != "" && !r.pathResolvesToRef(l.AccessPath, givenRef) {
|
||||
annotation = file.HiddenAnnotation
|
||||
}
|
||||
|
||||
l.Annotations[file.VisibleAnnotationKey] = annotation
|
||||
}
|
||||
|
||||
// pathResolvesToRef reports whether the given path in the squashed tree resolves to the given reference.
|
||||
// SearchByPath always follows basename links, so a hardlink surfaced at its own path (whose reference is its
|
||||
// target) still resolves to the target here and is correctly considered visible.
|
||||
func (r *ContainerImageAllLayers) pathResolvesToRef(path string, target stereoscopeFile.Reference) bool {
|
||||
ref, err := r.img.SquashedSearchContext.SearchByPath(path, filetree.DoNotFollowDeadBasenameLinks)
|
||||
return err == nil && ref.HasReference() && ref.ID() == target.ID()
|
||||
}
|
||||
|
||||
@@ -40,6 +40,17 @@ func (r *ContainerImageSquash) FilesByPath(paths ...string) ([]file.Location, er
|
||||
uniqueLocations := make([]file.Location, 0)
|
||||
|
||||
for _, path := range paths {
|
||||
// if the requested path is itself a hardlink, surface it at its own path (bound to its target's content)
|
||||
// rather than collapsing it onto the target's path, so that image results are in parity with directory
|
||||
// results (which cannot tell a hardlink from a regular file).
|
||||
if ownRef, targetRef, ok := r.hardLinkAtPath(path); ok {
|
||||
if !uniqueFileIDs.Contains(ownRef) {
|
||||
uniqueFileIDs.Add(ownRef)
|
||||
uniqueLocations = append(uniqueLocations, file.NewVirtualLocationFromImage(string(ownRef.RealPath), path, targetRef, r.img))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ref, err := r.img.SquashedSearchContext.SearchByPath(path, filetree.FollowBasenameLinks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -82,7 +93,7 @@ func (r *ContainerImageSquash) FilesByPath(paths ...string) ([]file.Location, er
|
||||
//
|
||||
//nolint:gocognit
|
||||
func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location, error) {
|
||||
uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
|
||||
uniqueCoordinates := file.NewCoordinateSet()
|
||||
uniqueLocations := make([]file.Location, 0)
|
||||
|
||||
for _, pattern := range patterns {
|
||||
@@ -116,10 +127,14 @@ func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location,
|
||||
return nil, fmt.Errorf("failed to find files by path (result=%+v): %w", result, err)
|
||||
}
|
||||
for _, resolvedLocation := range resolvedLocations {
|
||||
if uniqueFileIDs.Contains(resolvedLocation.Reference()) {
|
||||
// dedup on the surfaced coordinate rather than the underlying reference: distinct hardlinks share a
|
||||
// single target reference but each has its own real path, so a reference-based dedup would collapse
|
||||
// them back onto one entry (the exact behavior this parity fix removes). symlink resolutions keep
|
||||
// their target's real path, so they still collapse as before.
|
||||
if uniqueCoordinates.Contains(resolvedLocation.Coordinates) {
|
||||
continue
|
||||
}
|
||||
uniqueFileIDs.Add(resolvedLocation.Reference())
|
||||
uniqueCoordinates.Add(resolvedLocation.Coordinates)
|
||||
uniqueLocations = append(uniqueLocations, resolvedLocation)
|
||||
}
|
||||
}
|
||||
@@ -179,10 +194,16 @@ func (r *ContainerImageSquash) AllLocations(ctx context.Context) <-chan file.Loc
|
||||
go func() {
|
||||
defer close(results)
|
||||
for _, ref := range r.img.SquashedTree().AllFiles(stereoscopeFile.AllTypes()...) {
|
||||
loc := file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
|
||||
// surface a hardlink as the underlying type it points to (at its own path) so image results match
|
||||
// directory results, which cannot distinguish a hardlink from a regular file.
|
||||
if targetRef, ok := r.resolveHardLinkTarget(ref); ok {
|
||||
loc = file.NewVirtualLocationFromImage(string(ref.RealPath), string(ref.RealPath), targetRef, r.img)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img):
|
||||
case results <- loc:
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -190,6 +211,37 @@ func (r *ContainerImageSquash) AllLocations(ctx context.Context) <-chan file.Loc
|
||||
return results
|
||||
}
|
||||
|
||||
// hardLinkAtPath returns the hardlink's own reference and its resolved target reference when the basename of path is a
|
||||
// hardlink. ok is false when path does not exist or is not a hardlink. The lookup does not follow the basename link so
|
||||
// that the hardlink's own path is preserved. This adds a tree walk per FilesByPath path; if it shows up in
|
||||
// profiles, fold the hardlink check into the existing SearchByPath resolution.
|
||||
func (r *ContainerImageSquash) hardLinkAtPath(path string) (stereoscopeFile.Reference, stereoscopeFile.Reference, bool) {
|
||||
var own stereoscopeFile.Reference
|
||||
exists, resolution, err := r.img.SquashedTree().File(stereoscopeFile.Path(path))
|
||||
if err != nil || !exists || !resolution.HasReference() {
|
||||
return own, own, false
|
||||
}
|
||||
target, ok := r.resolveHardLinkTarget(*resolution.Reference)
|
||||
if !ok {
|
||||
return own, own, false
|
||||
}
|
||||
return *resolution.Reference, target, true
|
||||
}
|
||||
|
||||
// resolveHardLinkTarget returns the reference of a hardlink's underlying target when ref is a hardlink; ok is false
|
||||
// otherwise. No resolution is performed for non-hardlinks (symlinks keep their existing resolution semantics).
|
||||
func (r *ContainerImageSquash) resolveHardLinkTarget(ref stereoscopeFile.Reference) (stereoscopeFile.Reference, bool) {
|
||||
metadata, err := r.img.FileCatalog.Get(ref)
|
||||
if err != nil || metadata.Type != stereoscopeFile.TypeHardLink {
|
||||
return ref, false
|
||||
}
|
||||
resolved, err := r.img.ResolveLinkByImageSquash(ref)
|
||||
if err != nil || !resolved.HasReference() {
|
||||
return ref, false
|
||||
}
|
||||
return *resolved.Reference, true
|
||||
}
|
||||
|
||||
func (r *ContainerImageSquash) FilesByMIMEType(types ...string) ([]file.Location, error) {
|
||||
refs, err := r.img.SquashedSearchContext.SearchByMIMEType(types...)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
@@ -574,3 +575,87 @@ func TestSquashResolver_AllLocations(t *testing.T) {
|
||||
|
||||
assert.ElementsMatchf(t, expected, pathsList, "expected all paths to be indexed, but found different paths: \n%s", cmp.Diff(expected, paths.List()))
|
||||
}
|
||||
|
||||
// a hardlink should be surfaced at its own path as the underlying type it points to (a regular file with the target's
|
||||
// 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) {
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", "image-hardlinks")
|
||||
|
||||
resolvers := map[string]file.Resolver{}
|
||||
squash, err := NewFromContainerImageSquash(img)
|
||||
require.NoError(t, err)
|
||||
resolvers["squashed"] = squash
|
||||
|
||||
allLayers, err := NewFromContainerImageAllLayers(img)
|
||||
require.NoError(t, err)
|
||||
resolvers["all-layers"] = allLayers
|
||||
|
||||
const wantContents = "hardlinked contents\n"
|
||||
|
||||
// asserts a location surfaces the hardlink at its own path as a regular file with the target's content, and is
|
||||
// not marked hidden (all-layers annotates visibility; squash leaves the annotation unset, which must not read as
|
||||
// hidden either).
|
||||
assertHardlink := func(t *testing.T, resolver file.Resolver, wantPath string, loc file.Location) {
|
||||
t.Helper()
|
||||
assert.Equal(t, wantPath, loc.RealPath, "expected the hardlink's own path, not the target's")
|
||||
assert.NotEqual(t, file.HiddenAnnotation, loc.Annotations[file.VisibleAnnotationKey], "path=%s should not be hidden", wantPath)
|
||||
|
||||
meta, err := resolver.FileMetadataByLocation(loc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, stereoscopeFile.TypeRegular, meta.Type, "path=%s", wantPath)
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
require.NoError(t, err)
|
||||
actual, err := io.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, wantContents, string(actual), "path=%s", wantPath)
|
||||
}
|
||||
|
||||
for name, resolver := range resolvers {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("FilesByPath surfaces a hardlink at its own path with the target's content", func(t *testing.T) {
|
||||
for _, path := range []string{"/hardlink-a", "/hardlink-b", "/file.txt"} {
|
||||
locs, err := resolver.FilesByPath(path)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, locs, 1, "path=%s", path)
|
||||
assertHardlink(t, resolver, path, locs[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FilesByGlob surfaces every hardlink in a matched set, not just one", func(t *testing.T) {
|
||||
// the whole point of #5019: a glob that matches multiple hardlinks to the same target must return
|
||||
// all of them (each at its own path), not collapse them onto a single entry.
|
||||
locs, err := resolver.FilesByGlob("**/hardlink-*")
|
||||
require.NoError(t, err)
|
||||
|
||||
byPath := map[string]file.Location{}
|
||||
for _, loc := range locs {
|
||||
_, dup := byPath[loc.RealPath]
|
||||
assert.Falsef(t, dup, "path %s emitted more than once", loc.RealPath)
|
||||
byPath[loc.RealPath] = loc
|
||||
}
|
||||
for _, path := range []string{"/hardlink-a", "/hardlink-b"} {
|
||||
loc, ok := byPath[path]
|
||||
require.Truef(t, ok, "expected glob to surface %s", path)
|
||||
assertHardlink(t, resolver, path, loc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("AllLocations includes every hardlink path as a regular file exactly once", func(t *testing.T) {
|
||||
counts := map[string]int{}
|
||||
locsByPath := map[string]file.Location{}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
for loc := range resolver.AllLocations(ctx) {
|
||||
counts[loc.RealPath]++
|
||||
locsByPath[loc.RealPath] = loc
|
||||
}
|
||||
for _, path := range []string{"/file.txt", "/hardlink-a", "/hardlink-b"} {
|
||||
require.Equalf(t, 1, counts[path], "expected %s to be reported exactly once by AllLocations", path)
|
||||
assertHardlink(t, resolver, path, locsByPath[path])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# LAYER 0:
|
||||
FROM busybox:1.34.0@sha256:e8e5cca392e3cf056fcdb3093e7ac2bf83fcf28b3bcf5818fe8ae71cf360c231
|
||||
|
||||
# LAYER 1: a regular file with two hardlinks to it, all created in the same layer so the layer tar contains one
|
||||
# regular file entry and two hardlink (tar TypeLink) entries pointing at it.
|
||||
RUN echo "hardlinked contents" > /file.txt && ln /file.txt /hardlink-a && ln /file.txt /hardlink-b
|
||||
|
||||
# squash representation (of the files added here; busybox base also contributes hardlinked applets under /bin)
|
||||
# .
|
||||
# ├── file.txt
|
||||
# ├── hardlink-a (hardlink -> file.txt)
|
||||
# └── hardlink-b (hardlink -> file.txt)
|
||||
@@ -32,12 +32,15 @@ type parsedData struct {
|
||||
pkg.ApkDBEntry
|
||||
}
|
||||
|
||||
const maxApkDBFieldSize = 10 * 1024 * 1024
|
||||
|
||||
// parseApkDB parses packages from a given APK "installed" flat-file DB. For more
|
||||
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
|
||||
//
|
||||
//nolint:funlen
|
||||
func parseApkDB(ctx context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Buffer(nil, maxApkDBFieldSize)
|
||||
|
||||
var errs error
|
||||
var apks []parsedData
|
||||
|
||||
@@ -719,6 +719,25 @@ func Test_processChecksum(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseApkDBAllowsLargeFieldValues(t *testing.T) {
|
||||
contents := strings.Join([]string{
|
||||
"P:large-description",
|
||||
"V:1.0-r0",
|
||||
"A:x86_64",
|
||||
"S:1",
|
||||
"I:1",
|
||||
"T:" + strings.Repeat("a", 70*1024),
|
||||
"",
|
||||
}, "\n")
|
||||
reader := file.NewLocationReadCloser(file.NewLocation("large-installed-db"), io.NopCloser(strings.NewReader(contents)))
|
||||
|
||||
pkgs, _, err := parseApkDB(context.Background(), nil, new(generic.Environment), reader)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, pkgs, 1)
|
||||
assert.Equal(t, "large-description", pkgs[0].Name)
|
||||
}
|
||||
|
||||
func Test_parseApkDB_expectedPkgNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
fixture string
|
||||
|
||||
@@ -433,6 +433,16 @@ catalogers:
|
||||
cpes:
|
||||
- cpe:2.3:a:deno:deno:*:*:*:*:*:*:*:*
|
||||
type: BinaryPkg
|
||||
- method: glob
|
||||
criteria:
|
||||
- '**/bun'
|
||||
packages:
|
||||
- class: bun-binary
|
||||
name: bun
|
||||
purl: pkg:generic/bun
|
||||
cpes:
|
||||
- cpe:2.3:a:oven-sh:bun:*:*:*:*:*:*:*:*
|
||||
type: BinaryPkg
|
||||
- method: glob
|
||||
criteria:
|
||||
- '**/ghc*'
|
||||
|
||||
@@ -1499,6 +1499,28 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("deno-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "deno/1.11.3/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "deno",
|
||||
Version: "1.11.3",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/deno@1.11.3",
|
||||
Locations: locations("deno"),
|
||||
Metadata: metadata("deno-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "deno/1.11.4/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "deno",
|
||||
Version: "1.11.4",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/deno@1.11.4",
|
||||
Locations: locations("deno"),
|
||||
Metadata: metadata("deno-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "deno/1.16.4/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -1565,6 +1587,61 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("deno-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "deno/2.9.2/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "deno",
|
||||
Version: "2.9.2",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/deno@2.9.2",
|
||||
Locations: locations("deno"),
|
||||
Metadata: metadata("deno-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "bun/0.5.9/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "bun",
|
||||
Version: "0.5.9",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/bun@0.5.9",
|
||||
Locations: locations("bun"),
|
||||
Metadata: metadata("bun-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "bun/1.0.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "bun",
|
||||
Version: "1.0.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/bun@1.0.0",
|
||||
Locations: locations("bun"),
|
||||
Metadata: metadata("bun-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "bun/1.1.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "bun",
|
||||
Version: "1.1.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/bun@1.1.0",
|
||||
Locations: locations("bun"),
|
||||
Metadata: metadata("bun-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "bun/1.3.5/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "bun",
|
||||
Version: "1.3.5",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/bun@1.3.5",
|
||||
Locations: locations("bun"),
|
||||
Metadata: metadata("bun-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "haskell-ghc/7.10.3/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -2992,8 +3069,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
|
||||
Name: "busybox",
|
||||
Version: "1.35.0",
|
||||
PURL: "pkg:generic/busybox@1.35.0",
|
||||
Locations: locations("/bin/["),
|
||||
Metadata: metadata("busybox-binary", "/bin/[", "/bin/busybox"),
|
||||
Locations: locations("/bin/busybox"),
|
||||
Metadata: metadata("busybox-binary", "/bin/busybox"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -588,11 +588,20 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
Class: "deno-binary",
|
||||
FileGlob: "**/deno",
|
||||
EvidenceMatcher: binutils.MatchAny(
|
||||
m.FileContentsVersionMatcher(
|
||||
// Deno/2.6.3Deno/
|
||||
// Deno/1.41.0cli/
|
||||
`Deno/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)(Deno/|cli/)`,
|
||||
),
|
||||
m.FileContentsVersionMatcher(
|
||||
// Deno/2.6.3
|
||||
// Deno/1.41.0
|
||||
`Deno/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||
),
|
||||
m.FileContentsVersionMatcher(
|
||||
// cli/tools/standalone.rsdeno-canary/f4bed1081456089559c82441a13c4fb700840cac1.11.3dlwindows
|
||||
`deno-canary/[0-9a-z]{40}(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||
),
|
||||
m.FileContentsVersionMatcher(
|
||||
// deno::tools::standalonedeno-65db94feba9d4d51a09b74629f566dbc90484fbarelease/v1.29.4windows
|
||||
// cli/tools/standalone.rsdeno-74064c9d8c222b33b2a552ea0af1054f57002a96release/v1.28.3windows
|
||||
@@ -611,6 +620,20 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
PURL: mustPURL("pkg:generic/deno@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:deno:deno:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "bun-binary",
|
||||
FileGlob: "**/bun",
|
||||
EvidenceMatcher: binutils.MatchAny(
|
||||
// bun 1.1.0
|
||||
// Bun v1.0.0
|
||||
m.FileContentsVersionMatcher(`(?m)[Bb]un v?(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
// bun-1.1.0
|
||||
m.FileContentsVersionMatcher(`(?m)bun-(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
),
|
||||
Package: "bun",
|
||||
PURL: mustPURL("pkg:generic/bun@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:oven-sh:bun:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "haskell-ghc-binary",
|
||||
FileGlob: "**/ghc*",
|
||||
@@ -875,7 +898,13 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
{
|
||||
Class: "gzip-binary",
|
||||
FileGlob: "**/gzip",
|
||||
// GNU gzip assembles its version banner at runtime from argv[0], so the version is stored as a
|
||||
// bare NUL-delimited token with nothing to anchor against. Require a gzip-specific string
|
||||
// elsewhere in the file before trusting that token, otherwise any binary reachable by a path
|
||||
// named "gzip" (such as the busybox multicall binary, which provides a gzip applet by symlink)
|
||||
// is reported as GNU gzip with an arbitrary version.
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`not in gzip format|bug-gzip@gnu\.org|GZIP environment variable`,
|
||||
`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "gzip",
|
||||
|
||||
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
name: bun
|
||||
offset: 1000000
|
||||
length: 100
|
||||
snippetSha256: 0000000000000000000000000000000000000000000000000000000000000001
|
||||
fileSha256: 0000000000000000000000000000000000000000000000000000000000000001
|
||||
|
||||
### byte snippet to follow ###
|
||||
]
|
||||
|
||||
Bun v0.5.9
|
||||
bun-0.5.9
|
||||
JavaScript runtime for developing, testing, and bundling
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
name: bun
|
||||
offset: 1000000
|
||||
length: 100
|
||||
snippetSha256: 0000000000000000000000000000000000000000000000000000000000000002
|
||||
fileSha256: 0000000000000000000000000000000000000000000000000000000000000002
|
||||
|
||||
### byte snippet to follow ###
|
||||
]
|
||||
|
||||
Bun v1.0.0
|
||||
bun-1.0.0
|
||||
JavaScript runtime for developing, testing, and bundling
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
name: bun
|
||||
offset: 1000000
|
||||
length: 100
|
||||
snippetSha256: 0000000000000000000000000000000000000000000000000000000000000003
|
||||
fileSha256: 0000000000000000000000000000000000000000000000000000000000000003
|
||||
|
||||
### byte snippet to follow ###
|
||||
]
|
||||
|
||||
Bun v1.1.0
|
||||
bun-1.1.0
|
||||
JavaScript runtime for developing, testing, and bundling
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
name: bun
|
||||
offset: 1000000
|
||||
length: 100
|
||||
snippetSha256: 0000000000000000000000000000000000000000000000000000000000000004
|
||||
fileSha256: 0000000000000000000000000000000000000000000000000000000000000004
|
||||
|
||||
### byte snippet to follow ###
|
||||
]
|
||||
|
||||
Bun v1.3.5
|
||||
bun-1.3.5
|
||||
JavaScript runtime for developing, testing, and bundling
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
name: deno
|
||||
offset: 4221667
|
||||
length: 120
|
||||
snippetSha256: 89509d15b73fd2d901b20ebfe82c67fd27f70e7b01dcbf376e7e80e6c80f97d9
|
||||
fileSha256: 530a95c93ddfa41bd4d72d665e677d5af9a4275f1520e638305e60af5083a6d6
|
||||
|
||||
### byte snippet to follow ###
|
||||
g ctrl+d or close()
|
||||
Error: Uncaught cli/tools/standalone.rsdeno-canary/f4bed1081456089559c82441a13c4fb700840cac1.11.3dlw
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
name: deno
|
||||
offset: 4225763
|
||||
length: 120
|
||||
snippetSha256: b47e8cf36bdf93e4bc63a2d7bd0a5d759975f12751a66908661777eba7e90025
|
||||
fileSha256: 1a55c23bb0b666d1b26d9d16f3a9d27210b6b13421031f7f18ff18abfcb8664f
|
||||
|
||||
### byte snippet to follow ###
|
||||
g ctrl+d or close()
|
||||
Error: Uncaught cli/tools/standalone.rsdeno-canary/ed16f4332263e81bf00a39ebc2200291b1fcbb8e1.11.4dlw
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+24
@@ -140,6 +140,22 @@ from-images:
|
||||
paths:
|
||||
- /usr/bin/deno
|
||||
|
||||
- name: deno
|
||||
version: 1.11.3
|
||||
images:
|
||||
- ref: denoland/deno:1.11.3@sha256:340d071dc71c8403c75713694db04f59b8cb7f6c7244798cd7447d55f2750596
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/bin/deno
|
||||
|
||||
- name: deno
|
||||
version: 1.11.4
|
||||
images:
|
||||
- ref: denoland/deno:1.11.4@sha256:8e9a449a50b71c779ad1c10ec79055b20a331e78a3852818fac47bc3e85b49f4
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/bin/deno
|
||||
|
||||
- name: deno
|
||||
version: 1.16.4
|
||||
images:
|
||||
@@ -188,6 +204,14 @@ from-images:
|
||||
paths:
|
||||
- /usr/bin/deno
|
||||
|
||||
- name: deno
|
||||
version: 2.9.2
|
||||
images:
|
||||
- ref: denoland/deno:ubuntu-2.9.2@sha256:0d27941f871700ebd95953c36fb1a817135c20f5afe3a645935672dfab72b2e9
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/bin/deno
|
||||
|
||||
# haskell:7.10.3 detail information is missing.
|
||||
- name: haskell-ghc
|
||||
version: 7.10.3
|
||||
|
||||
@@ -156,7 +156,7 @@ func parseErlangString(data []byte, i *int) (erlangNode, error) {
|
||||
}
|
||||
if c == '\\' {
|
||||
*i++
|
||||
if len(data) >= *i {
|
||||
if *i >= len(data) {
|
||||
return node(nil), fmt.Errorf("invalid escape without closed string at %d", *i)
|
||||
}
|
||||
c = data[*i]
|
||||
|
||||
@@ -96,6 +96,11 @@ func Test_parseErlang(t *testing.T) {
|
||||
{ foo, bar }
|
||||
]}`,
|
||||
},
|
||||
{
|
||||
name: "string with an escaped quote",
|
||||
content: `
|
||||
{escaped, ["a\"b"]}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -112,3 +117,14 @@ func Test_parseErlang(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseErlangString_escapedByte(t *testing.T) {
|
||||
// a backslash escape mid-string used to always error out, since the bounds
|
||||
// check at the escape site was inverted (len(data) >= *i is true for
|
||||
// almost every position, not just an out-of-range one).
|
||||
data := []byte(`"a\"b"`)
|
||||
i := 0
|
||||
got, err := parseErlangString(data, &i)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, `a"b`, got.String())
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
|
||||
|
||||
// only use go.mod packages NOT found in source analysis
|
||||
goModPackages := c.createGoModPackages(ctx, resolver, modFile, sourceModules, reader, digests)
|
||||
c.applyReplaceDirectives(ctx, resolver, modFile, goModPackages, reader, digests)
|
||||
c.applyReplaceDirectives(ctx, resolver, modFile, sourceModules, goModPackages, reader, digests)
|
||||
c.applyExcludeDirectives(modFile, goModPackages)
|
||||
|
||||
pkgs = c.assembleResults(catalogedModules, goModPackages)
|
||||
@@ -365,8 +365,11 @@ func (c *goModCataloger) createGoModPackages(ctx context.Context, resolver file.
|
||||
}
|
||||
|
||||
// applyReplaceDirectives processes replace directives from go.mod
|
||||
func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) {
|
||||
func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, sourceModules map[string]*packages.Module, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) {
|
||||
for _, m := range modFile.Replace {
|
||||
if sourceModules != nil && sourceModules[m.Old.Path] != nil {
|
||||
continue
|
||||
}
|
||||
lics := c.licenseResolver.getLicenses(ctx, resolver, m.New.Path, m.New.Version)
|
||||
var finalPath string
|
||||
if !strings.HasPrefix(m.New.Path, ".") && !strings.HasPrefix(m.New.Path, "/") {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
stereofile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
)
|
||||
|
||||
func Test_parseGoSource_replacedModulesAreNotDuplicated(t *testing.T) {
|
||||
fixture := filepath.Join("testdata", "go-source-replacements")
|
||||
s, err := directorysource.NewFromPath(fixture)
|
||||
require.NoError(t, err)
|
||||
resolver, err := s.FileResolver(source.AllLayersScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
modPath, err := filepath.Abs(filepath.Join(fixture, "go.mod"))
|
||||
require.NoError(t, err)
|
||||
contents, err := os.Open(modPath)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, contents.Close()) })
|
||||
|
||||
reader := file.LocationReadCloser{
|
||||
Location: file.NewVirtualLocationFromDirectory("go.mod", "go.mod", *stereofile.NewFileReference(stereofile.Path(modPath))),
|
||||
ReadCloser: contents,
|
||||
}
|
||||
config := DefaultCatalogerConfig().WithUsePackagesLib(true).WithSearchRemoteLicenses(false)
|
||||
pkgs, _, err := newGoModCataloger(config).parseGoModFile(pkgtest.Context(t), resolver, nil, reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
versions := make(map[string][]string)
|
||||
for _, p := range pkgs {
|
||||
versions[p.Name] = append(versions[p.Name], p.Version)
|
||||
}
|
||||
require.Equal(t, []string{"v1.5.2"}, versions["rsc.io/quote"])
|
||||
require.Equal(t, []string{"v1.3.1"}, versions["rsc.io/sampler"])
|
||||
}
|
||||
@@ -16,6 +16,10 @@ import (
|
||||
// mainPackage is the import path the linker assigns to the binary's main package.
|
||||
const mainPackage = "main"
|
||||
|
||||
// vendorPrefix is the import-path prefix carried by vendored packages (e.g. from `go mod vendor`, or the
|
||||
// standard library's own vendored dependencies such as "vendor/golang.org/x/net/http2").
|
||||
const vendorPrefix = "vendor/"
|
||||
|
||||
// binarySymbol represents a single function symbol extracted from a go binary's pclntab.
|
||||
type binarySymbol struct {
|
||||
// packagePath is the import path of the package that owns the symbol (e.g. "github.com/foo/bar/internal/baz")
|
||||
@@ -52,10 +56,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
||||
continue
|
||||
}
|
||||
seen[fn.Name] = struct{}{}
|
||||
syms = append(syms, binarySymbol{
|
||||
packagePath: fn.PackageName(),
|
||||
name: fn.Name,
|
||||
})
|
||||
syms = append(syms, makeBinarySymbol(fn.Name, fn.PackageName()))
|
||||
}
|
||||
|
||||
// debug/gosym only exposes top-level functions; functions that the compiler inlined into their
|
||||
@@ -71,7 +72,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
||||
continue
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
syms = append(syms, binarySymbol{packagePath: pkgPath, name: name})
|
||||
syms = append(syms, makeBinarySymbol(name, pkgPath))
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
@@ -94,6 +95,55 @@ func packagePathFromSymbolName(name string) string {
|
||||
return name[:slash+1+dot]
|
||||
}
|
||||
|
||||
// makeBinarySymbol builds a binarySymbol, unescaping the %xx sequences the go linker introduces in the
|
||||
// import-path portion of a symbol name (see unescapePackagePath). The local-symbol suffix (method and
|
||||
// function names) is left untouched, so only the path prefix shared by packagePath and name is rewritten.
|
||||
func makeBinarySymbol(name, pkgPath string) binarySymbol {
|
||||
unescaped := unescapePackagePath(pkgPath)
|
||||
if unescaped != pkgPath && strings.HasPrefix(name, pkgPath) {
|
||||
name = unescaped + name[len(pkgPath):]
|
||||
}
|
||||
return binarySymbol{packagePath: unescaped, name: name}
|
||||
}
|
||||
|
||||
// unescapePackagePath reverses the escaping cmd/internal/objabi.PathToPrefix applies to import paths in
|
||||
// symbol names: bytes like '.' (at or after the final '/'), '%', '"', control bytes, and high bytes are
|
||||
// written as lowercase "%xx". For example "gopkg.in/yaml.v2" is stored as "gopkg.in/yaml%2ev2", so this
|
||||
// restores it before matching against the (unescaped) module paths from build info. A lone or malformed
|
||||
// '%' sequence is left as-is.
|
||||
func unescapePackagePath(path string) string {
|
||||
if !strings.Contains(path, "%") {
|
||||
return path
|
||||
}
|
||||
var b strings.Builder
|
||||
b.Grow(len(path))
|
||||
for i := 0; i < len(path); i++ {
|
||||
if path[i] == '%' && i+2 < len(path) {
|
||||
if hi, ok1 := unhex(path[i+1]); ok1 {
|
||||
if lo, ok2 := unhex(path[i+2]); ok2 {
|
||||
b.WriteByte(hi<<4 | lo)
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
b.WriteByte(path[i])
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func unhex(c byte) (byte, bool) {
|
||||
switch {
|
||||
case c >= '0' && c <= '9':
|
||||
return c - '0', true
|
||||
case c >= 'a' && c <= 'f':
|
||||
return c - 'a' + 10, true
|
||||
case c >= 'A' && c <= 'F':
|
||||
return c - 'A' + 10, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// nameWithoutTypeArgs strips the type-argument portion from an instantiated generic symbol name, e.g.
|
||||
// "foo/bar.Do[net/url.Values]" -> "foo/bar.Do". The slashes and dots inside the brackets would otherwise
|
||||
// corrupt package-path derivation (yielding "foo/bar.Do[net" for the example above). Mirrors
|
||||
@@ -273,8 +323,13 @@ func readPclntab(r io.ReaderAt) (pclntab []byte, textStart uint64, err error) {
|
||||
// "(*T).M"). Symbols from the "main" package are attributed to the main module and keyed by the "main"
|
||||
// import path the linker assigns. Standard-library symbols (which belong to no module) are collected
|
||||
// separately and returned as the second value, grouped by import path, so they can be attached to the
|
||||
// synthetic "stdlib" package. Compiler/runtime-internal symbols that are neither module-owned nor a
|
||||
// recognizable stdlib import path are dropped.
|
||||
// synthetic "stdlib" package. Vendored packages carry a "vendor/" import-path prefix: such symbols match
|
||||
// both modules whose own path carries the prefix and modules without it (matched with the prefix trimmed),
|
||||
// and the prefix is retained in the group key only when the owning module itself is named "vendor/...".
|
||||
// Module-less vendored packages (the stdlib's own vendored dependencies, e.g.
|
||||
// "vendor/golang.org/x/net/http2") are dropped: stdlib vulnerabilities seem to be reported against the public
|
||||
// packages (e.g. "crypto/x509"), not the vendored internal copies. Compiler/runtime-internal symbols that
|
||||
// are neither module-owned nor a recognizable stdlib import path are likewise dropped.
|
||||
func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Module) (byModule map[string]map[string][]string, stdlib map[string][]string) {
|
||||
if len(symbols) == 0 {
|
||||
return nil, nil
|
||||
@@ -302,16 +357,17 @@ func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Mod
|
||||
attrPath = main.Path
|
||||
}
|
||||
|
||||
var best string
|
||||
for _, modPath := range modulePaths {
|
||||
if len(modPath) > len(best) && (attrPath == modPath || strings.HasPrefix(attrPath, modPath+"/")) {
|
||||
best = modPath
|
||||
}
|
||||
best := findBestMatch(modulePaths, attrPath)
|
||||
|
||||
// the vendor/ prefix is only retained when the owning module itself is named "vendor/...";
|
||||
// in all other cases (non-vendored modules and vendored stdlib) the recorded import path is trimmed
|
||||
if !strings.HasPrefix(best, vendorPrefix) {
|
||||
importPath = strings.TrimPrefix(importPath, vendorPrefix)
|
||||
}
|
||||
|
||||
local := localSymbolName(sym.name, importPath)
|
||||
if best == "" {
|
||||
if importPath != mainPackage && isStandardImportPath(importPath) {
|
||||
if importPath != mainPackage && isStandardImportPath(importPath) { // drop stdlib vendored packages
|
||||
stdlib[importPath] = append(stdlib[importPath], local)
|
||||
}
|
||||
continue
|
||||
@@ -333,11 +389,36 @@ func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Mod
|
||||
return results, stdlib
|
||||
}
|
||||
|
||||
// findBestMatch returns the module path that owns the given package path taking into account vendor/ prefixes: a vendor/ import path
|
||||
// will take precedence and continue to match, non-vendored imports will match against their vendored equivalent
|
||||
func findBestMatch(modulePaths []string, importPath string) string {
|
||||
trimmedPath, trimmed := strings.CutPrefix(importPath, vendorPrefix)
|
||||
candidatePaths := []string{importPath, trimmedPath}
|
||||
if !trimmed {
|
||||
candidatePaths = candidatePaths[:1]
|
||||
}
|
||||
|
||||
var best string
|
||||
for _, candidate := range candidatePaths {
|
||||
for _, modPath := range modulePaths {
|
||||
// the prefix must end at a path-segment boundary in candidate, so that e.g. the module
|
||||
// "github.com/foo/bar" matches the package "github.com/foo/bar/baz" but not "github.com/foo/barbaz"
|
||||
if len(modPath) > len(best) && strings.HasPrefix(candidate, modPath) && (candidate == modPath || candidate[len(modPath)] == '/') {
|
||||
best = modPath
|
||||
}
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
// localSymbolName strips the owning package's import path prefix from a fully qualified symbol name, e.g.
|
||||
// "github.com/foo/bar.(*T).M" with import path "github.com/foo/bar" becomes "(*T).M". The name is returned
|
||||
// unchanged when it does not carry the expected prefix.
|
||||
func localSymbolName(name, importPath string) string {
|
||||
if importPath != "" && strings.HasPrefix(name, importPath+".") {
|
||||
if !strings.HasPrefix(importPath, vendorPrefix) {
|
||||
name = strings.TrimPrefix(name, vendorPrefix)
|
||||
}
|
||||
if len(importPath) < len(name) && strings.HasPrefix(name, importPath) && name[len(importPath)] == '.' {
|
||||
return name[len(importPath)+1:]
|
||||
}
|
||||
return name
|
||||
|
||||
@@ -16,6 +16,8 @@ func Test_moduleSymbols(t *testing.T) {
|
||||
deps := []*debug.Module{
|
||||
{Path: "github.com/foo/bar"},
|
||||
{Path: "github.com/foo/bar/v2"},
|
||||
{Path: "vendor/github.com/vendored/mod"},
|
||||
{Path: "github.com/vendored/mod"},
|
||||
nil,
|
||||
}
|
||||
|
||||
@@ -74,6 +76,63 @@ func Test_moduleSymbols(t *testing.T) {
|
||||
"runtime": {"main"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vendored packages match non-vendored modules and are recorded under the canonical import path",
|
||||
symbols: []binarySymbol{
|
||||
{packagePath: "vendor/github.com/foo/bar", name: "vendor/github.com/foo/bar.Parse"},
|
||||
{packagePath: "vendor/github.com/foo/bar/internal/util", name: "vendor/github.com/foo/bar/internal/util.(*Helper).Do"},
|
||||
},
|
||||
expected: map[string]map[string][]string{
|
||||
"github.com/foo/bar": {
|
||||
"github.com/foo/bar": {"Parse"},
|
||||
"github.com/foo/bar/internal/util": {"(*Helper).Do"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stdlib-vendored packages are dropped",
|
||||
symbols: []binarySymbol{
|
||||
{packagePath: "vendor/golang.org/x/net/http2", name: "vendor/golang.org/x/net/http2.(*Framer).ReadFrame"},
|
||||
},
|
||||
expected: map[string]map[string][]string{},
|
||||
},
|
||||
{
|
||||
name: "modules whose own path carries the vendor/ prefix match exactly and win over the trimmed match",
|
||||
symbols: []binarySymbol{
|
||||
{packagePath: "vendor/github.com/vendored/mod", name: "vendor/github.com/vendored/mod.Run"},
|
||||
{packagePath: "github.com/vendored/mod", name: "github.com/vendored/mod.Run"},
|
||||
},
|
||||
expected: map[string]map[string][]string{
|
||||
"vendor/github.com/vendored/mod": {
|
||||
"vendor/github.com/vendored/mod": {"Run"},
|
||||
},
|
||||
"github.com/vendored/mod": {
|
||||
"github.com/vendored/mod": {"Run"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vendored and unvendored symbols for the same package are merged and deduplicated under the canonical import path",
|
||||
symbols: []binarySymbol{
|
||||
{packagePath: "github.com/foo/bar", name: "github.com/foo/bar.Parse"},
|
||||
{packagePath: "vendor/github.com/foo/bar", name: "vendor/github.com/foo/bar.Parse"},
|
||||
},
|
||||
expected: map[string]map[string][]string{
|
||||
"github.com/foo/bar": {
|
||||
"github.com/foo/bar": {"Parse"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// "github.com/foo/barbaz" shares a string prefix with the "github.com/foo/bar" module but not a
|
||||
// path-segment boundary, so it must not be attributed to it (and, having a dotted first element,
|
||||
// it is not a stdlib path either — it is dropped)
|
||||
name: "module paths only match the package path at a path-segment boundary",
|
||||
symbols: []binarySymbol{
|
||||
{packagePath: "github.com/foo/barbaz", name: "github.com/foo/barbaz.Parse"},
|
||||
},
|
||||
expected: map[string]map[string][]string{},
|
||||
},
|
||||
{
|
||||
name: "duplicate symbols are deduplicated",
|
||||
symbols: []binarySymbol{
|
||||
@@ -97,6 +156,58 @@ func Test_moduleSymbols(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_escapedImportPathAttribution(t *testing.T) {
|
||||
// the go linker escapes the '.' in the last path element, so "gopkg.in/yaml.v2" appears in the symbol
|
||||
// table as "gopkg.in/yaml%2ev2". attribution must still land these under the unescaped module path.
|
||||
mainModule := &debug.Module{Path: "github.com/someorg/somecli"}
|
||||
deps := []*debug.Module{{Path: "gopkg.in/yaml.v2"}}
|
||||
|
||||
symbols := []binarySymbol{
|
||||
makeBinarySymbol("gopkg.in/yaml%2ev2.(*decoder).alias", "gopkg.in/yaml%2ev2"),
|
||||
makeBinarySymbol("gopkg.in/yaml%2ev2.(*TypeError).Error", "gopkg.in/yaml%2ev2"),
|
||||
}
|
||||
|
||||
gotByModule, gotStdlib := moduleSymbols(symbols, mainModule, deps)
|
||||
assert.Nil(t, gotStdlib)
|
||||
assert.Equal(t, map[string]map[string][]string{
|
||||
"gopkg.in/yaml.v2": {
|
||||
"gopkg.in/yaml.v2": {"(*TypeError).Error", "(*decoder).alias"},
|
||||
},
|
||||
}, gotByModule)
|
||||
}
|
||||
|
||||
func Test_unescapePackagePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"gopkg.in/yaml%2ev2", "gopkg.in/yaml.v2"},
|
||||
{"foo%25bar", "foo%bar"},
|
||||
{"foo%22bar", "foo\"bar"},
|
||||
// no escapes: unchanged
|
||||
{"github.com/foo/bar", "github.com/foo/bar"},
|
||||
{"", ""},
|
||||
// malformed sequences are left as-is
|
||||
{"foo%2", "foo%2"},
|
||||
{"foo%zz", "foo%zz"},
|
||||
{"foo%", "foo%"},
|
||||
// uppercase hex is accepted too
|
||||
{"foo%2Ebar", "foo.bar"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, unescapePackagePath(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeBinarySymbol(t *testing.T) {
|
||||
// only the import-path prefix is unescaped; the local-symbol suffix is preserved verbatim
|
||||
sym := makeBinarySymbol("gopkg.in/yaml%2ev2.(*decoder).alias", "gopkg.in/yaml%2ev2")
|
||||
assert.Equal(t, "gopkg.in/yaml.v2", sym.packagePath)
|
||||
assert.Equal(t, "gopkg.in/yaml.v2.(*decoder).alias", sym.name)
|
||||
}
|
||||
|
||||
func Test_localSymbolName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -239,6 +350,9 @@ func Test_packagePathFromSymbolName(t *testing.T) {
|
||||
// module paths that begin with "go." are not compiler-generated
|
||||
{"go.uber.org/zap.(*Logger).Info", "go.uber.org/zap"},
|
||||
{"go.opentelemetry.io/otel.Tracer", "go.opentelemetry.io/otel"},
|
||||
// vendored packages retain their "vendor/" import-path prefix in symbol names
|
||||
{"vendor/golang.org/x/net/http2.(*Framer).ReadFrame", "vendor/golang.org/x/net/http2"},
|
||||
{"vendor/github.com/foo/bar.Parse", "vendor/github.com/foo/bar"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
module example.com/repro
|
||||
|
||||
go 1.23
|
||||
|
||||
require rsc.io/quote v1.5.1
|
||||
|
||||
require (
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
|
||||
rsc.io/sampler v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
rsc.io/quote => rsc.io/quote v1.5.2
|
||||
rsc.io/sampler => rsc.io/sampler v1.3.1
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
|
||||
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
|
||||
rsc.io/sampler v1.3.1 h1:F0c3J2nQCdk9ODsNhU3sElnvPIxM/xV1c/qZuAeZmac=
|
||||
rsc.io/sampler v1.3.1/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"rsc.io/quote"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(quote.Hello())
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
"chainguard.dev/apko": [
|
||||
"cpe:2.3:a:chainguard:apko:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/Azure/go-ntlmssp": [
|
||||
"cpe:2.3:a:microsoft:go-ntlmssp:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/ClickHouse/ch-go": [
|
||||
"cpe:2.3:a:clickhouse:ch:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
@@ -223,6 +226,9 @@
|
||||
"github.com/xddxdd/bird-lg-go": [
|
||||
"cpe:2.3:a:xddxdd:bird-lg-go:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/xuri/excelize/v2": [
|
||||
"cpe:2.3:a:excelize:excelize:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/yuin/goldmark": [
|
||||
"cpe:2.3:a:yuin:goldmark:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
@@ -2327,6 +2333,9 @@
|
||||
"@ais-ltd/strategyen": [
|
||||
"cpe:2.3:a:ais-ltd:strategyen:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@amoy/common": [
|
||||
"cpe:2.3:a:amoyjs:common:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@angular/core": [
|
||||
"cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:*",
|
||||
"cpe:2.3:a:angularjs:angularjs:*:*:*:*:*:*:*:*"
|
||||
@@ -2349,6 +2358,9 @@
|
||||
"@apollosproject/data-connector-rock": [
|
||||
"cpe:2.3:a:apollosapp:data-connector-rock:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@appium/base-driver": [
|
||||
"cpe:2.3:a:appium:appium\\/base-driver:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@appium/support": [
|
||||
"cpe:2.3:a:appium:appium\\/support:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2403,6 +2415,9 @@
|
||||
"@better-auth/oauth-provider": [
|
||||
"cpe:2.3:a:better-auth:better-auth\\/oauth-provider:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@better-auth/sso": [
|
||||
"cpe:2.3:a:better-auth:better-auth\\/sso:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@braintree/sanitize-url": [
|
||||
"cpe:2.3:a:paypal:braintree\\/sanitize-url:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2616,6 +2631,9 @@
|
||||
"@octokit/webhooks": [
|
||||
"cpe:2.3:a:octokit:webhooks:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@openclaw/feishu": [
|
||||
"cpe:2.3:a:openclaw:openclaw\\/feishu:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@openclaw/voice-call": [
|
||||
"cpe:2.3:a:openclaw:openclaw\\/voice-call:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -3086,6 +3104,9 @@
|
||||
"cli": [
|
||||
"cpe:2.3:a:cli_project:cli:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"client-certificate-auth": [
|
||||
"cpe:2.3:a:tgies:client-certificate-auth:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"closure-compiler-stream": [
|
||||
"cpe:2.3:a:closure-compiler-stream_project:closure-compiler-stream:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -4234,6 +4255,9 @@
|
||||
"json8-merge-patch": [
|
||||
"cpe:2.3:a:json8-merge-patch_project:json8-merge-patch:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"jsonpath": [
|
||||
"cpe:2.3:a:dchester:jsonpath:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"jsonpointer": [
|
||||
"cpe:2.3:a:janl:jsonpointer:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5752,6 +5776,9 @@
|
||||
"ssrf-agent": [
|
||||
"cpe:2.3:a:ssrf-agent_project:ssrf-agent:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"ssrfcheck": [
|
||||
"cpe:2.3:a:felippe-regazio:ssrf_check:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"ssri": [
|
||||
"cpe:2.3:a:ssri_project:ssri:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -6819,6 +6846,9 @@
|
||||
"pyanyapi": [
|
||||
"cpe:2.3:a:pyanyapi_project:pyanyapi:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"pyasn1": [
|
||||
"cpe:2.3:a:pyasn1:pyasn1:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"pybluemonday": [
|
||||
"cpe:2.3:a:python:pybluemonday:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6834,6 +6864,9 @@
|
||||
"pydantic-ai": [
|
||||
"cpe:2.3:a:pydantic:pydantic_ai:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"pydantic-settings": [
|
||||
"cpe:2.3:a:pydantic:pydantic-settings:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"pydash": [
|
||||
"cpe:2.3:a:derrickgilland:pydash:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
@@ -6978,6 +7011,9 @@
|
||||
"sqlparse": [
|
||||
"cpe:2.3:a:sqlparse_project:sqlparse:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"stanza": [
|
||||
"cpe:2.3:a:stanford:stanza:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"starlette": [
|
||||
"cpe:2.3:a:encode:starlette:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
@@ -7181,6 +7217,9 @@
|
||||
"encoded_id-rails": [
|
||||
"cpe:2.3:a:diaconou:encodedid\\:\\:rails:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"excon": [
|
||||
"cpe:2.3:a:excon_project:excon:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
"faye": [
|
||||
"cpe:2.3:a:faye_project:faye:*:*:*:*:*:ruby:*:*"
|
||||
],
|
||||
@@ -7588,6 +7627,9 @@
|
||||
"ammonia": [
|
||||
"cpe:2.3:a:ammonia_project:ammonia:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"anchor-lang": [
|
||||
"cpe:2.3:a:anchor-lang:anchor:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"anymap": [
|
||||
"cpe:2.3:a:anymap_project:anymap:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -7690,6 +7732,12 @@
|
||||
"bzip2": [
|
||||
"cpe:2.3:a:bzip2_project:bzip2:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"c2pa": [
|
||||
"cpe:2.3:a:adobe:c2pa:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"c2patool": [
|
||||
"cpe:2.3:a:adobe:c2patool:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"cache": [
|
||||
"cpe:2.3:a:cache_project:cache:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -7951,6 +7999,9 @@
|
||||
"one2html": [
|
||||
"cpe:2.3:a:msiemens:one2html:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"openssl": [
|
||||
"cpe:2.3:a:sfackler:openssl:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"outer_cgi": [
|
||||
"cpe:2.3:a:outer_cgi_project:outer_cgi:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -8530,6 +8581,9 @@
|
||||
"add-subtitle": [
|
||||
"cpe:2.3:a:viitorcloud:add_subtitle:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"add-svg-support-for-media-uploader-inventivo": [
|
||||
"cpe:2.3:a:inventivo:inventivo:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"add-to-any": [
|
||||
"cpe:2.3:a:addtoany:addtoany_share_buttons:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8720,6 +8774,9 @@
|
||||
"advanced-recent-posts": [
|
||||
"cpe:2.3:a:advanced_recent_posts_project:advanced_recent_posts:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"advanced-schedule-posts": [
|
||||
"cpe:2.3:a:hijiriworld:advanced_schedule_posts:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"advanced-sermons": [
|
||||
"cpe:2.3:a:wpcodeus:advanced_sermons:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8784,6 +8841,7 @@
|
||||
"cpe:2.3:a:ai_contact_us_form_project:ai_contact_us_form:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ai-engine": [
|
||||
"cpe:2.3:a:jordymeow:ai_engine:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:meowapps:ai_engine:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ai-image-alt-text-generator-for-wp": [
|
||||
@@ -8843,6 +8901,9 @@
|
||||
"album-and-image-gallery-plus-lightbox": [
|
||||
"cpe:2.3:a:essentialplugin:album_and_image_gallery_plus_lightbox:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"alemha-watermark": [
|
||||
"cpe:2.3:a:mnbaa:alemha_watermark:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"alfred-click-collect": [
|
||||
"cpe:2.3:a:alfred24_click_\\\u0026_collect_project:alfred24_click_\\\u0026_collect:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9175,7 +9236,8 @@
|
||||
"cpe:2.3:a:article_directory_project:article_directory:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"article-directory-redux": [
|
||||
"cpe:2.3:a:icontrolwp:article_directory_redux:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:icontrolwp:article_directory_redux:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:icontrolwp:icontrolwp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"article2pdf": [
|
||||
"cpe:2.3:a:article2pdf_project:article2pdf:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -9469,6 +9531,9 @@
|
||||
"barcode-scanner-lite-pos-to-manage-products-inventory-and-orders": [
|
||||
"cpe:2.3:a:ukrsolution:barcode_scanner_and_inventory_manager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"base64-encoderdecoder": [
|
||||
"cpe:2.3:a:mranderson:base64_encoder\\/decoder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"basic-interactive-world-map": [
|
||||
"cpe:2.3:a:wpmapplugins:basic_interactive_world_map:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9923,6 +9988,9 @@
|
||||
"bsuite": [
|
||||
"cpe:2.3:a:bsuite_project:bsuite:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"bu-section-editing": [
|
||||
"cpe:2.3:a:bu:bu_section_editing:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"bubble-menu": [
|
||||
"cpe:2.3:a:wow-company:bubble_menu:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
@@ -10993,6 +11061,9 @@
|
||||
"cost-calculator-builder": [
|
||||
"cpe:2.3:a:stylemixthemes:cost_calculator_builder:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"coub": [
|
||||
"cpe:2.3:a:chartscss:coub:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"countdown-block": [
|
||||
"cpe:2.3:a:wpdeveloper:countdown_block:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -11644,6 +11715,9 @@
|
||||
"donation-thermometer": [
|
||||
"cpe:2.3:a:donation_thermometer_project:donation_thermometer:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"donations-block": [
|
||||
"cpe:2.3:a:bharatkambariya:donation_block_for_paypal:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"donations-for-woocommerce": [
|
||||
"cpe:2.3:a:wpzone:potent_donations_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -11731,6 +11805,9 @@
|
||||
"dtabs": [
|
||||
"cpe:2.3:a:dtabs_project:dtabs:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"dts-simple-share": [
|
||||
"cpe:2.3:a:missionmike:simple_share:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"duitku-social-payment-gateway": [
|
||||
"cpe:2.3:a:duitku:duitku_payment_gateway:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12122,9 +12199,12 @@
|
||||
"cpe:2.3:a:mandsconsulting:email_before_download:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"email-encoder-bundle": [
|
||||
"cpe:2.3:a:jannisthuemmig:email_encoder:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:onlineoptimisation:email_encoder:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wp-webhooks:email_encoder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"email-keep": [
|
||||
"cpe:2.3:a:intricateweb:email_keep:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"email-log": [
|
||||
"cpe:2.3:a:email_log_project:email_log:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12716,6 +12796,9 @@
|
||||
"five-minute-webshop": [
|
||||
"cpe:2.3:a:five_minute_webshop_project:five_minute_webshop:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"fl3r-accessibility-suite": [
|
||||
"cpe:2.3:a:fl3r:fl3r_accessibility_suite:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"fl3r-feelbox": [
|
||||
"cpe:2.3:a:armandofiore:fl3r_feelbox:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:fl3r-feelbox_project:fl3r-feelbox:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -13104,6 +13187,9 @@
|
||||
"game-server-status": [
|
||||
"cpe:2.3:a:game-server-status_project:game-server-status:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"game-users-share-buttons": [
|
||||
"cpe:2.3:a:gameusers:game_users_share_button:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gamepress": [
|
||||
"cpe:2.3:a:gamepress_project:gamepress:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13113,6 +13199,9 @@
|
||||
"gamipress-link": [
|
||||
"cpe:2.3:a:gamipress:gamipress_-_link:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gamipress-reset-user": [
|
||||
"cpe:2.3:a:gamipress:gamipress_-_reset_user:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"garden-gnome-package": [
|
||||
"cpe:2.3:a:ggnome:garden_gnome_package:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13197,6 +13286,9 @@
|
||||
"gestpay-for-woocommerce": [
|
||||
"cpe:2.3:a:fabrick:gestpay_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"get-bookings-wp": [
|
||||
"cpe:2.3:a:istmoplugins:get_bookings_wp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"get-custom-field-values": [
|
||||
"cpe:2.3:a:get_custom_field_values_project:get_custom_field_values:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13224,9 +13316,21 @@
|
||||
"gf-block-ips": [
|
||||
"cpe:2.3:a:brightplugins:block_ips_for_gravity_forms:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gf-constant-contact": [
|
||||
"cpe:2.3:a:rocketgenius:constant_contact:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gf-custom-style": [
|
||||
"cpe:2.3:a:alefypimentel:gf_custom_style:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gf-hubspot": [
|
||||
"cpe:2.3:a:crmperks:wp_gravity_forms_hubspot:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gf-insightly": [
|
||||
"cpe:2.3:a:crmperks:wp_gravity_forms_insightly:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gf-salesforce-crmperks": [
|
||||
"cpe:2.3:a:crmperks:wp_gravity_forms_salesforce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gf-zoho": [
|
||||
"cpe:2.3:a:crmperks:wp_gravity_forms_zoho_crm_and_bigin:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13659,6 +13763,9 @@
|
||||
"heureka": [
|
||||
"cpe:2.3:a:heureka:heureka:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"hide-links": [
|
||||
"cpe:2.3:a:avovkdesign:hide_links:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"hide-my-wp": [
|
||||
"cpe:2.3:a:wpplugins:hide_my_wp_ghost:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13683,6 +13790,9 @@
|
||||
"hkdev-maintenance-mode": [
|
||||
"cpe:2.3:a:helderk:maintenance_mode:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"hmapsprem": [
|
||||
"cpe:2.3:a:heroplugins:hero_maps_premium:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"hms-testimonials": [
|
||||
"cpe:2.3:a:hitmyserver:hms_testimonials:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13831,6 +13941,9 @@
|
||||
"ibtana-visual-editor": [
|
||||
"cpe:2.3:a:vowelweb:ibtana:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ibuildapp": [
|
||||
"cpe:2.3:a:ibuildapp:ibuildapp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"icegram": [
|
||||
"cpe:2.3:a:icegram:icegram_engage:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14054,6 +14167,9 @@
|
||||
"cpe:2.3:a:infusionsoft_project:infusionsoft:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:katz:infusionsoft_gravity_forms:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"infusionsoft-official-opt-in-forms": [
|
||||
"cpe:2.3:a:keap:keap_official_opt_in_forms:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"inline-google-spreadsheet-viewer": [
|
||||
"cpe:2.3:a:inline_google_spreadsheet_viewer_project:inline_google_spreadsheet_viewer:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14154,6 +14270,9 @@
|
||||
"intelly-related-posts": [
|
||||
"cpe:2.3:a:data443:inline_related_posts:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"interact-quiz-embed": [
|
||||
"cpe:2.3:a:tryinteract:embed_a_quiz_on_your_site:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"interactive-3d-flipbook-powered-physics-engine": [
|
||||
"cpe:2.3:a:3dflipbook:3d_flipbook:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14390,6 +14509,9 @@
|
||||
"jvm-rich-text-icons": [
|
||||
"cpe:2.3:a:jorisvm:jvm_gutenberg_rich_text_icons:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"jwp-a11y": [
|
||||
"cpe:2.3:a:jidaikobo:jwp-a11y:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"kadence-blocks": [
|
||||
"cpe:2.3:a:kadencewp:gutenberg_blocks_with_ai:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14879,6 +15001,9 @@
|
||||
"logo-slider-wp": [
|
||||
"cpe:2.3:a:logichunt:logo_slider:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"logs-de-connexion": [
|
||||
"cpe:2.3:a:floriansimunek:connexion_logs:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"logwpmail": [
|
||||
"cpe:2.3:a:premierethemes:log_wp_mail:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14951,6 +15076,9 @@
|
||||
"cpe:2.3:a:custom_popup_builder_project:custom_popup_builder:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:popup_\\|_custom_popup_builder_project:popup_\\|_custom_popup_builder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"magayo-lottery-results": [
|
||||
"cpe:2.3:a:magayo:magayo_lottery_results:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"magazine-blocks": [
|
||||
"cpe:2.3:a:themegrill:magazine_blocks:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15147,7 +15275,8 @@
|
||||
"cpe:2.3:a:master-addons:master_addons:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"master-slider": [
|
||||
"cpe:2.3:a:averta:master_slider:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:averta:master_slider:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:averta:master_slider:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"masterstudy-lms-learning-management-system": [
|
||||
"cpe:2.3:a:stylemixthemes:masterstudy_lms:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -15167,6 +15296,9 @@
|
||||
"materialis-companion": [
|
||||
"cpe:2.3:a:extendthemes:materialis_companion:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"matomo": [
|
||||
"cpe:2.3:a:matomo:matomo:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"matrix-pre-loader": [
|
||||
"cpe:2.3:a:nkb-bd:preloader_matrix:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15185,6 +15317,9 @@
|
||||
"mdc-youtube-downloader": [
|
||||
"cpe:2.3:a:mdc_youtube_downloader_project:mdc_youtube_downloader:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"media-alt-renamer": [
|
||||
"cpe:2.3:a:jordymeow:media_alt_renamer:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"media-downloader": [
|
||||
"cpe:2.3:a:media_downloader_project:media_downloader:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15373,6 +15508,9 @@
|
||||
"mihdan-index-now": [
|
||||
"cpe:2.3:a:kobzarev:index_now:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mini-course-generator": [
|
||||
"cpe:2.3:a:minicoursegenerator:mini_course_generator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mini-mail-dashboard-widget": [
|
||||
"cpe:2.3:a:mini_mail_dashboard_widget_project:mini_mail_dashboard_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15406,6 +15544,9 @@
|
||||
"ml-slider": [
|
||||
"cpe:2.3:a:metaslider:slider\\,_gallery\\,_and_carousel:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mm-breaking-news": [
|
||||
"cpe:2.3:a:mm-breaking_news_project:mm-breaking_news:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mmm-file-list": [
|
||||
"cpe:2.3:a:mediamanifesto:mmm_simple_file_list:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15492,6 +15633,9 @@
|
||||
"mortgage-calculators-wp": [
|
||||
"cpe:2.3:a:lenderd:mortgage_calculators_wp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mortgage-loan-calculator": [
|
||||
"cpe:2.3:a:mlcalc:mortgage_loan_calculator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"most-and-least-read-posts-widget": [
|
||||
"cpe:2.3:a:whiletrue:most_and_least_read_posts_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15508,7 +15652,7 @@
|
||||
"cpe:2.3:a:motor_racing_league_project:motor_racing_league:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"motors-car-dealership-classified-listings": [
|
||||
"cpe:2.3:a:stylemixthemes:motors_-_car_dealer\\,_classifieds_\\\u0026_listing:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:stylemixthemes:motors_-_car_dealer\\,_classifieds_\\\u0026_listing:*:*:*:*:starter:wordpress:*:*"
|
||||
],
|
||||
"mousewheel-smooth-scroll": [
|
||||
"cpe:2.3:a:mousewheel_smooth_scroll_project:mousewheel_smooth_scroll:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -15516,6 +15660,9 @@
|
||||
"move-addons": [
|
||||
"cpe:2.3:a:moveaddons:move_addons_for_elementor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"movie-database": [
|
||||
"cpe:2.3:a:heimkino-praxis:movie_database:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"movies": [
|
||||
"cpe:2.3:a:movies_project:movies:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15982,6 +16129,9 @@
|
||||
"official-sendle-shipping-method": [
|
||||
"cpe:2.3:a:joovii:sendle_shipping:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"offload-videos-bunny-netaws-s3": [
|
||||
"cpe:2.3:a:webgarh:offload_videos:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"og-tags": [
|
||||
"cpe:2.3:a:og_tags_project:og_tags:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16208,6 +16358,7 @@
|
||||
"cpe:2.3:a:benhuson:page_parts:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"page-scroll-to-id": [
|
||||
"cpe:2.3:a:malihu:page_scroll_to_id:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:page_scroll_to_id_project:page_scroll_to_id:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"page-views-count": [
|
||||
@@ -16641,6 +16792,9 @@
|
||||
"popup-zyrex": [
|
||||
"cpe:2.3:a:zyrex:popup:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"popup4phone": [
|
||||
"cpe:2.3:a:ivanweb:popup4phone:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"popupally": [
|
||||
"cpe:2.3:a:accessally:popupally:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16654,6 +16808,9 @@
|
||||
"portfolio": [
|
||||
"cpe:2.3:a:bestwebsoft:portfolio:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"portfolio-and-projects": [
|
||||
"cpe:2.3:a:essentialplugin:portfolio_and_projects:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"portfolio-by-lisa-westlund": [
|
||||
"cpe:2.3:a:portfolio_project:portfolio:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17141,7 +17298,10 @@
|
||||
"cpe:2.3:a:wepupil:quiz_expert_-_easy_quiz_maker\\,_exam_and_test_manager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"quiz-maker": [
|
||||
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:agency:wordpress:*:*",
|
||||
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:business:wordpress:*:*",
|
||||
"cpe:2.3:a:ays-pro:quiz_maker:*:*:*:*:developer:wordpress:*:*"
|
||||
],
|
||||
"quiz-master-next": [
|
||||
"cpe:2.3:a:expresstech:quiz_and_survey_master:*:*:*:*:*:wordpress:*:*",
|
||||
@@ -17441,6 +17601,9 @@
|
||||
"responsive-css-editor": [
|
||||
"cpe:2.3:a:wpwox:responsive_css_editor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"responsive-filterable-portfolio": [
|
||||
"cpe:2.3:a:i13websolution:responsive_filterable_portfolio:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"responsive-food-and-drink-menu": [
|
||||
"cpe:2.3:a:corporatezen:responsive_food_and_drink_menu:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17470,6 +17633,12 @@
|
||||
"responsive-vector-maps": [
|
||||
"cpe:2.3:a:thinkupthemes:responsive_vector_maps:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"responsive-video-embed": [
|
||||
"cpe:2.3:a:lepileppanen:responsive_video_embed:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"rest-api-to-miniprogram": [
|
||||
"cpe:2.3:a:jianbo:rest_api_to_miniprogram:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"restaurant-cafe-addon-for-elementor": [
|
||||
"cpe:2.3:a:nicheaddons:restaurant_\\\u0026_cafe_addon_for_elementor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17655,6 +17824,9 @@
|
||||
"safe-svg": [
|
||||
"cpe:2.3:a:10up:safe_svg:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"safetymails-forms": [
|
||||
"cpe:2.3:a:henriquerodrigues:safetyforms:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"sagepay-server-gateway-for-woocommerce": [
|
||||
"cpe:2.3:a:patsatech:sagepay_server_gateway_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17707,6 +17879,9 @@
|
||||
"schema-app-structured-data-for-schemaorg": [
|
||||
"cpe:2.3:a:schemaapp:schema_app_structured_data:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"school-management": [
|
||||
"cpe:2.3:a:dasinfomedia:school_management_system:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"school-management-system": [
|
||||
"cpe:2.3:a:weblizar:school_management_-_education_\\\u0026_learning_management:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18091,6 +18266,9 @@
|
||||
"simple-author-box": [
|
||||
"cpe:2.3:a:webfactoryltd:simple_author_box:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-backup": [
|
||||
"cpe:2.3:a:mywebsiteadvisor:simple_backup:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-banner": [
|
||||
"cpe:2.3:a:simple_banner_project:simple_banner:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18545,6 +18723,9 @@
|
||||
"slideshow-se": [
|
||||
"cpe:2.3:a:slideshow_se_project:slideshow_se:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"slingblocks": [
|
||||
"cpe:2.3:a:funnelkit:slingblocks:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"small-package-quotes-fedex-edition": [
|
||||
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:fedex:wordpress:*:*"
|
||||
],
|
||||
@@ -18837,6 +19018,9 @@
|
||||
"sportspress": [
|
||||
"cpe:2.3:a:themeboy:sportspress:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"spotbot": [
|
||||
"cpe:2.3:a:jakehelbig:spotbot:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"spotify-play-button-for-wordpress": [
|
||||
"cpe:2.3:a:followmedarling:spotify-play-button-for-wordpress:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18867,6 +19051,9 @@
|
||||
"srbtranslatin": [
|
||||
"cpe:2.3:a:srbtranslatin_project:srbtranslatin:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ssv-events": [
|
||||
"cpe:2.3:a:moridrin:ssv_events:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"st-daily-tip": [
|
||||
"cpe:2.3:a:sanskruti:st-daily-tip:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18981,6 +19168,9 @@
|
||||
"strategery-migrations": [
|
||||
"cpe:2.3:a:strategery_migrations_project:strategery_migrations:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"stratum": [
|
||||
"cpe:2.3:a:motopress:stratum:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"stray-quotes": [
|
||||
"cpe:2.3:a:unalignedcode:stray_random_quotes:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19011,6 +19201,9 @@
|
||||
"subaccounts-for-woocommerce": [
|
||||
"cpe:2.3:a:mediaticus:subaccounts_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"sublanguage": [
|
||||
"cpe:2.3:a:maximeschoeni:sublanguage:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"subscribe-sidebar": [
|
||||
"cpe:2.3:a:blubrry:subscribe_sidebar:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19447,12 +19640,18 @@
|
||||
"timetics": [
|
||||
"cpe:2.3:a:arraytics:wp_timetics:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"timezonecalculator": [
|
||||
"cpe:2.3:a:bernhard-riedl:timezonecalculator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"timthumb-vulnerability-scanner": [
|
||||
"cpe:2.3:a:peterbutler:timthumb_vulnerability_scanner:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tinychat-roomspy": [
|
||||
"cpe:2.3:a:tinychat:room_spy:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tinymce-and-tinymce-advanced-professsional-formats-and-styles": [
|
||||
"cpe:2.3:a:blackbam:tinymce_and_tinymce_advanced_professsional_formats_and_styles:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tinymce-custom-styles": [
|
||||
"cpe:2.3:a:tinymce_custom_styles_project:tinymce_custom_styles:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19521,6 +19720,9 @@
|
||||
"tournamatch": [
|
||||
"cpe:2.3:a:tournamatch:tournamatch:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tournament-bracket-generator": [
|
||||
"cpe:2.3:a:blakelong:tournament_bracket_generator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tp-education": [
|
||||
"cpe:2.3:a:themepalace:tp_education:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19939,6 +20141,9 @@
|
||||
"user-login-log": [
|
||||
"cpe:2.3:a:intechnosoftware:user_login_log:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"user-messages": [
|
||||
"cpe:2.3:a:marvinlabs:user_messages:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"user-meta": [
|
||||
"cpe:2.3:a:user-meta:user_meta_user_profile_builder_and_user_management:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19962,6 +20167,9 @@
|
||||
"user-role": [
|
||||
"cpe:2.3:a:bestwebsoft:user_role:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"user-shortcodes-plus": [
|
||||
"cpe:2.3:a:kylebjohnson:user_shortcodes_plus:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"user-spam-remover": [
|
||||
"cpe:2.3:a:joelhardi:user_spam_remover:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -20282,6 +20490,9 @@
|
||||
"weaverx-theme-support": [
|
||||
"cpe:2.3:a:weavertheme:weaver_xtreme_theme_support:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"web-application-firewall": [
|
||||
"cpe:2.3:a:miniorange:web_application_firewall:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"web-directory-free": [
|
||||
"cpe:2.3:a:salephpscripts:web_directory_free:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -20574,6 +20785,9 @@
|
||||
"woo-gutenberg-products-block": [
|
||||
"cpe:2.3:a:automattic:woocommerce_blocks:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"woo-mailerlite": [
|
||||
"cpe:2.3:a:mailerlite:mailerlite:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"woo-moneybird": [
|
||||
"cpe:2.3:a:techastha:integration_of_moneybird_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -20808,6 +21022,9 @@
|
||||
"wooemailreport": [
|
||||
"cpe:2.3:a:eggemplo:woocommerce_email_report:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wooexim": [
|
||||
"cpe:2.3:a:bulktheme:wooexim:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wooframework-tweaks": [
|
||||
"cpe:2.3:a:woocommerce:wooframework_tweaks:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -20923,6 +21140,9 @@
|
||||
"wordpress-users": [
|
||||
"cpe:2.3:a:jonathonkemp:wordpress_users:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"work-the-flow-file-upload": [
|
||||
"cpe:2.3:a:lyntonreed:work_the_flow_file_upload:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"workreap": [
|
||||
"cpe:2.3:a:amentotech:workreap:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -21154,6 +21374,9 @@
|
||||
"wp-consultant": [
|
||||
"cpe:2.3:a:wp_consultant_project:wp_consultant:*:*:-:*:-:wordpress:*:*"
|
||||
],
|
||||
"wp-contact-form": [
|
||||
"cpe:2.3:a:ftwr:wp_contact_form:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-contact-slider": [
|
||||
"cpe:2.3:a:wpexperts:wp_contact_slider:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -21452,6 +21675,9 @@
|
||||
"wp-fountain": [
|
||||
"cpe:2.3:a:wp_fountain_project:wp_fountain:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-front-end-login-and-register": [
|
||||
"cpe:2.3:a:hellomohsinkhan:wp_front-end_login_and_register:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-front-end-profile": [
|
||||
"cpe:2.3:a:wp_front_end_profile_project:wp_front_end_profile:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -21612,6 +21838,9 @@
|
||||
"wp-jobs": [
|
||||
"cpe:2.3:a:intensewp:wp_jobs:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-jobs2careers": [
|
||||
"cpe:2.3:a:skipthedrive:wp_talroo:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-jquery-lightbox": [
|
||||
"cpe:2.3:a:lightpress:lightbox:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -21904,6 +22133,9 @@
|
||||
"wp-product-gallery-lite": [
|
||||
"cpe:2.3:a:accesspressthemes:wp_product_gallery_lite:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-programmmanager": [
|
||||
"cpe:2.3:a:mantus667:wp-pmanager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-propagator": [
|
||||
"cpe:2.3:a:widgilabs:plugin_propagator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -22585,7 +22817,8 @@
|
||||
"cpe:2.3:a:maennchen1:wpshopgermany_-_protected_shops:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpshopify": [
|
||||
"cpe:2.3:a:shopwp_project:shopwp:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:shopwp_project:shopwp:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wpshop:shopwp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpsimpletools-log-viewer": [
|
||||
"cpe:2.3:a:wpsimpletools:basic_log_viewer:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -23112,6 +23345,9 @@
|
||||
"empowerwp": [
|
||||
"cpe:2.3:a:extendthemes:empowerwp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"enfold": [
|
||||
"cpe:2.3:a:kriesi:enfold:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"enlighten": [
|
||||
"cpe:2.3:a:accesspressthemes:enlighten:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -23356,6 +23592,9 @@
|
||||
"tijaji": [
|
||||
"cpe:2.3:a:tijaji:tijaji:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"total": [
|
||||
"cpe:2.3:a:hashthemes:total:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"transcend": [
|
||||
"cpe:2.3:a:cpothemes:transcend:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
|
||||
@@ -1,26 +1,49 @@
|
||||
package cpegenerate
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func candidateVendorsForRPM(p pkg.Package) fieldCandidateSet {
|
||||
vendors := newFieldCandidateSet()
|
||||
var vendor string
|
||||
|
||||
switch m := p.Metadata.(type) {
|
||||
case pkg.RpmDBEntry:
|
||||
if m.Vendor != "" {
|
||||
vendors.add(fieldCandidate{
|
||||
value: normalizeName(m.Vendor),
|
||||
disallowSubSelections: true,
|
||||
})
|
||||
}
|
||||
vendor = m.Vendor
|
||||
case pkg.RpmArchive:
|
||||
if m.Vendor != "" {
|
||||
vendors.add(fieldCandidate{
|
||||
value: normalizeName(m.Vendor),
|
||||
disallowSubSelections: true,
|
||||
})
|
||||
}
|
||||
vendor = m.Vendor
|
||||
}
|
||||
|
||||
vendor = stripTrailingURL(vendor)
|
||||
if vendor != "" {
|
||||
vendors.add(fieldCandidate{
|
||||
value: normalizeName(vendor),
|
||||
disallowSubSelections: true,
|
||||
})
|
||||
}
|
||||
|
||||
return vendors
|
||||
}
|
||||
|
||||
func stripTrailingURL(value string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if !strings.HasSuffix(trimmed, ">") {
|
||||
return value
|
||||
}
|
||||
|
||||
open := strings.LastIndex(trimmed, "<")
|
||||
if open == -1 {
|
||||
return value
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(trimmed[open+1 : len(trimmed)-1])
|
||||
if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") {
|
||||
return value
|
||||
}
|
||||
|
||||
return strings.TrimSpace(trimmed[:open])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package cpegenerate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestCandidateVendorsForRPM(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata any
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "database vendor with publisher URL",
|
||||
metadata: pkg.RpmDBEntry{
|
||||
Vendor: "SUSE LLC <https://www.suse.com/>",
|
||||
},
|
||||
expected: []string{"susellc"},
|
||||
},
|
||||
{
|
||||
name: "archive vendor with publisher URL",
|
||||
metadata: pkg.RpmArchive{
|
||||
Vendor: "SUSE LLC <https://www.suse.com/>",
|
||||
},
|
||||
expected: []string{"susellc"},
|
||||
},
|
||||
{
|
||||
name: "plain vendor",
|
||||
metadata: pkg.RpmDBEntry{
|
||||
Vendor: "Red Hat, Inc.",
|
||||
},
|
||||
expected: []string{"redhat"},
|
||||
},
|
||||
{
|
||||
name: "non-URL angle-bracket suffix",
|
||||
metadata: pkg.RpmDBEntry{
|
||||
Vendor: "Example <support@example.com>",
|
||||
},
|
||||
expected: []string{"example<support@example.com>"},
|
||||
},
|
||||
{
|
||||
name: "non-HTTP URL",
|
||||
metadata: pkg.RpmDBEntry{
|
||||
Vendor: "Example <ftp://example.com>",
|
||||
},
|
||||
expected: []string{"example<ftp://example.com>"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
p := pkg.Package{Metadata: test.metadata}
|
||||
assert.ElementsMatch(t, test.expected, candidateVendorsForRPM(p).uniqueValues())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,11 @@ type packageLock struct {
|
||||
|
||||
// lockDependency represents a single package dependency listed in the package.lock json file
|
||||
type lockDependency struct {
|
||||
Version string `json:"version"`
|
||||
Resolved string `json:"resolved"`
|
||||
Integrity string `json:"integrity"`
|
||||
Dev bool `json:"dev"`
|
||||
Version string `json:"version"`
|
||||
Resolved string `json:"resolved"`
|
||||
Integrity string `json:"integrity"`
|
||||
Dev bool `json:"dev"`
|
||||
Dependencies map[string]lockDependency `json:"dependencies"`
|
||||
}
|
||||
|
||||
type lockPackage struct {
|
||||
@@ -77,14 +78,7 @@ func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolve
|
||||
}
|
||||
|
||||
if lock.LockfileVersion == 1 {
|
||||
for name, pkgMeta := range lock.Dependencies {
|
||||
// skip packages that are only present as a dev dependency
|
||||
if !a.cfg.IncludeDevDependencies && pkgMeta.Dev {
|
||||
continue
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, newPackageLockV1Package(ctx, a.cfg, resolver, reader.Location, name, pkgMeta))
|
||||
}
|
||||
pkgs = append(pkgs, a.packageLockV1Packages(ctx, resolver, reader.Location, lock.Dependencies)...)
|
||||
}
|
||||
|
||||
if lock.LockfileVersion == 2 || lock.LockfileVersion == 3 {
|
||||
@@ -116,6 +110,32 @@ func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolve
|
||||
return pkgs, dependency.Resolve(packageLockDependencySpecifier, pkgs), unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
}
|
||||
|
||||
func (a genericPackageLockAdapter) packageLockV1Packages(ctx context.Context, resolver file.Resolver, location file.Location, dependencies map[string]lockDependency) []pkg.Package {
|
||||
var pkgs []pkg.Package
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
var walk func(map[string]lockDependency)
|
||||
walk = func(dependencies map[string]lockDependency) {
|
||||
for name, pkgMeta := range dependencies {
|
||||
// Skipping a dev-only dependency also skips its dev-only subtree.
|
||||
if !a.cfg.IncludeDevDependencies && pkgMeta.Dev {
|
||||
continue
|
||||
}
|
||||
|
||||
p := newPackageLockV1Package(ctx, a.cfg, resolver, location, name, pkgMeta)
|
||||
if _, exists := seen[p.PURL]; !exists {
|
||||
pkgs = append(pkgs, p)
|
||||
seen[p.PURL] = struct{}{}
|
||||
}
|
||||
|
||||
walk(pkgMeta.Dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
walk(dependencies)
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||
// The license field could be either a string or an array.
|
||||
|
||||
|
||||
@@ -111,6 +111,66 @@ func TestParsePackageLock(t *testing.T) {
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestParsePackageLockV1NestedDependencies(t *testing.T) {
|
||||
fixture := "testdata/pkg-lock/nested-package-lock-1.json"
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "duplicate",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/duplicate@1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/duplicate/-/duplicate-1.0.0.tgz", Integrity: "sha512-duplicate"},
|
||||
},
|
||||
{
|
||||
Name: "middle",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/middle@1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/middle/-/middle-1.0.0.tgz", Integrity: "sha512-middle"},
|
||||
},
|
||||
{
|
||||
Name: "parent",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/parent@1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/parent/-/parent-1.0.0.tgz", Integrity: "sha512-parent"},
|
||||
},
|
||||
{
|
||||
Name: "shared",
|
||||
Version: "0.5.0",
|
||||
PURL: "pkg:npm/shared@0.5.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/shared/-/shared-0.5.0.tgz", Integrity: "sha512-shared-0.5"},
|
||||
},
|
||||
{
|
||||
Name: "shared",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/shared@1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/shared/-/shared-1.0.0.tgz", Integrity: "sha512-shared-1"},
|
||||
},
|
||||
{
|
||||
Name: "shared",
|
||||
Version: "2.0.0",
|
||||
PURL: "pkg:npm/shared@2.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/shared/-/shared-2.0.0.tgz", Integrity: "sha512-shared-2"},
|
||||
},
|
||||
}
|
||||
for i := range expectedPkgs {
|
||||
expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
|
||||
}
|
||||
|
||||
adapter := newGenericPackageLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, nil)
|
||||
}
|
||||
|
||||
func TestParsePackageLockV2(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
fixture := "testdata/pkg-lock/package-lock-2.json"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"parent": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parent/-/parent-1.0.0.tgz",
|
||||
"integrity": "sha512-parent",
|
||||
"dependencies": {
|
||||
"shared": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shared/-/shared-1.0.0.tgz",
|
||||
"integrity": "sha512-shared-1"
|
||||
},
|
||||
"middle": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/middle/-/middle-1.0.0.tgz",
|
||||
"integrity": "sha512-middle",
|
||||
"dependencies": {
|
||||
"shared": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/shared/-/shared-0.5.0.tgz",
|
||||
"integrity": "sha512-shared-0.5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"duplicate": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/duplicate/-/duplicate-1.0.0.tgz",
|
||||
"integrity": "sha512-duplicate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shared/-/shared-2.0.0.tgz",
|
||||
"integrity": "sha512-shared-2"
|
||||
},
|
||||
"duplicate": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/duplicate/-/duplicate-1.0.0.tgz",
|
||||
"integrity": "sha512-duplicate"
|
||||
},
|
||||
"dev-parent": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dev-nested": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ func newPackageFromCargoMetadata(m pkg.RustCargoLockEntry, locations ...file.Loc
|
||||
Name: m.Name,
|
||||
Version: m.Version,
|
||||
Locations: file.NewLocationSet(locations...),
|
||||
PURL: packageURL(m.Name, m.Version),
|
||||
PURL: cargoLockPackageURL(m),
|
||||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
Metadata: m,
|
||||
@@ -25,6 +25,18 @@ func newPackageFromCargoMetadata(m pkg.RustCargoLockEntry, locations ...file.Loc
|
||||
return p
|
||||
}
|
||||
|
||||
func cargoLockPackageURL(m pkg.RustCargoLockEntry) string {
|
||||
// Cargo omits source for packages resolved from the local workspace or a
|
||||
// path dependency. A cargo PURL without a repository qualifier identifies
|
||||
// a package on crates.io, so emitting one for a local package can cause
|
||||
// downstream scanners to match unrelated registry vulnerabilities.
|
||||
if m.Source == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return packageURL(m.Name, m.Version)
|
||||
}
|
||||
|
||||
func newPackageFromAudit(dep *rustaudit.Package, locations ...file.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: dep.Name,
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_packageURL(t *testing.T) {
|
||||
@@ -31,3 +33,34 @@ func Test_packageURL(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPackageFromCargoMetadataPURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "crates.io package",
|
||||
source: "registry+https://github.com/rust-lang/crates.io-index",
|
||||
want: "pkg:cargo/telemetry@0.1.0",
|
||||
},
|
||||
{
|
||||
name: "local path package",
|
||||
source: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := newPackageFromCargoMetadata(pkg.RustCargoLockEntry{
|
||||
Name: "telemetry",
|
||||
Version: "0.1.0",
|
||||
Source: tt.source,
|
||||
})
|
||||
|
||||
assert.Equal(t, tt.want, got.PURL)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ func TestCargoLockWithGitDependencies(t *testing.T) {
|
||||
helloWorld := pkg.Package{
|
||||
Name: "hello_world",
|
||||
Version: "0.1.0",
|
||||
PURL: "pkg:cargo/hello_world@0.1.0",
|
||||
PURL: "",
|
||||
Locations: locations,
|
||||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
@@ -673,6 +673,77 @@ func TestCargoLockWithGitDependencies(t *testing.T) {
|
||||
pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestCargoLockWithPathDependencies(t *testing.T) {
|
||||
fixture := "testdata/path-deps/Cargo.lock"
|
||||
locations := file.NewLocationSet(file.NewLocation(fixture))
|
||||
|
||||
app := pkg.Package{
|
||||
Name: "app",
|
||||
Version: "1.0.0",
|
||||
Locations: locations,
|
||||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Metadata: pkg.RustCargoLockEntry{
|
||||
Name: "app",
|
||||
Version: "1.0.0",
|
||||
Dependencies: []string{
|
||||
"memchr",
|
||||
"telemetry",
|
||||
},
|
||||
},
|
||||
}
|
||||
memchr := pkg.Package{
|
||||
Name: "memchr",
|
||||
Version: "2.7.4",
|
||||
PURL: "pkg:cargo/memchr@2.7.4",
|
||||
Locations: locations,
|
||||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Metadata: pkg.RustCargoLockEntry{
|
||||
Name: "memchr",
|
||||
Version: "2.7.4",
|
||||
Source: "registry+https://github.com/rust-lang/crates.io-index",
|
||||
Checksum: "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3",
|
||||
Dependencies: []string{},
|
||||
},
|
||||
}
|
||||
telemetry := pkg.Package{
|
||||
Name: "telemetry",
|
||||
Version: "0.1.0",
|
||||
Locations: locations,
|
||||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Metadata: pkg.RustCargoLockEntry{
|
||||
Name: "telemetry",
|
||||
Version: "0.1.0",
|
||||
Dependencies: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
expectedPkgs := []pkg.Package{
|
||||
app,
|
||||
memchr,
|
||||
telemetry,
|
||||
}
|
||||
expectedRelationships := []artifact.Relationship{
|
||||
{
|
||||
From: memchr,
|
||||
To: app,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: telemetry,
|
||||
To: app,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestCargoLockDependencySpecification(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "app"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"telemetry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "telemetry"
|
||||
version = "0.1.0"
|
||||
@@ -1,6 +1,7 @@
|
||||
package snapsource
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
@@ -21,6 +22,11 @@ type snapManifest struct {
|
||||
|
||||
const manifestLocation = "/meta/snap.yaml"
|
||||
|
||||
// errNoManifest means the payload is a readable squashfs that simply does not carry a snap manifest.
|
||||
// This is distinct from a manifest that is present but unusable, and callers are expected to treat
|
||||
// the two differently.
|
||||
var errNoManifest = errors.New("no snap manifest file found")
|
||||
|
||||
func parseManifest(resolver file.Resolver) (*snapManifest, error) {
|
||||
locations, err := resolver.FilesByPath(manifestLocation)
|
||||
if err != nil {
|
||||
@@ -28,7 +34,7 @@ func parseManifest(resolver file.Resolver) (*snapManifest, error) {
|
||||
}
|
||||
|
||||
if len(locations) == 0 {
|
||||
return nil, fmt.Errorf("no snap manifest file found")
|
||||
return nil, errNoManifest
|
||||
}
|
||||
|
||||
if len(locations) > 1 {
|
||||
@@ -48,9 +54,8 @@ func parseManifest(resolver file.Resolver) (*snapManifest, error) {
|
||||
return nil, fmt.Errorf("unable to decode snap manifest file: %w", err)
|
||||
}
|
||||
|
||||
if manifest.Name == "" || manifest.Version == "" {
|
||||
return nil, fmt.Errorf("invalid snap manifest file: missing name or version")
|
||||
}
|
||||
|
||||
// note: name and version are deliberately not validated here. a manifest that decoded but is
|
||||
// missing them still carries usable fields (summary, base, grade, ...) and throwing it away would
|
||||
// describe less than we know. the caller surfaces the missing identity.
|
||||
return &manifest, nil
|
||||
}
|
||||
|
||||
@@ -77,22 +77,33 @@ func getRemoteSnapFile(ctx context.Context, fs afero.Fs, getter intFile.Getter,
|
||||
return newSnapFileFromRemote(ctx, fs, cfg, getter, info)
|
||||
}
|
||||
|
||||
func newSnapFileFromRemote(ctx context.Context, fs afero.Fs, cfg Config, getter intFile.Getter, info *remoteSnap) (*snapFile, error) {
|
||||
func newSnapFileFromRemote(ctx context.Context, fs afero.Fs, cfg Config, getter intFile.Getter, info *remoteSnap) (_ *snapFile, err error) {
|
||||
t, err := afero.TempDir(fs, "", "syft-snap-")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
|
||||
closer := func() error {
|
||||
return fs.RemoveAll(t)
|
||||
}
|
||||
|
||||
// any failure past this point must not leave the temp directory (or a partially downloaded snap)
|
||||
// behind. On success the caller owns the directory by way of snapFile.Cleanup.
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if closeErr := closer(); closeErr != nil {
|
||||
log.WithFields("error", closeErr, "directory", t).Warn("unable to remove temp directory for snap")
|
||||
}
|
||||
}()
|
||||
|
||||
snapFilePath := path.Join(t, path.Base(info.URL))
|
||||
err = downloadSnap(getter, info, snapFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to download snap file: %w", err)
|
||||
}
|
||||
|
||||
closer := func() error {
|
||||
return fs.RemoveAll(t)
|
||||
}
|
||||
|
||||
mimeType, digests, err := getSnapFileInfo(ctx, fs, snapFilePath, cfg.DigestAlgorithms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -3,6 +3,7 @@ package snapsource
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -119,7 +120,21 @@ func newFromPath(cfg Config, f *snapFile) (source.Source, error) {
|
||||
closer: f.Cleanup,
|
||||
}
|
||||
|
||||
return s, s.extractManifest()
|
||||
if err := s.extractManifest(); err != nil {
|
||||
// It is tried on any path ending in .snap or .squashfs, so declining one is routine and
|
||||
// a later provider usually succeeds. When the user asked for --from snap there is no
|
||||
// fallback and this error reaches them intact.
|
||||
log.WithFields("error", err, "path", f.Path).Debug("unable to read snap")
|
||||
|
||||
// never hand back a source alongside an error: callers that discard the source on error would
|
||||
// leave behind the open squashfs file and the temp directory holding a downloaded snap
|
||||
if closeErr := s.Close(); closeErr != nil {
|
||||
log.WithFields("error", closeErr, "path", f.Path).Warn("unable to clean up snap source")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *snapSource) extractManifest() error {
|
||||
@@ -128,13 +143,26 @@ func (s *snapSource) extractManifest() error {
|
||||
return fmt.Errorf("unable to create snap file resolver: %w", err)
|
||||
}
|
||||
|
||||
// a manifest problem must never fail the source. this source is the only provider that can descend
|
||||
// into squashfs (filesource cannot), so returning an error here would fall through to a provider
|
||||
// that reports zero packages for a payload that has plenty. describe what we can instead.
|
||||
manifest, err := parseManifest(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse snap manifest file: %w", err)
|
||||
switch {
|
||||
case errors.Is(err, errNoManifest):
|
||||
// not every squashfs payload is a snap, and one without a manifest is still worth cataloging
|
||||
log.WithFields("path", s.squashfsPath).Debug("no snap manifest file found")
|
||||
return nil
|
||||
case err != nil:
|
||||
// the manifest is there but unusable, which is worth surfacing: the SBOM will have no source
|
||||
// name or version to show for it
|
||||
log.WithFields("error", err, "path", s.squashfsPath).Warn("unable to parse snap manifest file")
|
||||
return nil
|
||||
}
|
||||
|
||||
if manifest != nil {
|
||||
s.manifest = *manifest
|
||||
s.manifest = *manifest
|
||||
|
||||
if s.manifest.Name == "" || s.manifest.Version == "" {
|
||||
log.WithFields("path", s.squashfsPath).Warn("snap manifest is missing name or version")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -176,25 +204,34 @@ func (s snapSource) Describe() source.Description {
|
||||
}
|
||||
}
|
||||
|
||||
// Close releases everything the source holds. Every step runs even if an earlier one fails, since
|
||||
// bailing early would skip the temp directory removal and leave a downloaded snap on disk. Safe to
|
||||
// call more than once.
|
||||
func (s *snapSource) Close() error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
var errs error
|
||||
if s.squashFileCloser != nil {
|
||||
if err := s.squashFileCloser(); err != nil {
|
||||
return fmt.Errorf("unable to close snap resolver: %w", err)
|
||||
errs = errors.Join(errs, fmt.Errorf("unable to close snap resolver: %w", err))
|
||||
}
|
||||
s.squashFileCloser = nil
|
||||
}
|
||||
s.resolver = nil
|
||||
if s.fs != nil {
|
||||
if err := s.fs.Close(); err != nil {
|
||||
return fmt.Errorf("unable to close snap squashfs: %w", err)
|
||||
errs = errors.Join(errs, fmt.Errorf("unable to close snap squashfs: %w", err))
|
||||
}
|
||||
s.fs = nil
|
||||
}
|
||||
if s.closer != nil {
|
||||
if err := s.closer(); err != nil {
|
||||
return fmt.Errorf("unable to close snap source: %w", err)
|
||||
errs = errors.Join(errs, fmt.Errorf("unable to close snap source: %w", err))
|
||||
}
|
||||
s.closer = nil
|
||||
}
|
||||
return nil
|
||||
return errs
|
||||
}
|
||||
|
||||
func (s *snapSource) FileResolver(_ source.Scope) (file.Resolver, error) {
|
||||
|
||||
@@ -2,15 +2,23 @@ package snapsource
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
diskFile "github.com/diskfs/go-diskfs/backend/file"
|
||||
"github.com/diskfs/go-diskfs/filesystem/squashfs"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/go-homedir"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestNewFromLocal(t *testing.T) {
|
||||
@@ -84,3 +92,205 @@ func TestNewFromLocal(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFromPathOnResolverError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
// remote snaps carry a cleanup closure for the temp directory holding the download; local snaps
|
||||
// have none, since the file belongs to the user
|
||||
remote bool
|
||||
wantCleanupRuns int
|
||||
wantFileRemoved bool
|
||||
}{
|
||||
{
|
||||
name: "downloaded snap is cleaned up",
|
||||
remote: true,
|
||||
wantCleanupRuns: 1,
|
||||
wantFileRemoved: true,
|
||||
},
|
||||
{
|
||||
name: "local snap file is left in place",
|
||||
remote: false,
|
||||
wantCleanupRuns: 0,
|
||||
wantFileRemoved: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// the resolver reads the squashfs from the real filesystem, so the payload must live on disk
|
||||
tempDir := t.TempDir()
|
||||
snapPath := filepath.Join(tempDir, "not-really-a-snap.snap")
|
||||
require.NoError(t, os.WriteFile(snapPath, []byte("not squashfs"), 0600))
|
||||
|
||||
var cleanupRuns int
|
||||
f := &snapFile{
|
||||
Path: snapPath,
|
||||
MimeType: "text/plain",
|
||||
}
|
||||
if tt.remote {
|
||||
f.Cleanup = func() error {
|
||||
cleanupRuns++
|
||||
return os.RemoveAll(tempDir)
|
||||
}
|
||||
}
|
||||
|
||||
got, err := newFromPath(Config{Request: snapPath}, f)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unable to create snap file resolver")
|
||||
|
||||
// a source returned alongside an error is discarded by callers without being closed, which
|
||||
// would leave the downloaded snap behind
|
||||
assert.Nil(t, got, "expected nil source on error")
|
||||
assert.Equal(t, tt.wantCleanupRuns, cleanupRuns)
|
||||
|
||||
_, statErr := os.Stat(snapPath)
|
||||
if tt.wantFileRemoved {
|
||||
assert.True(t, os.IsNotExist(statErr), "snap file was not cleaned up")
|
||||
} else {
|
||||
assert.NoError(t, statErr, "the user's snap file must never be removed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// a manifest problem must never fail the source: snapsource is the only provider that can descend
|
||||
// into squashfs, so an error here falls through to filesource and every package inside the payload
|
||||
// silently disappears from the SBOM.
|
||||
func TestNewFromPathToleratesManifestProblems(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
manifest string // written to /meta/snap.yaml; empty means no manifest at all
|
||||
wantName string
|
||||
wantVersion string
|
||||
wantSummary string
|
||||
}{
|
||||
{
|
||||
name: "no manifest at all",
|
||||
manifest: "",
|
||||
},
|
||||
{
|
||||
name: "manifest is not valid yaml",
|
||||
manifest: "name: etcd\n\tversion: 3.4\n",
|
||||
},
|
||||
{
|
||||
name: "manifest is missing name and version",
|
||||
manifest: "summary: a thing\nbase: core22\n",
|
||||
wantSummary: "a thing",
|
||||
},
|
||||
{
|
||||
name: "manifest is complete",
|
||||
manifest: "name: etcd\nversion: 3.4.0\nsummary: a thing\n",
|
||||
wantName: "etcd",
|
||||
wantVersion: "3.4.0",
|
||||
wantSummary: "a thing",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
snapPath := writeSquashfs(t, tt.manifest)
|
||||
|
||||
src, err := newFromPath(Config{Request: snapPath}, &snapFile{Path: snapPath})
|
||||
require.NoError(t, err, "a manifest problem must not fail the source")
|
||||
require.NotNil(t, src)
|
||||
t.Cleanup(func() { _ = src.Close() })
|
||||
|
||||
desc := src.Describe()
|
||||
assert.Equal(t, tt.wantName, desc.Name)
|
||||
assert.Equal(t, tt.wantVersion, desc.Version)
|
||||
|
||||
meta, ok := desc.Metadata.(source.SnapMetadata)
|
||||
require.True(t, ok, "expected snap metadata")
|
||||
assert.Equal(t, tt.wantSummary, meta.Summary, "fields that did decode must survive")
|
||||
|
||||
// the whole point: the payload is still walkable, so cataloging sees what is inside
|
||||
r, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
locations, err := r.FilesByPath("/payload.txt")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, locations, 1, "contents must remain catalogable regardless of the manifest")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapSourceClose(t *testing.T) {
|
||||
newSource := func(closeErr, cleanupErr error) (*snapSource, *int) {
|
||||
var cleanupRuns int
|
||||
return &snapSource{
|
||||
mutex: &sync.Mutex{},
|
||||
squashFileCloser: func() error {
|
||||
return closeErr
|
||||
},
|
||||
closer: func() error {
|
||||
cleanupRuns++
|
||||
return cleanupErr
|
||||
},
|
||||
}, &cleanupRuns
|
||||
}
|
||||
|
||||
t.Run("temp directory is removed even when closing the file handle fails", func(t *testing.T) {
|
||||
s, cleanupRuns := newSource(errors.New("handle is busy"), nil)
|
||||
|
||||
err := s.Close()
|
||||
|
||||
// the whole point: a failure here used to return early and skip the cleanup below, leaving a
|
||||
// downloaded snap on disk
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "handle is busy")
|
||||
assert.Equal(t, 1, *cleanupRuns, "temp directory removal was skipped")
|
||||
})
|
||||
|
||||
t.Run("every failure is reported, not just the first", func(t *testing.T) {
|
||||
s, _ := newSource(errors.New("handle is busy"), errors.New("directory is busy"))
|
||||
|
||||
err := s.Close()
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "handle is busy")
|
||||
assert.Contains(t, err.Error(), "directory is busy")
|
||||
})
|
||||
|
||||
t.Run("is safe to call twice", func(t *testing.T) {
|
||||
s, cleanupRuns := newSource(nil, nil)
|
||||
|
||||
require.NoError(t, s.Close())
|
||||
require.NoError(t, s.Close())
|
||||
assert.Equal(t, 1, *cleanupRuns, "cleanup must not run again on a second close")
|
||||
})
|
||||
}
|
||||
|
||||
// writeSquashfs builds a real squashfs image containing /payload.txt and, when manifest is non-empty,
|
||||
// /meta/snap.yaml. Returns the path to the image.
|
||||
func writeSquashfs(t *testing.T, manifest string) string {
|
||||
t.Helper()
|
||||
|
||||
snapPath := filepath.Join(t.TempDir(), "test.snap")
|
||||
f, err := os.Create(snapPath)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, f.Close()) }()
|
||||
|
||||
fs, err := squashfs.Create(diskFile.New(f, false), 0, 0, 4096)
|
||||
require.NoError(t, err)
|
||||
|
||||
write := func(path, contents string) {
|
||||
t.Helper()
|
||||
w, err := fs.OpenFile(path, os.O_CREATE|os.O_RDWR)
|
||||
require.NoError(t, err)
|
||||
_, err = w.Write([]byte(contents))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// note: go-diskfs wants paths relative to the image root, without a leading slash
|
||||
require.NoError(t, fs.Mkdir("."))
|
||||
write("payload.txt", "hello")
|
||||
|
||||
if manifest != "" {
|
||||
require.NoError(t, fs.Mkdir("meta"))
|
||||
write(strings.TrimPrefix(manifestLocation, "/"), manifest)
|
||||
}
|
||||
|
||||
require.NoError(t, fs.Finalize(squashfs.FinalizeOptions{}))
|
||||
return snapPath
|
||||
}
|
||||
|
||||
@@ -264,6 +264,10 @@ func TestNewSnapFileFromRemote(t *testing.T) {
|
||||
|
||||
_, err = fs.Stat(result.Path)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
// cleanup owns the whole temp directory, not just the snap file within it
|
||||
_, err = fs.Stat(filepath.Dir(result.Path))
|
||||
assert.True(t, os.IsNotExist(err), "temp directory outlived the cleanup closure")
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -315,6 +319,75 @@ func TestNewSnapFileFromRemote(t *testing.T) {
|
||||
expectError: true,
|
||||
errorMsg: "failed to download snap file",
|
||||
},
|
||||
{
|
||||
name: "download writes partial data then fails",
|
||||
cfg: Config{
|
||||
DigestAlgorithms: []crypto.Hash{crypto.SHA256},
|
||||
},
|
||||
info: &remoteSnap{
|
||||
snapIdentity: snapIdentity{
|
||||
Name: "partial-snap",
|
||||
Channel: "stable",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
URL: "https://api.snapcraft.io/download/partial_snap.snap",
|
||||
},
|
||||
setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) {
|
||||
mockGetter.On("GetFile", mock.AnythingOfType("string"), "https://api.snapcraft.io/download/partial_snap.snap", mock.Anything).Run(func(args mock.Arguments) {
|
||||
// simulate a transfer that writes some bytes before the connection drops
|
||||
require.NoError(t, createMockSquashfsFile(fs, args.String(0)))
|
||||
}).Return(fmt.Errorf("connection reset by peer"))
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "failed to download snap file",
|
||||
},
|
||||
{
|
||||
// isSquashFSFile trusts the .snap extension, so non-squashfs content under that name is accepted
|
||||
name: "downloaded file has a snap extension but non-snap content",
|
||||
cfg: Config{
|
||||
DigestAlgorithms: []crypto.Hash{crypto.SHA256},
|
||||
},
|
||||
info: &remoteSnap{
|
||||
snapIdentity: snapIdentity{
|
||||
Name: "not-a-snap",
|
||||
Channel: "stable",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
URL: "https://api.snapcraft.io/download/not_a_snap.snap",
|
||||
},
|
||||
setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) {
|
||||
mockGetter.On("GetFile", mock.AnythingOfType("string"), "https://api.snapcraft.io/download/not_a_snap.snap", mock.Anything).Run(func(args mock.Arguments) {
|
||||
require.NoError(t, afero.WriteFile(fs, args.String(0), []byte("not squashfs"), 0644))
|
||||
}).Return(nil)
|
||||
},
|
||||
expectError: false,
|
||||
validate: func(t *testing.T, result *snapFile, fs afero.Fs) {
|
||||
assert.NotNil(t, result)
|
||||
assert.Contains(t, result.Path, "not_a_snap.snap")
|
||||
assert.NotNil(t, result.Cleanup)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "downloaded file is not a snap",
|
||||
cfg: Config{
|
||||
DigestAlgorithms: []crypto.Hash{crypto.SHA256},
|
||||
},
|
||||
info: &remoteSnap{
|
||||
snapIdentity: snapIdentity{
|
||||
Name: "not-a-snap",
|
||||
Channel: "stable",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
URL: "https://api.snapcraft.io/download/not_a_snap.tar.gz",
|
||||
},
|
||||
setupMock: func(mockGetter *mockFileGetter, fs afero.Fs) {
|
||||
mockGetter.On("GetFile", mock.AnythingOfType("string"), "https://api.snapcraft.io/download/not_a_snap.tar.gz", mock.Anything).Run(func(args mock.Arguments) {
|
||||
require.NoError(t, afero.WriteFile(fs, args.String(0), []byte("not squashfs"), 0644))
|
||||
}).Return(nil)
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "not a valid squashfs/snap file",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -334,8 +407,18 @@ func TestNewSnapFileFromRemote(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), tt.errorMsg)
|
||||
}
|
||||
assert.Nil(t, result)
|
||||
|
||||
// the temp directory and any partially downloaded payload must not be left behind. The
|
||||
// download destination handed to the getter lives directly under the temp directory.
|
||||
require.Len(t, mockGetter.Calls, 1)
|
||||
tempDir := filepath.Dir(mockGetter.Calls[0].Arguments.String(0))
|
||||
_, statErr := fs.Stat(tempDir)
|
||||
assert.True(t, os.IsNotExist(statErr), "temp directory %q was not cleaned up after failure", tempDir)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
if result.Cleanup != nil {
|
||||
t.Cleanup(func() { _ = result.Cleanup() })
|
||||
}
|
||||
if tt.validate != nil {
|
||||
tt.validate(t, result, fs)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user