mirror of
https://github.com/anchore/syft.git
synced 2026-08-22 01:53:35 +02:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf82010f3c | ||
|
|
7ca1f22395 | ||
|
|
93cf89363b | ||
|
|
34ef7dcbfe | ||
|
|
29edf90038 | ||
|
|
766907e6ed | ||
|
|
e820f2d21a | ||
|
|
39c9c690df | ||
|
|
c4597a08a2 | ||
|
|
236755a6b1 | ||
|
|
168eb64235 | ||
|
|
490732aa58 | ||
|
|
fd4796b0d8 | ||
|
|
78f39ee3a5 | ||
|
|
5c6cf08a8c | ||
|
|
8bb3b5eed1 | ||
|
|
21cca30e5b | ||
|
|
ed76e96749 | ||
|
|
ab508169e6 | ||
|
|
360dbc04aa | ||
|
|
58a033f924 | ||
|
|
04a6fa1b41 | ||
|
|
d63c774fa9 | ||
|
|
3eee102f63 | ||
|
|
20cc3df387 | ||
|
|
4782a94029 | ||
|
|
2293641e3b | ||
|
|
9dd9ce0bdd | ||
|
|
949ac70369 | ||
|
|
07fb23487f | ||
|
|
f45586b457 | ||
|
|
0fe98ed1f2 | ||
|
|
5f67883fe8 | ||
|
|
d288dd67ae | ||
|
|
da745b13e8 | ||
|
|
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 |
@ -16,7 +16,7 @@ tools:
|
|||||||
# used to upload test fixture cache
|
# used to upload test fixture cache
|
||||||
- name: oras
|
- name: oras
|
||||||
version:
|
version:
|
||||||
want: v1.3.2
|
want: v1.3.3
|
||||||
method: github-release
|
method: github-release
|
||||||
with:
|
with:
|
||||||
repo: oras-project/oras
|
repo: oras-project/oras
|
||||||
|
|||||||
13
.github/workflows/release.yaml
vendored
13
.github/workflows/release.yaml
vendored
@ -32,7 +32,7 @@ jobs:
|
|||||||
if: ${{ github.event.inputs.phase == 'all' }}
|
if: ${{ github.event.inputs.phase == 'all' }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # required for fetching tags
|
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:
|
with:
|
||||||
version: ${{ github.event.inputs.version }}
|
version: ${{ github.event.inputs.version }}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ jobs:
|
|||||||
packages: write # required for publishing release artifacts to GitHub packages
|
packages: write # required for publishing release artifacts to GitHub packages
|
||||||
id-token: write # required for keyless signing (cosign/sigstore OIDC)
|
id-token: write # required for keyless signing (cosign/sigstore OIDC)
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
@ -83,14 +83,19 @@ jobs:
|
|||||||
- name: Bootstrap environment
|
- name: Bootstrap environment
|
||||||
uses: ./.github/actions/bootstrap
|
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
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 #v4.4.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f #v4.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
|
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
|
||||||
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
|
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 #v4.4.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f #v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
@ -20,12 +20,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
security-events: write # for uploading SARIF results
|
security-events: write # for uploading SARIF results
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Run zizmor"
|
- name: "Run zizmor"
|
||||||
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
|
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
|
||||||
with:
|
with:
|
||||||
# there is a pass/fail gate as a repo ruleset (if there is no ruleset configured then the action will pass by default)
|
# 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
|
advanced-security: true
|
||||||
|
|||||||
14
.github/workflows/validations.yaml
vendored
14
.github/workflows/validations.yaml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ jobs:
|
|||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||||
|
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
. "github.com/anchore/go-make"
|
. "github.com/anchore/go-make"
|
||||||
|
"github.com/anchore/go-make/config"
|
||||||
"github.com/anchore/go-make/file"
|
"github.com/anchore/go-make/file"
|
||||||
"github.com/anchore/go-make/lang"
|
"github.com/anchore/go-make/lang"
|
||||||
"github.com/anchore/go-make/run"
|
"github.com/anchore/go-make/run"
|
||||||
@ -27,22 +29,43 @@ func main() {
|
|||||||
gotest.Name("unit"),
|
gotest.Name("unit"),
|
||||||
gotest.ExcludeGlob("**/test/**"),
|
gotest.ExcludeGlob("**/test/**"),
|
||||||
gotest.CoverageThreshold(62),
|
gotest.CoverageThreshold(62),
|
||||||
|
race(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// integration tests: native go-make Task. The race-detector smoke against a
|
// integration tests: run `go test` directly instead of via gotest.Tasks(), which
|
||||||
// real image stays bundled here (RunsOn integration) so `make integration`
|
// has no way to set a timeout. The suite is a single package of ~36 sequential
|
||||||
// behaves like the Taskfile version did.
|
// tests over 18 docker fixture images, so with a cold fixture cache (every image
|
||||||
gotest.Tasks(
|
// built + saved inline, under -race) it runs well past `go test`'s default 10m
|
||||||
gotest.Name("integration"),
|
// timeout -- which is exactly the case when regenerating the fixture cache from
|
||||||
gotest.IncludeGlob("./cmd/syft/internal/test/integration/..."),
|
// scratch. -count=1 keeps the go test cache from short-circuiting a run whose
|
||||||
gotest.Verbose(),
|
// side effect (the built fixtures) is the thing we're after.
|
||||||
gotest.NoCoverage(),
|
//
|
||||||
),
|
// 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{
|
Task{
|
||||||
Name: "integration:race-smoke",
|
Name: "integration:race-smoke",
|
||||||
Description: "exercise the CLI with the race detector",
|
Description: "exercise the CLI with the race detector",
|
||||||
RunsOn: lang.List("integration"),
|
RunsOn: lang.List("integration"),
|
||||||
Run: func() {
|
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")
|
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:
|
// snapshotBinPath replicates the SNAPSHOT_BIN computation from the prior Taskfile:
|
||||||
// <repoRoot>/snapshot/<os>-build_<os>_<arch>/syft, where arch maps amd64->amd64_v1
|
// <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.
|
// and arm64->arm64_v8.0 to match goreleaser's per-target output directory naming.
|
||||||
|
|||||||
49
.make/main_test.go
Normal file
49
.make/main_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/anchore/syft/actions/workflows/validations.yaml" target="_blank"><img alt="Validations" src="https://github.com/anchore/syft/actions/workflows/validations.yaml/badge.svg"></a>
|
<a href="https://github.com/anchore/syft/actions/workflows/validations.yaml" target="_blank"><img alt="Validations" src="https://github.com/anchore/syft/actions/workflows/validations.yaml/badge.svg"></a>
|
||||||
<a href="https://goreportcard.com/report/github.com/anchore/syft" target="_blank"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/anchore/syft"></a>
|
|
||||||
<a href="https://github.com/anchore/syft/releases/latest" target="_blank"><img alt="GitHub release" src="https://img.shields.io/github/release/anchore/syft.svg"></a>
|
<a href="https://github.com/anchore/syft/releases/latest" target="_blank"><img alt="GitHub release" src="https://img.shields.io/github/release/anchore/syft.svg"></a>
|
||||||
<a href="https://github.com/anchore/syft" target="_blank"><img alt="GitHub go.mod Go version" src="https://img.shields.io/github/go-mod/go-version/anchore/syft.svg"></a>
|
<a href="https://github.com/anchore/syft" target="_blank"><img alt="GitHub go.mod Go version" src="https://img.shields.io/github/go-mod/go-version/anchore/syft.svg"></a>
|
||||||
<a href="https://github.com/anchore/syft/blob/main/LICENSE" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
|
<a href="https://github.com/anchore/syft/blob/main/LICENSE" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
|
||||||
|
|||||||
@ -125,6 +125,7 @@ tasks:
|
|||||||
desc: Run tests for pipeline utils
|
desc: Run tests for pipeline utils
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: .github/scripts/labeler_test.py
|
- cmd: .github/scripts/labeler_test.py
|
||||||
|
- cmd: go test -C .make ./...
|
||||||
|
|
||||||
snapshot-smoke-test:
|
snapshot-smoke-test:
|
||||||
desc: Run a smoke test on the snapshot builds + docker images
|
desc: Run a smoke test on the snapshot builds + docker images
|
||||||
|
|||||||
@ -199,7 +199,8 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
|||||||
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
|
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
|
||||||
).
|
).
|
||||||
WithUsePackagesLib(*multiLevelOption(true, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.UsePackagesLib)).
|
WithUsePackagesLib(*multiLevelOption(true, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.UsePackagesLib)).
|
||||||
WithCaptureSymbols(cfg.Golang.CaptureSymbols),
|
WithCaptureSymbols(cfg.Golang.CaptureSymbols).
|
||||||
|
WithCaptureSymbolsModules(cfg.Golang.CaptureSymbolsModules),
|
||||||
JavaScript: javascript.DefaultCatalogerConfig().
|
JavaScript: javascript.DefaultCatalogerConfig().
|
||||||
WithIncludeDevDependencies(*multiLevelOption(false, cfg.JavaScript.IncludeDevDependencies)).
|
WithIncludeDevDependencies(*multiLevelOption(false, cfg.JavaScript.IncludeDevDependencies)).
|
||||||
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)).
|
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)).
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/anchore/clio"
|
"github.com/anchore/clio"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/cataloging"
|
"github.com/anchore/syft/syft/cataloging"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||||
)
|
)
|
||||||
@ -19,6 +20,7 @@ type golangConfig struct {
|
|||||||
MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"`
|
MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"`
|
||||||
UsePackagesLib *bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"`
|
UsePackagesLib *bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"`
|
||||||
CaptureSymbols cataloging.SymbolScope `json:"capture-symbols" yaml:"capture-symbols" mapstructure:"capture-symbols"`
|
CaptureSymbols cataloging.SymbolScope `json:"capture-symbols" yaml:"capture-symbols" mapstructure:"capture-symbols"`
|
||||||
|
CaptureSymbolsModules []string `json:"capture-symbols-modules" yaml:"capture-symbols-modules" mapstructure:"capture-symbols-modules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ interface {
|
var _ interface {
|
||||||
@ -42,8 +44,16 @@ if unset this defaults to $GONOPROXY`)
|
|||||||
always show (devel) as the version. Use these options to control heuristics to guess
|
always show (devel) as the version. Use these options to control heuristics to guess
|
||||||
a more accurate version from the binary.`)
|
a more accurate version from the binary.`)
|
||||||
descriptions.Add(&o.UsePackagesLib, `use the golang.org/x/tools/go/packages library, which executes golang tooling found on the path in addition to potential network access to get the most accurate results`)
|
descriptions.Add(&o.UsePackagesLib, `use the golang.org/x/tools/go/packages library, which executes golang tooling found on the path in addition to potential network access to get the most accurate results`)
|
||||||
|
// note: descriptions must be static string literals; the app config discovery that generates the
|
||||||
|
// capability docs reads them straight out of the AST
|
||||||
descriptions.Add(&o.CaptureSymbols, `capture function symbols from the binary symbol table (pclntab). valid values are:
|
descriptions.Add(&o.CaptureSymbols, `capture function symbols from the binary symbol table (pclntab). valid values are:
|
||||||
"none" (disabled), "stdlib" (only the synthetic stdlib package), and "all" (all module packages plus stdlib)`)
|
"none" (disabled), "stdlib" (only the synthetic stdlib package), "extended-stdlib" (stdlib plus every
|
||||||
|
module under golang.org/x/), and "all" (all module packages plus stdlib)`)
|
||||||
|
descriptions.Add(&o.CaptureSymbolsModules, `glob patterns matched against go module paths (e.g. github.com/klauspost/**) that should have symbols
|
||||||
|
captured in addition to whatever capture-symbols selects. ** crosses path separators, * does not.
|
||||||
|
a trailing major version suffix is ignored when matching, so github.com/foo/* covers github.com/foo/bar/v2;
|
||||||
|
spelling a suffix out in the pattern selects only that major version.
|
||||||
|
this can only widen the selection, never narrow it, and is inert when capture-symbols is none`)
|
||||||
descriptions.Add(&o.MainModuleVersion.FromLDFlags, `look for LD flags that appear to be setting a version (e.g. -X main.version=1.0.0)`)
|
descriptions.Add(&o.MainModuleVersion.FromLDFlags, `look for LD flags that appear to be setting a version (e.g. -X main.version=1.0.0)`)
|
||||||
descriptions.Add(&o.MainModuleVersion.FromBuildSettings, `use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
|
descriptions.Add(&o.MainModuleVersion.FromBuildSettings, `use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
|
||||||
(e.g. v0.0.0-20220308212642-53e6d0aaf6fb) when a more accurate version cannot be found otherwise`)
|
(e.g. v0.0.0-20220308212642-53e6d0aaf6fb) when a more accurate version cannot be found otherwise`)
|
||||||
@ -51,7 +61,24 @@ a more accurate version from the binary.`)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *golangConfig) PostLoad() error {
|
func (o *golangConfig) PostLoad() error {
|
||||||
|
raw := strings.TrimSpace(string(o.CaptureSymbols))
|
||||||
o.CaptureSymbols = o.CaptureSymbols.Parse()
|
o.CaptureSymbols = o.CaptureSymbols.Parse()
|
||||||
|
|
||||||
|
// an unrecognized value still resolves to "none", but say so rather than silently capturing nothing.
|
||||||
|
// stay quiet for unset and an explicit "none", which is the default and would otherwise warn on nearly
|
||||||
|
// every scan.
|
||||||
|
if o.CaptureSymbols == cataloging.SymbolScopeNone && raw != "" && !strings.EqualFold(raw, string(cataloging.SymbolScopeNone)) {
|
||||||
|
log.Warnf("unknown golang.capture-symbols value %q, defaulting to %q (valid values: none, stdlib, extended-stdlib, all)", raw, cataloging.SymbolScopeNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim only, deliberately not Flatten: viper already splits a comma-separated scalar (env var or a bare
|
||||||
|
// yaml string) into a slice before this point, and there is no CLI flag feeding this key. The one thing
|
||||||
|
// Flatten would add is splitting commas *inside* a list entry, which silently breaks doublestar brace
|
||||||
|
// alternation like github.com/{foo,bar}/**. Viper's split does not trim, so that part is still needed.
|
||||||
|
for i, pattern := range o.CaptureSymbolsModules {
|
||||||
|
o.CaptureSymbolsModules[i] = strings.TrimSpace(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,5 +105,6 @@ func defaultGolangConfig() golangConfig {
|
|||||||
},
|
},
|
||||||
UsePackagesLib: nil, // this defaults to true, which is the API default
|
UsePackagesLib: nil, // this defaults to true, which is the API default
|
||||||
CaptureSymbols: def.CaptureSymbols,
|
CaptureSymbols: def.CaptureSymbols,
|
||||||
|
CaptureSymbolsModules: def.CaptureSymbolsModules,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ func Test_golangConfig_PostLoad(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
cfg golangConfig
|
cfg golangConfig
|
||||||
expected cataloging.SymbolScope
|
expected cataloging.SymbolScope
|
||||||
|
expectedModules []string
|
||||||
wantErr assert.ErrorAssertionFunc
|
wantErr assert.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -25,6 +26,32 @@ func Test_golangConfig_PostLoad(t *testing.T) {
|
|||||||
cfg: golangConfig{CaptureSymbols: "stdlib"},
|
cfg: golangConfig{CaptureSymbols: "stdlib"},
|
||||||
expected: cataloging.SymbolScopeStdlib,
|
expected: cataloging.SymbolScopeStdlib,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "normalize extended-stdlib",
|
||||||
|
cfg: golangConfig{CaptureSymbols: " Extended-Stdlib "},
|
||||||
|
expected: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "module patterns keep embedded commas",
|
||||||
|
cfg: golangConfig{
|
||||||
|
CaptureSymbols: "stdlib",
|
||||||
|
// brace alternation contains a comma; splitting on it would corrupt the pattern
|
||||||
|
CaptureSymbolsModules: []string{"github.com/{foo,bar}/**", "golang.org/x/**"},
|
||||||
|
},
|
||||||
|
expected: cataloging.SymbolScopeStdlib,
|
||||||
|
expectedModules: []string{"github.com/{foo,bar}/**", "golang.org/x/**"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// viper splits a comma-separated scalar (env var or bare yaml string) but does not trim,
|
||||||
|
// so a leading space would otherwise survive into a pattern that silently matches nothing
|
||||||
|
name: "module patterns are trimmed",
|
||||||
|
cfg: golangConfig{
|
||||||
|
CaptureSymbols: "stdlib",
|
||||||
|
CaptureSymbolsModules: []string{"golang.org/x/**", " github.com/foo/** "},
|
||||||
|
},
|
||||||
|
expected: cataloging.SymbolScopeStdlib,
|
||||||
|
expectedModules: []string{"golang.org/x/**", "github.com/foo/**"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "empty defaults to none",
|
name: "empty defaults to none",
|
||||||
cfg: golangConfig{CaptureSymbols: ""},
|
cfg: golangConfig{CaptureSymbols: ""},
|
||||||
@ -32,7 +59,7 @@ func Test_golangConfig_PostLoad(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid value defaults to none",
|
name: "invalid value defaults to none",
|
||||||
cfg: golangConfig{CaptureSymbols: "bogus"},
|
cfg: golangConfig{CaptureSymbols: "stdlbi"},
|
||||||
expected: cataloging.SymbolScopeNone,
|
expected: cataloging.SymbolScopeNone,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,6 +67,16 @@ func Test_golangConfig_PostLoad(t *testing.T) {
|
|||||||
cfg: golangConfig{CaptureSymbols: "true"},
|
cfg: golangConfig{CaptureSymbols: "true"},
|
||||||
expected: cataloging.SymbolScopeNone,
|
expected: cataloging.SymbolScopeNone,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "explicit none resolves to none",
|
||||||
|
cfg: golangConfig{CaptureSymbols: "none"},
|
||||||
|
expected: cataloging.SymbolScopeNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit none is not case sensitive",
|
||||||
|
cfg: golangConfig{CaptureSymbols: " NONE "},
|
||||||
|
expected: cataloging.SymbolScopeNone,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -52,6 +89,7 @@ func Test_golangConfig_PostLoad(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.Equal(t, tt.expected, tt.cfg.CaptureSymbols)
|
assert.Equal(t, tt.expected, tt.cfg.CaptureSymbols)
|
||||||
|
assert.Equal(t, tt.expectedModules, tt.cfg.CaptureSymbolsModules)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
145
cmd/syft/internal/test/integration/multiplatform_oci_test.go
Normal file
145
cmd/syft/internal/test/integration/multiplatform_oci_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
@ -8,3 +8,5 @@
|
|||||||
|
|
||||||
**/go.sum
|
**/go.sum
|
||||||
!image-go-bin-arch-coverage/go.sum
|
!image-go-bin-arch-coverage/go.sum
|
||||||
|
|
||||||
|
cache/
|
||||||
165
go.mod
165
go.mod
@ -18,11 +18,11 @@ require (
|
|||||||
github.com/anchore/go-homedir v0.1.1
|
github.com/anchore/go-homedir v0.1.1
|
||||||
github.com/anchore/go-logger v0.1.1
|
github.com/anchore/go-logger v0.1.1
|
||||||
github.com/anchore/go-macholibre 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-sync v0.1.1
|
||||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||||
github.com/anchore/packageurl-go v0.2.0
|
github.com/anchore/packageurl-go v0.2.0
|
||||||
github.com/anchore/stereoscope v0.2.2
|
github.com/anchore/stereoscope v0.3.1-0.20260820190420-3cd14ecb9fbf
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||||
github.com/aquasecurity/go-pep440-version v0.0.1
|
github.com/aquasecurity/go-pep440-version v0.0.1
|
||||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
||||||
@ -34,36 +34,36 @@ require (
|
|||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/dave/jennifer v1.7.1
|
github.com/dave/jennifer v1.7.1
|
||||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
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/distribution/reference v0.6.0
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/elliotchance/phpserialize v1.4.0
|
github.com/elliotchance/phpserialize v1.4.0
|
||||||
github.com/facebookincubator/nvdtools v0.1.5
|
github.com/facebookincubator/nvdtools v0.1.5
|
||||||
github.com/github/go-spdx/v2 v2.7.0
|
github.com/github/go-spdx/v2 v2.7.0
|
||||||
github.com/gkampitakis/go-snaps v0.5.22
|
github.com/gkampitakis/go-snaps v0.5.23
|
||||||
github.com/go-git/go-billy/v5 v5.9.0
|
github.com/go-git/go-billy/v5 v5.9.1
|
||||||
github.com/go-git/go-git/v5 v5.19.1
|
github.com/go-git/go-git/v5 v5.19.2
|
||||||
github.com/go-test/deep v1.1.1
|
github.com/go-test/deep v1.1.1
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||||
github.com/goccy/go-yaml v1.19.2
|
github.com/goccy/go-yaml v1.19.2
|
||||||
github.com/gohugoio/hashstructure v0.6.0
|
github.com/gohugoio/hashstructure v0.6.0
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/go-containerregistry v0.21.7
|
github.com/google/go-containerregistry v0.21.9
|
||||||
github.com/google/licensecheck v0.3.1
|
github.com/google/licensecheck v0.3.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gookit/color v1.6.1
|
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-cleanhttp v0.5.2
|
||||||
github.com/hashicorp/go-getter v1.8.6
|
github.com/hashicorp/go-getter v1.8.8
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/hashicorp/hcl/v2 v2.24.0
|
github.com/hashicorp/hcl/v2 v2.24.0
|
||||||
github.com/iancoleman/strcase v0.3.0
|
github.com/iancoleman/strcase v0.3.0
|
||||||
github.com/invopop/jsonschema v0.14.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/jinzhu/copier v0.4.0
|
||||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||||
github.com/klauspost/compress v1.19.0
|
github.com/klauspost/compress v1.19.2
|
||||||
github.com/magiconair/properties v1.8.10
|
github.com/magiconair/properties v1.18.11
|
||||||
github.com/mholt/archives v0.1.5
|
github.com/mholt/archives v0.1.5
|
||||||
github.com/moby/sys/mountinfo v0.7.2
|
github.com/moby/sys/mountinfo v0.7.2
|
||||||
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
|
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
|
||||||
@ -82,8 +82,8 @@ require (
|
|||||||
github.com/spdx/tools-golang v0.6.0-rc4
|
github.com/spdx/tools-golang v0.6.0-rc4
|
||||||
github.com/spf13/afero v1.15.0
|
github.com/spf13/afero v1.15.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.12.0
|
||||||
github.com/ulikunitz/xz v0.5.15
|
github.com/ulikunitz/xz v0.5.16
|
||||||
github.com/vbatts/go-mtree v0.7.0
|
github.com/vbatts/go-mtree v0.7.0
|
||||||
github.com/vifraa/gopom v1.0.0
|
github.com/vifraa/gopom v1.0.0
|
||||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
|
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
|
||||||
@ -91,14 +91,14 @@ require (
|
|||||||
github.com/xeipuuv/gojsonschema v1.2.0
|
github.com/xeipuuv/gojsonschema v1.2.0
|
||||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||||
go.uber.org/goleak v1.3.0
|
go.uber.org/goleak v1.3.0
|
||||||
go.yaml.in/yaml/v3 v3.0.4
|
go.yaml.in/yaml/v3 v3.0.5
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
||||||
golang.org/x/mod v0.37.0
|
golang.org/x/mod v0.40.0
|
||||||
golang.org/x/net v0.56.0
|
golang.org/x/net v0.58.0
|
||||||
golang.org/x/time v0.15.0
|
golang.org/x/time v0.15.0
|
||||||
golang.org/x/tools v0.47.0
|
golang.org/x/tools v0.49.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
modernc.org/sqlite v1.53.0
|
modernc.org/sqlite v1.56.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -108,19 +108,19 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cel.dev/expr v0.25.1 // indirect
|
cel.dev/expr v0.25.2 // indirect
|
||||||
cloud.google.com/go v0.123.0 // indirect
|
cloud.google.com/go v0.123.0 // indirect
|
||||||
cloud.google.com/go/auth v0.18.2 // indirect
|
cloud.google.com/go/auth v0.22.0 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||||
cloud.google.com/go/iam v1.5.3 // indirect
|
cloud.google.com/go/iam v1.11.0 // indirect
|
||||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
cloud.google.com/go/monitoring v1.29.0 // indirect
|
||||||
cloud.google.com/go/storage v1.61.3 // indirect
|
cloud.google.com/go/storage v1.64.0 // indirect
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
github.com/DataDog/zstd v1.5.5 // 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.33.0 // indirect
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect
|
github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect
|
||||||
github.com/Microsoft/hcsshim v0.15.0-rc.1 // indirect
|
github.com/Microsoft/hcsshim v0.15.0-rc.1 // indirect
|
||||||
@ -133,25 +133,24 @@ require (
|
|||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/aquasecurity/go-version v0.0.1 // indirect
|
github.com/aquasecurity/go-version v0.0.1 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.43.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect
|
github.com/aws/aws-sdk-go-v2/config v1.32.35 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.34 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect
|
github.com/aws/aws-sdk-go-v2/service/signin v1.5.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.33.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.45.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
|
github.com/aws/smithy-go v1.27.6 // indirect
|
||||||
github.com/aws/smithy-go v1.24.2 // indirect
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||||
@ -168,10 +167,10 @@ require (
|
|||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.3 // 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/cgroups/v3 v3.1.3 // indirect
|
||||||
github.com/containerd/containerd/api v1.11.1 // 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/continuity v0.5.0 // indirect
|
||||||
github.com/containerd/errdefs v1.0.0 // indirect
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
@ -182,21 +181,20 @@ require (
|
|||||||
github.com/containerd/ttrpc v1.2.8 // indirect
|
github.com/containerd/ttrpc v1.2.8 // indirect
|
||||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1 // 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.7.2+incompatible // indirect
|
||||||
github.com/docker/cli v29.5.3+incompatible // indirect
|
|
||||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||||
github.com/docker/go-connections v0.7.0 // indirect
|
github.com/docker/go-connections v0.8.1 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
github.com/fatih/color v1.19.0 // indirect
|
||||||
github.com/felixge/fgprof v0.9.5 // indirect
|
github.com/felixge/fgprof v0.9.5 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
||||||
github.com/gkampitakis/ciinfo v0.3.4 // indirect
|
github.com/gkampitakis/ciinfo v0.3.4 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||||
@ -205,13 +203,13 @@ require (
|
|||||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect
|
||||||
github.com/google/s2a-go v0.1.9 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.19 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.23.0 // indirect
|
||||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect
|
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
github.com/hashicorp/go-version v1.9.0 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/henvic/httpretty v0.1.4 // indirect
|
github.com/henvic/httpretty v0.1.4 // indirect
|
||||||
github.com/huandu/xstrings v1.5.0 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
@ -226,7 +224,7 @@ require (
|
|||||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
github.com/maruel/natural v1.3.0 // indirect
|
github.com/maruel/natural v1.3.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
|
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.21 // indirect
|
github.com/mattn/go-runewidth v0.0.21 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
@ -238,8 +236,8 @@ require (
|
|||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/locker v1.0.1 // indirect
|
github.com/moby/locker v1.0.1 // indirect
|
||||||
github.com/moby/moby/api v1.54.2 // indirect
|
github.com/moby/moby/api v1.55.0 // indirect
|
||||||
github.com/moby/moby/client v0.4.1 // indirect
|
github.com/moby/moby/client v0.5.1 // indirect
|
||||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
github.com/moby/sys/signal v0.7.1 // indirect
|
github.com/moby/sys/signal v0.7.1 // indirect
|
||||||
github.com/moby/sys/user v0.4.0 // indirect
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
@ -257,7 +255,7 @@ require (
|
|||||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/opencontainers/runtime-spec v1.3.0 // indirect
|
github.com/opencontainers/runtime-spec v1.3.0 // indirect
|
||||||
github.com/pborman/indent v1.2.1 // 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/pierrec/lz4/v4 v4.1.26 // indirect
|
||||||
github.com/piprate/json-gold v0.7.0 // indirect
|
github.com/piprate/json-gold v0.7.0 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||||
@ -265,7 +263,6 @@ require (
|
|||||||
github.com/pkg/profile v1.7.0 // indirect
|
github.com/pkg/profile v1.7.0 // indirect
|
||||||
github.com/pkg/xattr v0.4.12 // indirect
|
github.com/pkg/xattr v0.4.12 // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
@ -281,8 +278,8 @@ require (
|
|||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/spf13/viper v1.21.0 // indirect
|
github.com/spf13/viper v1.21.0 // indirect
|
||||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.3 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/sylabs/sif/v2 v2.24.1 // indirect
|
github.com/sylabs/sif/v2 v2.24.1 // indirect
|
||||||
github.com/sylabs/squashfs v1.0.6 // indirect
|
github.com/sylabs/squashfs v1.0.6 // indirect
|
||||||
@ -299,32 +296,32 @@ require (
|
|||||||
github.com/zclconf/go-cty v1.16.3 // indirect
|
github.com/zclconf/go-cty v1.16.3 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // 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.44.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.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/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
|
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
|
||||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||||
golang.org/x/crypto v0.53.0 // indirect
|
golang.org/x/crypto v0.55.0 // indirect
|
||||||
golang.org/x/oauth2 v0.36.0 // indirect
|
golang.org/x/oauth2 v0.36.0 // indirect
|
||||||
golang.org/x/sync v0.21.0 // indirect
|
golang.org/x/sync v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
golang.org/x/term v0.44.0 // indirect
|
golang.org/x/term v0.45.0 // indirect
|
||||||
golang.org/x/text v0.38.0 // indirect
|
golang.org/x/text v0.41.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||||
gonum.org/v1/gonum v0.17.0 // indirect
|
gonum.org/v1/gonum v0.17.0 // indirect
|
||||||
google.golang.org/api v0.271.0 // indirect
|
google.golang.org/api v0.292.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
google.golang.org/genproto v0.0.0-20260519071638-aa98bba5eb94 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect
|
||||||
google.golang.org/grpc v1.80.0 // indirect
|
google.golang.org/grpc v1.83.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
modernc.org/libc v1.73.4 // indirect
|
modernc.org/libc v1.74.4 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
359
go.sum
359
go.sum
@ -1,5 +1,5 @@
|
|||||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
|
||||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
@ -31,8 +31,8 @@ cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0c
|
|||||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
cloud.google.com/go/auth v0.22.0 h1:Xp9wAKkLoeaYb5pYZZoQGz4E9sdPxIbzS3gywZE3ciQ=
|
||||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
cloud.google.com/go/auth v0.22.0/go.mod h1:M9o2Oz+YI2jAfxewJgb1vyI3vceHF+eohmxyzmrl+9s=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
@ -46,14 +46,14 @@ cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCB
|
|||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM=
|
||||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4=
|
||||||
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
|
cloud.google.com/go/logging v1.18.0 h1:KhzZq+1cSkPH9YUaKLLhLtQxIHitVayBmk0sGfoM9+k=
|
||||||
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
|
cloud.google.com/go/logging v1.18.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI=
|
||||||
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
|
cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM=
|
||||||
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
|
cloud.google.com/go/longrunning v1.2.0/go.mod h1:5KMQALFGOCtFoi2xSOA1u3H7WKlhmckgiyFw7+LGQp0=
|
||||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
cloud.google.com/go/monitoring v1.29.0 h1:AHhDsFaSax1/4k+qlIDX/SDGe6hggnfXJ9dkgD9qBPY=
|
||||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
cloud.google.com/go/monitoring v1.29.0/go.mod h1:72NOVjJXHY/HBfoLT0+qlCZBT059+9VXLeAnL2PeeVM=
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
@ -63,10 +63,10 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=
|
cloud.google.com/go/storage v1.64.0 h1:KLpxI/oX9LxeRsNqn877d2WyeT3ryiEwnGt8pwcSPZg=
|
||||||
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk=
|
cloud.google.com/go/storage v1.64.0/go.mod h1:lWyAtwvDZHdL3k68WVKbESP6bmWaV23ZJJ/JEVw/ZaQ=
|
||||||
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
|
cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E=
|
||||||
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
|
cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0=
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
@ -82,14 +82,14 @@ 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/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 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
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.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
|
||||||
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.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
|
||||||
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.57.0 h1:jLdiS1vO+XJFyDSWRHBx56r4s/NNtcl5J6KyCcWUX/w=
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0/go.mod h1:8lmpHY+1VRoteiOwyrQMDt1YGXOrFKCz+1wJW7n3ODY=
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0 h1:cSjUzZ7KU8hicTgzaSv9NmSyM9fTVK3y5lsBUl3wOis=
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0/go.mod h1:dzcEjy1WJ0Q4u9twNR3LcLhNoYMRCrMCMafpxa0TjPQ=
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 h1:RoO5+d7uCmDqovLrHCr2/BuViUXvdcrNxyNM1pN9dDQ=
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
||||||
@ -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-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 h1:sTBHi3S1WVIOCamc6NyT8Sn9mBmD30DCTfjfVm4k6/M=
|
||||||
github.com/anchore/go-macholibre v0.1.1/go.mod h1:YNq2610RlvGCK0Za+Klz/OCMtUaEnwwAGA/VfUAQaro=
|
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.2.0 h1:ixcyMg/guhBL9XjBVwhTv9CG48xO07Qkg2uDX2+zx/c=
|
||||||
github.com/anchore/go-rpmdb v0.1.0/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
|
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 h1:q+859fW2/jbHJHB2etbNfRlFwYpknyvbqqk1hUdamQ4=
|
||||||
github.com/anchore/go-struct-converter v0.2.0-rc2/go.mod h1:cDBA5vhcR62nXWo8QH9/Kk2807o65ISaHPNPX66L+Uw=
|
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=
|
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/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 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk=
|
||||||
github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE=
|
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.3.1-0.20260820190420-3cd14ecb9fbf h1:eTQTL06tadOz+V4ubhXOlc5oKQ11cXbyo/clnxlcsnU=
|
||||||
github.com/anchore/stereoscope v0.2.2/go.mod h1:ylJXJKebLctP7u9ewguB1d0zUETJxvAA7r2DiuljDTM=
|
github.com/anchore/stereoscope v0.3.1-0.20260820190420-3cd14ecb9fbf/go.mod h1:cWQ8fLFblnkJ0c3BSFY+BwXe4DcJDdysP/gnoWHVb7s=
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
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 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
@ -169,44 +169,42 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
|||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
|
github.com/aws/aws-sdk-go-v2 v1.43.4 h1:b9FTvbRwy+JCsfp2Wp6wV/KbOx3Aj7nkoFb2cRX0IhE=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
github.com/aws/aws-sdk-go-v2 v1.43.4/go.mod h1:70vwSy16txshwG+g55WkpgPKDIByzHI8ccBsOteo3bQ=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 h1:aiuaKlDweRC5qExJondpWjOgyzMHpofpwspGXUtwn4c=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16/go.mod h1:nG/LOlmox9BDe9HvQnXWzgcK8uKbgBMZ/Hp5pVt/21I=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0=
|
github.com/aws/aws-sdk-go-v2/config v1.32.35 h1:UEzXuET8E42lxBPijuACu/tEK7v5lFPlk0Q+GT5WD9E=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g=
|
github.com/aws/aws-sdk-go-v2/config v1.32.35/go.mod h1:KaMtJpFa2JlL2BStjjHQVwQpzZEmw+ND/EgVrfFoo2g=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.34 h1:y6GkSmcv5myd1ngrYbGmiLlwQqB6TQhOuN/tbSSuWDY=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.34/go.mod h1:w3dTcnDVoQIewjo7JG45hduAToikiIFLC4FIO7fndvw=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35 h1:+S7kbJoLDDQ5tE+lHrUBgMkzC8NLgsaioS2F3dVoFAE=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35/go.mod h1:Ak7xXviIARfFdNUJ9Etb0bdVDt/KAvKjMGJVLWXDzik=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35 h1:kzVuGlatQtYinwBJEEyLAbggepCoavosiaHHX9+fD+c=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35/go.mod h1:0yLx0yEI+SfqeJMPvOtIEFoZbiQYXMGszBueiutQyaI=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35 h1:WK6CjihTuLisCjSKKbildJ79sGZZgbBz3iNa7VsKIhU=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35/go.mod h1:KYleN57luLoe97R7vTnx8PMcVrr9gAcRECtOjl91DNg=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36 h1:jbGY4CXLzZElOXgGsexlC3Hi+3YM0rSmk4opFXKqg/k=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36/go.mod h1:uBu/9aKsS/UQGc72RAt3y54kjgYQxmhut8ZD2dXCDNE=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 h1:JJLBQxwY+AFwuPAi5ivGc1ChnTdUt4cXMv7e76m2c/Y=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15/go.mod h1:lQknBIe78MVL0cQOQDlag8KGflMbMEVFx9mB6O8ENvk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28 h1:Q1TF1J9jVD+vFo0LzNnmNdQ9EAt52TS+MQlq9Ir+Yxo=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28/go.mod h1:4KqXXC/p1hrotmouDFbrRoWaLy962b9PMUReCG6+uWo=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 h1:BBEElKh4a+rKshvjrfpajTe9CbpZvrbb4Jkg2PB7RzA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35/go.mod h1:zaZk983w//8beSruBVec/mr4CmDwgZitW/qzGhAAX0g=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36 h1:EUIwBoN+q7UmhAejxgD27APiRjh1vwCFo53gSqdT0BM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36/go.mod h1:6u00gmlTGR6W0b2k9NBrld7MnOEmf1Spqx0VVt6AqyE=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.5 h1:HpN6GgZ3T8pSvRp81ZsgumNjlvRsa+9M0ZL2o6W4uLY=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.5/go.mod h1:5FTZoQxhmLEiCAtYVk6V+t0iS/B5yGZVLZ3Wq5FDJZI=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
|
github.com/aws/aws-sdk-go-v2/service/signin v1.5.4 h1:cOJELVNrq5Q3Udry2GLuHUM7MhwpeaQRdYaoa6GI/yI=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
|
github.com/aws/aws-sdk-go-v2/service/signin v1.5.4/go.mod h1:f4LxzKBtaTxD7xh3PiVg3CE1tchQemfmghaJr+NbK2c=
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.33.4 h1:AMW7a7S8iQaHjBYZdU3PCq4GKRPijTPRAc7e6XtEThY=
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.33.4/go.mod h1:QQNsFV1DVXoXcZt18FS8lI8rtUrlDyAuWZLQ5shunv4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4 h1:AsbZcJAQPRmHDJG8K1N0pof/1zPWjVT8TFlTWuGLSvo=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4/go.mod h1:6imqztH0//t0mKbl6yWl7swSEl7F/w32oAmqB3vP1ag=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.45.4 h1:w/AryDYMjSUANSQ2uoZxJovUsMTwWJNTv3IMex30Y+4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.45.4/go.mod h1:WeBiAa67azG7Su9Vf+ChGDBLiAozJCXzdjXiPBUwtbc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU=
|
github.com/aws/smithy-go v1.27.6 h1:0zjT8jgK3jbrTT7JJ3EE6JsMhX8JTrZ+f1sEndYDXrA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk=
|
github.com/aws/smithy-go v1.27.6/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
|
||||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||||
@ -290,14 +288,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-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-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-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-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||||
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/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||||
github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ=
|
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/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 h1:h8nfoDW9+fNsC/9TwiAHj8B1GzXKtR4eFtkhi/X5RLU=
|
||||||
github.com/containerd/containerd/api v1.11.1/go.mod h1:CaQFRu+N1MtbgL6JDOJLUB1hCKESU1lD6MuTJhgtdlw=
|
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.3 h1:MUNBVVBTBpPll7KPh5GTvkC3cfG03PQLAHVdsUoue9k=
|
||||||
github.com/containerd/containerd/v2 v2.3.2/go.mod h1:rHKGm3VW6wNrINb3x8mNT+w7qYXFVElTt/8HTuxVhD4=
|
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 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg=
|
||||||
github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
@ -333,18 +331,18 @@ 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 h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
|
||||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
|
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/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.4 h1:0j2d7eG4IjyxL6+ChWbDPocdBCF6HQ4HBWU2WDYWVnc=
|
||||||
github.com/diskfs/go-diskfs v1.9.3/go.mod h1:TePJORO83Adh5pb2SqsxAwaP0fofFxKLkxctiS/9OQc=
|
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 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
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 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
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.7.2+incompatible h1:dlkwallR8XqfeVnA2ELEhdwvb4lsSwuB4IgsG8Q9cLY=
|
||||||
github.com/docker/cli v29.5.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v29.7.2+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 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||||
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
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=
|
github.com/docker/go-connections v0.8.1 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M=
|
||||||
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||||
@ -371,14 +369,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.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 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
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.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||||
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/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 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
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.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 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.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
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 h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
github.com/erofs/go-erofs v0.3.0 h1:o/W5ABAA3sHYl97WL93dacKEfeDpJhdFf3c2snAti7I=
|
github.com/erofs/go-erofs v0.3.0 h1:o/W5ABAA3sHYl97WL93dacKEfeDpJhdFf3c2snAti7I=
|
||||||
@ -389,8 +387,8 @@ github.com/facebookincubator/nvdtools v0.1.5/go.mod h1:Kh55SAWnjckS96TBSrXI99KrE
|
|||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||||
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
||||||
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
||||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||||
@ -403,27 +401,27 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
|||||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/github/go-spdx/v2 v2.7.0 h1:GzfXx4wFdlilARxmFRXW/mgUy3A4vSqZocCMFV6XFdQ=
|
github.com/github/go-spdx/v2 v2.7.0 h1:GzfXx4wFdlilARxmFRXW/mgUy3A4vSqZocCMFV6XFdQ=
|
||||||
github.com/github/go-spdx/v2 v2.7.0/go.mod h1:Ftc45YYG1WzpzwEPKRVm9Jv8vDqOrN4gWoCkK+bHer0=
|
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 h1:5eBSibVuSMbb/H6Elc0IIEFbkzCJi3lm94n0+U7Z0KY=
|
||||||
github.com/gkampitakis/ciinfo v0.3.4/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
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.23 h1:okh5QR48zpUjpWtu65AtqxdCY8huJq+dEDuUzd1PuKg=
|
||||||
github.com/gkampitakis/go-snaps v0.5.22/go.mod h1:uy3lVzCCRRsAwYqSocyw5fY8xRLCYEfqoOJNxr8HonM=
|
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 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||||
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
|
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 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
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 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
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.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
|
||||||
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/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 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-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=
|
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
|
||||||
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@ -509,8 +507,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU=
|
github.com/google/go-containerregistry v0.21.9 h1:F+D4uZ3iA3DLMJLfhaqMdHJbzeqm/216WGQq2dokuLs=
|
||||||
github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE=
|
github.com/google/go-containerregistry v0.21.9/go.mod h1:dP5XNKcL7kMFF/TB3LfvWmVhAcv7iqkHb3oDK8aauTo=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
|
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
|
||||||
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
|
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
|
||||||
@ -537,32 +535,32 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
|
|||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.19 h1:mMOE7DN2+p76/EdIrmAy9B9bH+yC4563vmnJ34QR8i4=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.19/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=
|
||||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg=
|
||||||
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
||||||
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
|
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
|
||||||
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
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 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
|
||||||
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
|
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.25.0 h1:1AMBhMKtI24nTtn588Bq53FqNiOvEw1x9Nb4HbRrThs=
|
||||||
github.com/gpustack/gguf-parser-go v0.24.1/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
|
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/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.74 h1:mymLUKThnV9wFvogOK8NnsMP9/vlhnjXY98gr2QIGW8=
|
||||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc=
|
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74/go.mod h1:Bh9qYL8ehmDxSg14Tk8oxFCP90XHfs6NxV1D84884xA=
|
||||||
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@ -572,8 +570,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
|||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
github.com/hashicorp/go-getter v1.8.6 h1:9sQboWULaydVphxc4S64oAI4YqpuCk7nPmvbk131ebY=
|
github.com/hashicorp/go-getter v1.8.8 h1:sRakhf+EH6s0LZLO2IgBwZ6hAFp+fZOZMNREwVELV80=
|
||||||
github.com/hashicorp/go-getter v1.8.6/go.mod h1:nVH12eOV2P58dIiL3rsU6Fh3wLeJEKBOJzhMmzlSWoo=
|
github.com/hashicorp/go-getter v1.8.8/go.mod h1:fqFlibKpwfns/s4oljLB3upJspyfFLQhS7031PlfUDc=
|
||||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||||
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
@ -589,8 +587,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
|||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
|
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
|
||||||
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
@ -624,8 +622,8 @@ github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8i
|
|||||||
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
|
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 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
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.3 h1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ=
|
||||||
github.com/jedib0t/go-pretty/v6 v6.8.1/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
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/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 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
@ -645,8 +643,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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
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.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
|
github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
|
||||||
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
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 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
@ -670,8 +668,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/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/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.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
github.com/magiconair/properties v1.18.11 h1:j5ozYZl0zCjG7ahMDH0GWIobOvvUzT0BdAguG0ViKy0=
|
||||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.18.11/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
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 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg=
|
||||||
github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||||
@ -688,8 +686,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
|
|||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw=
|
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw=
|
||||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
|
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
|
||||||
@ -723,10 +721,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/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 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
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.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
||||||
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||||
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
|
github.com/moby/moby/client v0.5.1 h1:tYNaJno4c0HXz12y5BiqEDy0rVTYkWzI26lGvnTMiJw=
|
||||||
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
|
github.com/moby/moby/client v0.5.1/go.mod h1:odLstlZ6uSnfvAgVxMpvgmb8SUdd+siH2T0GBuxVAlM=
|
||||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
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/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
@ -786,8 +784,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.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 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
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.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||||
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
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 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
|
||||||
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
github.com/piprate/json-gold v0.7.0 h1:bEMirgA5y8Z2loTQfxyIFfY+EflxH1CTP6r/KIlcJNw=
|
github.com/piprate/json-gold v0.7.0 h1:bEMirgA5y8Z2loTQfxyIFfY+EflxH1CTP6r/KIlcJNw=
|
||||||
@ -902,14 +900,14 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
|
|||||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
|
||||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
@ -920,8 +918,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
@ -947,8 +945,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/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/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.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.16 h1:ld6NyySjx5lowVKwJvMRLnW5nxKX/xnpSiFYZ/Lxur0=
|
||||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
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 h1:ytmOc3MTRidZiBi9VBCyZ2BHe4fZS47L5v7BVXDWW4E=
|
||||||
github.com/vbatts/go-mtree v0.7.0/go.mod h1:EjdpFC+LZy1TXbRGNa1MKKgjQ+7ew3foMFJK8o4/TdY=
|
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=
|
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
|
||||||
@ -998,32 +996,35 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
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 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
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.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
|
||||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 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/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0=
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU=
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk=
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q=
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
|
||||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||||
@ -1040,8 +1041,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-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-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.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -1081,8 +1082,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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
||||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -1127,8 +1128,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-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.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.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.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -1160,8 +1161,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-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-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.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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
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-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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -1236,13 +1237,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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
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-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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -1253,8 +1254,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.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.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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -1315,8 +1316,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.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.5/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.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.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -1357,8 +1358,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
|
|||||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
||||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
||||||
google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY=
|
google.golang.org/api v0.292.0 h1:Ewiwo/GTtiaPZSNAZQUcWLh8AYDEoPmIXyJfeoTSMHU=
|
||||||
google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q=
|
google.golang.org/api v0.292.0/go.mod h1:07kjmMnFGm2RQuCza2EZM/5N68G/fVvFb1xKjWqoFA0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@ -1429,12 +1430,12 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
|
|||||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
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-20260519071638-aa98bba5eb94 h1:YJjbgu+dkp5kUJLfpMyCLfBIWZb/FcJyuLeo1gVBOuo=
|
||||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
google.golang.org/genproto v0.0.0-20260519071638-aa98bba5eb94/go.mod h1:RRHjglSYABVCWpQ7USCpdfhcd9t4PkajvVwyynZizTc=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 h1:jQ9p21COKWjP3VwuFrNRiiOTMh3mPpN45R7SLrH/HUU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7/go.mod h1:KqHwBx2upmfa1XSi1WuRvC+2VGCLtooKkfmyvRbUmqA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@ -1462,8 +1463,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.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.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.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
|
||||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
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-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
@ -1513,20 +1514,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=
|
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 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
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.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
||||||
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||||
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
|
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 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
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 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
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.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||||
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
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 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA=
|
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
|
||||||
modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
@ -1535,8 +1536,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
|||||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
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 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M=
|
modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0=
|
||||||
modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=
|
modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|||||||
@ -15,7 +15,9 @@ application: # AUTO-GENERATED - application-level config keys
|
|||||||
- key: dotnet.relax-dll-claims-when-bundling-detected
|
- key: dotnet.relax-dll-claims-when-bundling-detected
|
||||||
description: show all packages from the deps.json if bundling tooling is present as a dependency (e.g. ILRepack)
|
description: show all packages from the deps.json if bundling tooling is present as a dependency (e.g. ILRepack)
|
||||||
- key: golang.capture-symbols
|
- key: golang.capture-symbols
|
||||||
description: 'capture function symbols from the binary symbol table (pclntab). valid values are: "none" (disabled), "stdlib" (only the synthetic stdlib package), and "all" (all module packages plus stdlib)'
|
description: 'capture function symbols from the binary symbol table (pclntab). valid values are: "none" (disabled), "stdlib" (only the synthetic stdlib package), "extended-stdlib" (stdlib plus every module under golang.org/x/), and "all" (all module packages plus stdlib)'
|
||||||
|
- key: golang.capture-symbols-modules
|
||||||
|
description: glob patterns matched against go module paths (e.g. github.com/klauspost/**) that should have symbols captured in addition to whatever capture-symbols selects. ** crosses path separators, * does not. a trailing major version suffix is ignored when matching, so github.com/foo/* covers github.com/foo/bar/v2; spelling a suffix out in the pattern selects only that major version. this can only widen the selection, never narrow it, and is inert when capture-symbols is none
|
||||||
- key: golang.local-mod-cache-dir
|
- key: golang.local-mod-cache-dir
|
||||||
description: specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod
|
description: specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod
|
||||||
- key: golang.local-vendor-dir
|
- key: golang.local-vendor-dir
|
||||||
|
|||||||
@ -35,3 +35,24 @@ Create the new schema by running `make generate-json-schema` from the root of th
|
|||||||
- If there is an existing schema for the given version and the new schema **does not** match the existing schema, an error is shown indicating to increment the version appropriately (see the "Versioning" section)
|
- If there is an existing schema for the given version and the new schema **does not** match the existing schema, an error is shown indicating to increment the version appropriately (see the "Versioning" section)
|
||||||
|
|
||||||
***Note: never delete a JSON schema and never change an existing JSON schema once it has been published in a release!*** Only add new schemas with a newly incremented version. All previous schema files must be stored in the `schema/json/` directory.
|
***Note: never delete a JSON schema and never change an existing JSON schema once it has been published in a release!*** Only add new schemas with a newly incremented version. All previous schema files must be stored in the `schema/json/` directory.
|
||||||
|
|
||||||
|
### Exception: `description`-only corrections
|
||||||
|
|
||||||
|
A published schema may be amended in place for one narrow case: the change touches only `description` text and leaves the data shape identical. Descriptions are documentation carried alongside the schema rather than constraints a validator evaluates, so correcting one cannot invalidate a document that already validated against that version. Minting a new version instead would leave the old one permanently describing the tool incorrectly, and spend a version number on no semantic change.
|
||||||
|
|
||||||
|
This applies when **every** one of the following holds:
|
||||||
|
|
||||||
|
- the only differences are `description` values
|
||||||
|
- no field, type, enum, `required` entry, or `$ref` is added, removed, or altered
|
||||||
|
- the `$id` version is unchanged
|
||||||
|
|
||||||
|
Anything else, including adding a field that happens to be optional, is a schema change and needs a version bump per the "Versioning" section above.
|
||||||
|
|
||||||
|
The generator blocks an in-place edit by design, since it refuses to overwrite a file that differs from what it would produce. To amend one, delete the schema file and regenerate so it is rewritten from the current Go doc comments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm schema/json/schema-$VERSION.json
|
||||||
|
make generate-json-schema
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is byte-identical to what the generator produces, so do not hand-edit the JSON. Re-running `make generate-json-schema` afterwards should report `No change to the existing schema!`, and `make check-json-schema-drift` should pass. Confirm with `git diff` that the only changes are the intended description lines.
|
||||||
|
|||||||
@ -1662,7 +1662,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Symbols are the function symbols from this module that are compiled into the binary, extracted from\nthe binary symbol table (pclntab) and grouped by the import path of the package that owns them. Each\nvalue is the sorted, deduplicated list of symbol names local to that package, i.e. with the import\npath prefix stripped (e.g. import path \"github.com/foo/bar\" -\u003e \"(*Type).Method\"). The fully qualified\nname is the import path, a \".\", and the local name. One exception: the binary's main package appears\nunder the key \"main\" (the name the linker assigns), not its original source import path, which is not\nrecoverable from the binary. Populated only when the golang cataloger's capture-symbols scope covers\nthis package: the \"all\" scope populates every module package plus the synthetic stdlib package, while\nthe \"stdlib\" scope populates only the stdlib package."
|
"description": "Symbols are the function symbols from this module that are compiled into the binary, extracted from\nthe binary symbol table (pclntab) and grouped by the import path of the package that owns them. Each\nvalue is the sorted, deduplicated list of symbol names local to that package, i.e. with the import\npath prefix stripped (e.g. import path \"github.com/foo/bar\" -\u003e \"(*Type).Method\"). The fully qualified\nname is the import path, a \".\", and the local name. One exception: the binary's main package appears\nunder the key \"main\" (the name the linker assigns), not its original source import path, which is not\nrecoverable from the binary. Populated only when the golang cataloger's capture-symbols scope covers\nthis package: the \"all\" scope populates every module package plus the synthetic stdlib package, the\n\"extended-stdlib\" scope populates the stdlib package plus every module under golang.org/x/, and the\n\"stdlib\" scope populates only the stdlib package. The capture-symbols-modules glob patterns populate\nany additional modules they match."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@ -1662,7 +1662,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Symbols are the function symbols from this module that are compiled into the binary, extracted from\nthe binary symbol table (pclntab) and grouped by the import path of the package that owns them. Each\nvalue is the sorted, deduplicated list of symbol names local to that package, i.e. with the import\npath prefix stripped (e.g. import path \"github.com/foo/bar\" -\u003e \"(*Type).Method\"). The fully qualified\nname is the import path, a \".\", and the local name. One exception: the binary's main package appears\nunder the key \"main\" (the name the linker assigns), not its original source import path, which is not\nrecoverable from the binary. Populated only when the golang cataloger's capture-symbols scope covers\nthis package: the \"all\" scope populates every module package plus the synthetic stdlib package, while\nthe \"stdlib\" scope populates only the stdlib package."
|
"description": "Symbols are the function symbols from this module that are compiled into the binary, extracted from\nthe binary symbol table (pclntab) and grouped by the import path of the package that owns them. Each\nvalue is the sorted, deduplicated list of symbol names local to that package, i.e. with the import\npath prefix stripped (e.g. import path \"github.com/foo/bar\" -\u003e \"(*Type).Method\"). The fully qualified\nname is the import path, a \".\", and the local name. One exception: the binary's main package appears\nunder the key \"main\" (the name the linker assigns), not its original source import path, which is not\nrecoverable from the binary. Populated only when the golang cataloger's capture-symbols scope covers\nthis package: the \"all\" scope populates every module package plus the synthetic stdlib package, the\n\"extended-stdlib\" scope populates the stdlib package plus every module under golang.org/x/, and the\n\"stdlib\" scope populates only the stdlib package. The capture-symbols-modules glob patterns populate\nany additional modules they match."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@ -12,6 +12,10 @@ const (
|
|||||||
// SymbolScopeStdlib captures symbols only for the synthetic "stdlib" package, leaving module packages without symbols.
|
// SymbolScopeStdlib captures symbols only for the synthetic "stdlib" package, leaving module packages without symbols.
|
||||||
SymbolScopeStdlib SymbolScope = "stdlib"
|
SymbolScopeStdlib SymbolScope = "stdlib"
|
||||||
|
|
||||||
|
// SymbolScopeExtendedStdlib captures symbols for the synthetic "stdlib" package as well as every module
|
||||||
|
// under golang.org/x/ (the extended standard library).
|
||||||
|
SymbolScopeExtendedStdlib SymbolScope = "extended-stdlib"
|
||||||
|
|
||||||
// SymbolScopeAll captures symbols for all module packages as well as the synthetic "stdlib" package.
|
// SymbolScopeAll captures symbols for all module packages as well as the synthetic "stdlib" package.
|
||||||
SymbolScopeAll SymbolScope = "all"
|
SymbolScopeAll SymbolScope = "all"
|
||||||
)
|
)
|
||||||
@ -21,6 +25,8 @@ func (s SymbolScope) Parse() SymbolScope {
|
|||||||
switch strings.ToLower(strings.TrimSpace(string(s))) {
|
switch strings.ToLower(strings.TrimSpace(string(s))) {
|
||||||
case string(SymbolScopeAll):
|
case string(SymbolScopeAll):
|
||||||
return SymbolScopeAll
|
return SymbolScopeAll
|
||||||
|
case string(SymbolScopeExtendedStdlib):
|
||||||
|
return SymbolScopeExtendedStdlib
|
||||||
case string(SymbolScopeStdlib):
|
case string(SymbolScopeStdlib):
|
||||||
return SymbolScopeStdlib
|
return SymbolScopeStdlib
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,11 @@ func Test_SymbolScope_Parse(t *testing.T) {
|
|||||||
{" all ", SymbolScopeAll},
|
{" all ", SymbolScopeAll},
|
||||||
{"stdlib", SymbolScopeStdlib},
|
{"stdlib", SymbolScopeStdlib},
|
||||||
{"Stdlib", SymbolScopeStdlib},
|
{"Stdlib", SymbolScopeStdlib},
|
||||||
|
{"extended-stdlib", SymbolScopeExtendedStdlib},
|
||||||
|
{"Extended-Stdlib", SymbolScopeExtendedStdlib},
|
||||||
|
{"EXTENDED-STDLIB", SymbolScopeExtendedStdlib},
|
||||||
|
{" extended-stdlib ", SymbolScopeExtendedStdlib},
|
||||||
|
{"extended_stdlib", SymbolScopeNone},
|
||||||
{"none", SymbolScopeNone},
|
{"none", SymbolScopeNone},
|
||||||
{"", SymbolScopeNone},
|
{"", SymbolScopeNone},
|
||||||
{"true", SymbolScopeNone},
|
{"true", SymbolScopeNone},
|
||||||
|
|||||||
@ -10,11 +10,12 @@ import (
|
|||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/internal/unknown"
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
)
|
)
|
||||||
|
|
||||||
func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) error {
|
func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) error {
|
||||||
f, err := elf.NewFile(reader)
|
f, err := elfutil.NewFile(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -152,6 +152,26 @@ 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. Note that RealPath may differ from ref.RealPath; callers must not assume the two
|
||||||
|
// agree for these locations. The FileSystemID comes from the given ref's layer.
|
||||||
|
func NewVirtualLocationFromImage(realPath, accessPath string, ref file.Reference, img *image.Image) Location {
|
||||||
|
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.
|
// 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 {
|
func NewLocationFromDirectory(responsePath string, fd string, ref file.Reference) Location {
|
||||||
return Location{
|
return Location{
|
||||||
|
|||||||
@ -175,6 +175,8 @@ func toSyftFileType(ty string) stereoscopeFile.Type {
|
|||||||
case "SymbolicLink":
|
case "SymbolicLink":
|
||||||
return stereoscopeFile.TypeSymLink
|
return stereoscopeFile.TypeSymLink
|
||||||
case "HardLink":
|
case "HardLink":
|
||||||
|
// kept for reading SBOMs written before stereoscope adopted hardlinks at index time (they are emitted as
|
||||||
|
// regular files now, except for malformed link entries)
|
||||||
return stereoscopeFile.TypeHardLink
|
return stereoscopeFile.TypeHardLink
|
||||||
case "Directory":
|
case "Directory":
|
||||||
return stereoscopeFile.TypeDirectory
|
return stereoscopeFile.TypeDirectory
|
||||||
|
|||||||
422
syft/internal/elfutil/elfutil.go
Normal file
422
syft/internal/elfutil/elfutil.go
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
Package elfutil provides an ELF opener that rejects sections whose compression headers declare an
|
||||||
|
implausible decompressed size.
|
||||||
|
|
||||||
|
debug/elf sizes a section's buffer from the section's own compression header, and a highly compressible
|
||||||
|
stream really does deliver the bytes that header promises, so internal/saferio's chunked read does not
|
||||||
|
help: it appends its way to every declared byte, and Go's 1.25x slice growth puts the lifetime cost near
|
||||||
|
five times that. 512MB of zeros is 510KB of zlib, and that file drives elf.NewFile through 2.6GB of
|
||||||
|
allocation and returns no error, which is a fatal Go OOM rather than a recoverable panic. Zlib caps out
|
||||||
|
near 1000:1, but debug/elf also accepts zstd, which passes 32000:1 on zeroed input, so the input needed
|
||||||
|
to declare a given size is smaller still.
|
||||||
|
|
||||||
|
The check runs in two parts, because debug/elf expands sections at two different times:
|
||||||
|
|
||||||
|
- the section-name string table is the one section elf.NewFile expands on its own, so it has to be
|
||||||
|
bounded against the raw bytes before the call is made. That is CheckSectionNameTable, and the walk
|
||||||
|
of the section table it needs is why this package decodes any ELF structures by hand at all.
|
||||||
|
- every other section is expanded lazily by (*Section).Open, so those are bounded after the parse,
|
||||||
|
where names, types and decompressed sizes are resolved already.
|
||||||
|
|
||||||
|
Only the sections syft can actually drive debug/elf into decompressing are bounded. DWARF is deliberately
|
||||||
|
excluded: syft never asks for it, so a binary carrying a large compressed .debug_info is cataloged rather
|
||||||
|
than skipped over a section nobody reads.
|
||||||
|
|
||||||
|
Sources:
|
||||||
|
- https://www.sco.com/developers/gabi/latest/ch4.sheader.html
|
||||||
|
- https://groups.google.com/g/generic-abi/c/satyPkuMisk
|
||||||
|
- https://docs.oracle.com/en/operating-systems/solaris/oracle-solaris/11.4/linkers-libraries/gnu-style-section-compression.html
|
||||||
|
- https://sourceware.org/pipermail/binutils/2015-July/089559.html
|
||||||
|
- https://cs.opensource.google/go/go/+/refs/tags/go1.26.5:src/debug/elf/file.go
|
||||||
|
- https://go.dev/doc/go1.19#linker
|
||||||
|
*/
|
||||||
|
package elfutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"debug/elf"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
intFile "github.com/anchore/syft/internal/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxDeclaredSectionSize bounds the decompressed size any single reachable section may declare.
|
||||||
|
//
|
||||||
|
// The bound only ever applies to a compressed section, and the gABI allows compression only on
|
||||||
|
// non-allocable ones, so nothing syft reads on the hot path (.data, .text, notes) can reach it. The
|
||||||
|
// realistic worst case is a compressed .symtab/.strtab on a very large binary, well under this. The
|
||||||
|
// trade-off is that a binary whose symbol or string table decompresses past this is skipped rather than
|
||||||
|
// cataloged; that is the correct direction to fail, since the alternative is OOM-killing the whole scan.
|
||||||
|
//
|
||||||
|
// Note that this is a per-section bound, not a per-file one: a file may hold several sections that each
|
||||||
|
// sit just under it. That is deliberate, since the sections syft actually reads are few.
|
||||||
|
const maxDeclaredSectionSize uint64 = 128 * intFile.MB
|
||||||
|
|
||||||
|
// legacyZlibHeaderSize is the size of the .zdebug header: the "ZLIB" magic plus a big-endian size.
|
||||||
|
const legacyZlibHeaderSize = 12
|
||||||
|
|
||||||
|
// sectionsReadByName are the sections syft asks debug/elf for by name. Everything else it reaches by
|
||||||
|
// section type, which reachableSections covers separately. Not every one of these ends in a read of the
|
||||||
|
// contents (.text is used for its address, .symtab only for a nil check), but bounding any name syft asks
|
||||||
|
// for is cheaper to keep true than tracking which lookups reach Data.
|
||||||
|
//
|
||||||
|
// Add to this list when a cataloger starts reading a new section by name. The ruleguard rule only stops a
|
||||||
|
// new debug/elf call site; it cannot see a new (*Section).Data call behind an already-bounded open.
|
||||||
|
var sectionsReadByName = map[string]struct{}{
|
||||||
|
".symtab": {},
|
||||||
|
".data": {},
|
||||||
|
".text": {},
|
||||||
|
".gopclntab": {},
|
||||||
|
".note.package": {},
|
||||||
|
".modinfo": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// wire sizes of the structures we decode, which differ between the two ELF classes
|
||||||
|
var (
|
||||||
|
sizeSection32 = int64(binary.Size(elf.Section32{}))
|
||||||
|
sizeSection64 = int64(binary.Size(elf.Section64{}))
|
||||||
|
sizeChdr32 = int64(binary.Size(elf.Chdr32{}))
|
||||||
|
sizeChdr64 = int64(binary.Size(elf.Chdr64{}))
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewFile parses an ELF file, rejecting it if a section syft may read declares a decompressed size this
|
||||||
|
// process should not be asked to allocate. It is a drop-in for elf.NewFile, except that a rejection is
|
||||||
|
// reported as a plain error rather than an *elf.FormatError, so callers that distinguish "not an ELF"
|
||||||
|
// from "bad ELF" treat it as the latter.
|
||||||
|
func NewFile(r io.ReaderAt) (*elf.File, error) {
|
||||||
|
if err := CheckSectionNameTable(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := elf.NewFile(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkReachableSections(f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkReachableSections bounds every section syft can drive debug/elf into decompressing. This runs
|
||||||
|
// after the parse because (*Section).Open expands lazily, so nothing has been allocated yet.
|
||||||
|
func checkReachableSections(f *elf.File) error {
|
||||||
|
for i := range reachableSections(f) {
|
||||||
|
s := f.Sections[i]
|
||||||
|
declared, claimed := declaredSectionSize(s)
|
||||||
|
if claimed && declared > maxDeclaredSectionSize {
|
||||||
|
return fmt.Errorf("elf section %q declares %d decompressed bytes, over the %d byte limit",
|
||||||
|
s.Name, declared, maxDeclaredSectionSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reachableSections reports which sections syft can actually reach. It marks by type generously, since
|
||||||
|
// the debug/elf accessors all go through SectionByType, which returns only the first section of a type: a
|
||||||
|
// second SHT_SYMTAB is bounded here despite being unreachable. DWARF is absent by design: nothing in syft
|
||||||
|
// calls File.DWARF, so debug/elf is never asked to expand a .debug_* section.
|
||||||
|
func reachableSections(f *elf.File) map[int]struct{} {
|
||||||
|
reach := make(map[int]struct{})
|
||||||
|
mark := func(i int) {
|
||||||
|
if i >= 0 && i < len(f.Sections) {
|
||||||
|
reach[i] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, s := range f.Sections {
|
||||||
|
switch s.Type {
|
||||||
|
case elf.SHT_SYMTAB, elf.SHT_DYNSYM, elf.SHT_DYNAMIC:
|
||||||
|
// File.Symbols, File.DynamicSymbols and File.DynString each read one of these plus the
|
||||||
|
// string table its Link field points at
|
||||||
|
mark(i)
|
||||||
|
mark(int(s.Link))
|
||||||
|
case elf.SHT_GNU_VERSYM, elf.SHT_GNU_VERDEF, elf.SHT_GNU_VERNEED:
|
||||||
|
// File.DynamicSymbols pulls the symbol version tables in behind the caller's back, via
|
||||||
|
// gnuVersionInit. Strictly they are only reachable alongside a .dynsym, and VERDEF/VERNEED
|
||||||
|
// only alongside a VERSYM, but they are marked unconditionally rather than tracked: the only
|
||||||
|
// file that loses out carries an oversized version table and no dynamic symbols at all,
|
||||||
|
// which is not something a toolchain emits.
|
||||||
|
mark(i)
|
||||||
|
}
|
||||||
|
if _, ok := sectionsReadByName[s.Name]; ok {
|
||||||
|
mark(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reach
|
||||||
|
}
|
||||||
|
|
||||||
|
// declaredSectionSize reports the decompressed size a section claims, if it claims one at all.
|
||||||
|
//
|
||||||
|
// ELF grew two separate ways of saying "this section is compressed", and (*Section).Open honors both, so
|
||||||
|
// this dispatches the same way Open does and in the same order. That correspondence is the whole design:
|
||||||
|
// wherever Open returns a decompressing reader, this reports the size that reader will expand to. Missing
|
||||||
|
// one is the bug this package exists to prevent, so when a Go release changes Open, this is what has to be
|
||||||
|
// re-read against it.
|
||||||
|
//
|
||||||
|
// It deliberately over-reports in one place, an unrecognized ch_type: Open hands back an error reader for
|
||||||
|
// anything but zlib and zstd, so nothing is allocated, but the type is not exposed on elf.Section and
|
||||||
|
// re-reading the header for it would only spare a file debug/elf cannot decompress anyway.
|
||||||
|
func declaredSectionSize(s *elf.Section) (uint64, bool) {
|
||||||
|
// SHT_NOBITS marks a section that occupies no bytes in the file, .bss being the familiar one: it
|
||||||
|
// declares a size the loader zero-fills at run time rather than a size stored anywhere. Its sh_offset
|
||||||
|
// is only a conceptual placement and routinely lands inside some other section's bytes. Two things
|
||||||
|
// follow, and both say to test this before looking at compression at all:
|
||||||
|
// - Open tests it first too, ahead of any compression, and hands back a reader that fails every
|
||||||
|
// read. Nothing is allocated for the declared size, however large it is.
|
||||||
|
// - the gABI forbids SHF_COMPRESSED here, but elf.NewFile does not enforce that and parses a
|
||||||
|
// compression header anyway. The Size it leaves on the section was decoded from whatever bytes lie
|
||||||
|
// at that borrowed offset, so it is not a figure to bound anything against.
|
||||||
|
if s.Type == elf.SHT_NOBITS {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Flags&elf.SHF_COMPRESSED != 0 {
|
||||||
|
return chdrDeclaredSize(s)
|
||||||
|
}
|
||||||
|
return zdebugDeclaredSize(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// chdrDeclaredSize reports the size declared by the modern compression form: an SHF_COMPRESSED flag,
|
||||||
|
// plus an Elf32_Chdr or Elf64_Chdr at the head of the section giving the algorithm and the decompressed
|
||||||
|
// size. This is the form standardized in the gABI and the one any current toolchain emits.
|
||||||
|
func chdrDeclaredSize(s *elf.Section) (uint64, bool) {
|
||||||
|
// SHF_ALLOC marks a section the loader maps into the running process, and nothing decompresses a
|
||||||
|
// section on its way into memory, so the gABI makes the two flags mutually exclusive. Open enforces
|
||||||
|
// that rather than tolerating it: a section setting both gets an error reader, never a decompressing
|
||||||
|
// one, so a size declared alongside SHF_ALLOC is never allocated.
|
||||||
|
if s.Flags&elf.SHF_ALLOC != 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
// elf.NewFile decodes the Chdr during the parse and overwrites Size with ch_size, leaving the on-disk
|
||||||
|
// figure in FileSize. So the number a hostile file controls is already here, and no second read of the
|
||||||
|
// file is needed.
|
||||||
|
return s.Size, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// zdebugDeclaredSize reports the size declared by the legacy compression form. Before SHF_COMPRESSED was
|
||||||
|
// added to the gABI, GNU tooling signaled a compressed debug section by renaming it, .debug_info to
|
||||||
|
// .zdebug_info, and prefixing the payload with a header of its own: the magic "ZLIB" followed by the
|
||||||
|
// decompressed size. Toolchains default to the gABI form now, Go's own linker having emitted .zdebug up
|
||||||
|
// to 1.19, but this is not vestigial: nothing ties the prefix to a debug section, so a crafted file can
|
||||||
|
// hang it on a section syft does read, an SHT_SYMTAB named .zdebug_x being the obvious one.
|
||||||
|
func zdebugDeclaredSize(s *elf.Section) (uint64, bool) {
|
||||||
|
// no flag distinguishes this form, so the section name is the only signal, and Open gates on the name
|
||||||
|
// before it so much as looks at the bytes. Matching that gate exactly is what keeps an ordinary
|
||||||
|
// section whose contents happen to begin with "ZLIB" out of scope: Open will never decompress it, so
|
||||||
|
// neither may this.
|
||||||
|
if !strings.HasPrefix(s.Name, ".zdebug") {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
// unlike the Chdr above, nothing in the parse has looked at this header, so read it here. Reading off
|
||||||
|
// the section is only safe because SHF_COMPRESSED is clear on this path, since elf.NewFile leaves the
|
||||||
|
// embedded ReaderAt nil for a compressed one. Open treats a short read or absent magic as "not really
|
||||||
|
// compressed after all" and serves the bytes as they lie.
|
||||||
|
var hdr [legacyZlibHeaderSize]byte
|
||||||
|
if n, _ := s.ReadAt(hdr[:], 0); n != legacyZlibHeaderSize || string(hdr[:4]) != "ZLIB" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
// the size is big-endian whatever byte order the rest of the file uses. The header predates the gABI
|
||||||
|
// mechanism and was simply defined that way, carrying no endianness of its own.
|
||||||
|
return binary.BigEndian.Uint64(hdr[4:]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckSectionNameTable bounds the one section elf.NewFile expands on its own. Anything it cannot walk is
|
||||||
|
// passed through untouched, leaving elf.NewFile to describe the file. It does not repeat every check
|
||||||
|
// elf.NewFile makes ahead of that expansion, though, so a file that is both malformed and oversized can
|
||||||
|
// come back with this error rather than an *elf.FormatError.
|
||||||
|
//
|
||||||
|
// NewFile calls this itself. It is exported for the callers that cannot use NewFile because the
|
||||||
|
// debug/elf call is made for them inside another package, debug/buildinfo being the one syft reaches:
|
||||||
|
// gate the reader on this and the eager section-name table read is bounded, which is everything that
|
||||||
|
// package expands today, since it reaches .go.buildinfo through the program headers instead.
|
||||||
|
func CheckSectionNameTable(r io.ReaderAt) error {
|
||||||
|
class, order, ok := identify(r)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sh, ok := sectionNameTableHeader(r, class, order)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// the name table's own name is not resolved at the point elf.NewFile reads it, so debug/elf cannot
|
||||||
|
// take the legacy .zdebug path for this section: only a compression header can apply
|
||||||
|
if sh.flags&uint64(elf.SHF_COMPRESSED) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
declared, err := compressedSize(r, class, order, sh.offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil //nolint:nilerr // truncated section; elf.NewFile will say so
|
||||||
|
}
|
||||||
|
if declared > maxDeclaredSectionSize {
|
||||||
|
return fmt.Errorf("elf section name table declares %d decompressed bytes, over the %d byte limit",
|
||||||
|
declared, maxDeclaredSectionSize)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// identify decodes the class and byte order every later read depends on, out of the file's 16
|
||||||
|
// identification bytes. It reports false for anything that is not an ELF this package understands, so
|
||||||
|
// elf.NewFile remains the single source of format errors.
|
||||||
|
func identify(r io.ReaderAt) (elf.Class, binary.ByteOrder, bool) {
|
||||||
|
var ident [16]byte
|
||||||
|
if _, err := io.ReadFull(io.NewSectionReader(r, 0, int64(len(ident))), ident[:]); err != nil {
|
||||||
|
return 0, nil, false
|
||||||
|
}
|
||||||
|
if string(ident[:4]) != elf.ELFMAG {
|
||||||
|
return 0, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
class := elf.Class(ident[elf.EI_CLASS])
|
||||||
|
if class != elf.ELFCLASS32 && class != elf.ELFCLASS64 {
|
||||||
|
return 0, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch elf.Data(ident[elf.EI_DATA]) {
|
||||||
|
case elf.ELFDATA2LSB:
|
||||||
|
return class, binary.LittleEndian, true
|
||||||
|
case elf.ELFDATA2MSB:
|
||||||
|
return class, binary.BigEndian, true
|
||||||
|
}
|
||||||
|
return 0, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// sectionNameTableHeader locates the header of the section syft's own parse cannot reach, the one
|
||||||
|
// elf.NewFile expands for itself. It reports false whenever the file names no name table or is malformed
|
||||||
|
// enough that nothing gets decompressed: either way there is nothing to bound, and describing the file is
|
||||||
|
// elf.NewFile's job rather than this package's.
|
||||||
|
func sectionNameTableHeader(r io.ReaderAt, class elf.Class, order binary.ByteOrder) (sectionHeader, bool) {
|
||||||
|
shoff, shentsize, shnum, shstrndx, err := fileHeader(r, class, order)
|
||||||
|
if err != nil || shoff <= 0 || shentsize < sectionHeaderSize(class) {
|
||||||
|
return sectionHeader{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// a zero e_shnum means the real count lives in section 0's size field, and an e_shstrndx of
|
||||||
|
// SHN_XINDEX means the real string table index lives in that same header's link field
|
||||||
|
if shnum == 0 {
|
||||||
|
sh, err := readSectionHeader(r, class, order, shoff)
|
||||||
|
if err != nil {
|
||||||
|
return sectionHeader{}, false
|
||||||
|
}
|
||||||
|
shnum = sh.size
|
||||||
|
if shstrndx == uint64(elf.SHN_XINDEX) {
|
||||||
|
shstrndx = uint64(sh.link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// an index of zero means the file has no section name table, so elf.NewFile returns before reading one.
|
||||||
|
// An out-of-range index expands nothing either, though elf.NewFile only rejects it when e_shnum was
|
||||||
|
// non-zero; reached through SHN_XINDEX it indexes out of range and panics instead. That is a
|
||||||
|
// recoverable panic the task executor already contains, unlike the OOM this package exists to stop.
|
||||||
|
if shstrndx == 0 || shstrndx >= shnum {
|
||||||
|
return sectionHeader{}, false
|
||||||
|
}
|
||||||
|
// the index is file-controlled, so guard the multiply and then the sum against a table the file
|
||||||
|
// places past the end of the address space
|
||||||
|
if shstrndx > uint64(math.MaxInt64)/uint64(shentsize) {
|
||||||
|
return sectionHeader{}, false
|
||||||
|
}
|
||||||
|
off := shoff + int64(shstrndx)*shentsize
|
||||||
|
if off < 0 {
|
||||||
|
return sectionHeader{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
sh, err := readSectionHeader(r, class, order, off)
|
||||||
|
if err != nil {
|
||||||
|
return sectionHeader{}, false
|
||||||
|
}
|
||||||
|
return sh, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// sectionHeader is the handful of fields we need out of an Elf32_Shdr or an Elf64_Shdr.
|
||||||
|
type sectionHeader struct {
|
||||||
|
flags uint64
|
||||||
|
offset int64
|
||||||
|
size uint64
|
||||||
|
link uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileHeader returns the section table location and shape out of the ELF file header.
|
||||||
|
func fileHeader(r io.ReaderAt, class elf.Class, order binary.ByteOrder) (shoff, shentsize int64, shnum, shstrndx uint64, err error) {
|
||||||
|
if class == elf.ELFCLASS32 {
|
||||||
|
var h elf.Header32
|
||||||
|
if err := binary.Read(io.NewSectionReader(r, 0, int64(binary.Size(h))), order, &h); err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
return int64(h.Shoff), int64(h.Shentsize), uint64(h.Shnum), uint64(h.Shstrndx), nil
|
||||||
|
}
|
||||||
|
var h elf.Header64
|
||||||
|
if err := binary.Read(io.NewSectionReader(r, 0, int64(binary.Size(h))), order, &h); err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
// Shoff is a uint64, so a file may claim an offset that does not fit in an int64; the caller rejects
|
||||||
|
// a negative result rather than wrapping into a plausible-looking one
|
||||||
|
return int64(h.Shoff), int64(h.Shentsize), uint64(h.Shnum), uint64(h.Shstrndx), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readSectionHeader decodes one section header. Reads go through binary.Read, which uses io.ReadFull, so
|
||||||
|
// a short read is an error rather than a partially-zeroed header.
|
||||||
|
func readSectionHeader(r io.ReaderAt, class elf.Class, order binary.ByteOrder, off int64) (sectionHeader, error) {
|
||||||
|
sr := io.NewSectionReader(r, off, sectionHeaderSize(class))
|
||||||
|
if class == elf.ELFCLASS32 {
|
||||||
|
var sh elf.Section32
|
||||||
|
if err := binary.Read(sr, order, &sh); err != nil {
|
||||||
|
return sectionHeader{}, err
|
||||||
|
}
|
||||||
|
return sectionHeader{
|
||||||
|
flags: uint64(sh.Flags),
|
||||||
|
offset: int64(sh.Off),
|
||||||
|
size: uint64(sh.Size),
|
||||||
|
link: sh.Link,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
var sh elf.Section64
|
||||||
|
if err := binary.Read(sr, order, &sh); err != nil {
|
||||||
|
return sectionHeader{}, err
|
||||||
|
}
|
||||||
|
return sectionHeader{
|
||||||
|
flags: sh.Flags,
|
||||||
|
offset: int64(sh.Off),
|
||||||
|
size: sh.Size,
|
||||||
|
link: sh.Link,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compressedSize reads the decompressed size out of a section's SHF_COMPRESSED header. Chdr64 carries a
|
||||||
|
// blank field that binary.Size counts and binary.Read skips, so the decoded offsets match what debug/elf
|
||||||
|
// reaches for with unsafe.Offsetof.
|
||||||
|
func compressedSize(r io.ReaderAt, class elf.Class, order binary.ByteOrder, off int64) (uint64, error) {
|
||||||
|
if off < 0 {
|
||||||
|
return 0, fmt.Errorf("negative section offset")
|
||||||
|
}
|
||||||
|
sr := io.NewSectionReader(r, off, compressionHeaderSize(class))
|
||||||
|
if class == elf.ELFCLASS32 {
|
||||||
|
var ch elf.Chdr32
|
||||||
|
if err := binary.Read(sr, order, &ch); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64(ch.Size), nil
|
||||||
|
}
|
||||||
|
var ch elf.Chdr64
|
||||||
|
if err := binary.Read(sr, order, &ch); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return ch.Size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sectionHeaderSize(class elf.Class) int64 {
|
||||||
|
if class == elf.ELFCLASS32 {
|
||||||
|
return sizeSection32
|
||||||
|
}
|
||||||
|
return sizeSection64
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressionHeaderSize(class elf.Class) int64 {
|
||||||
|
if class == elf.ELFCLASS32 {
|
||||||
|
return sizeChdr32
|
||||||
|
}
|
||||||
|
return sizeChdr64
|
||||||
|
}
|
||||||
652
syft/internal/elfutil/elfutil_test.go
Normal file
652
syft/internal/elfutil/elfutil_test.go
Normal file
@ -0,0 +1,652 @@
|
|||||||
|
package elfutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"debug/elf"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// section describes one section to place in a fixture. The compression headers are deliberately
|
||||||
|
// separable from the payload so a fixture can claim far more than it can deliver, which is the whole
|
||||||
|
// shape of the attack under test.
|
||||||
|
type section struct {
|
||||||
|
name string
|
||||||
|
typ elf.SectionType
|
||||||
|
flags elf.SectionFlag
|
||||||
|
link uint32
|
||||||
|
body []byte
|
||||||
|
compressed bool // emit a SHF_COMPRESSED chdr ahead of body
|
||||||
|
legacyZlib bool // emit a 12-byte .zdebug-style ZLIB header ahead of body
|
||||||
|
declaredSize uint64
|
||||||
|
offsetOf string // borrow another section's file offset, for SHT_NOBITS fixtures
|
||||||
|
shSize uint64 // override sh_size, since an SHT_NOBITS section's size is not its span in the file
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildOpts struct {
|
||||||
|
// nameTable overrides the generated .shstrtab, so a fixture can compress the one section
|
||||||
|
// elf.NewFile expands on its own.
|
||||||
|
nameTable *section
|
||||||
|
// extendedShnum parks the real section count in section 0 and zeroes e_shnum.
|
||||||
|
extendedShnum bool
|
||||||
|
// xindexShstrndx sets e_shstrndx to SHN_XINDEX so the real index comes from section 0's link.
|
||||||
|
xindexShstrndx bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildELF assembles a minimal ELF: a null section, then secs, then a generated .shstrtab that
|
||||||
|
// e_shstrndx points at.
|
||||||
|
func buildELF(t *testing.T, class elf.Class, order binary.ByteOrder, secs []section, opts buildOpts) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
nameTable := section{name: ".shstrtab", typ: elf.SHT_STRTAB}
|
||||||
|
if opts.nameTable != nil {
|
||||||
|
nameTable = *opts.nameTable
|
||||||
|
nameTable.name = ".shstrtab"
|
||||||
|
nameTable.typ = elf.SHT_STRTAB
|
||||||
|
}
|
||||||
|
|
||||||
|
all := append([]section{{}}, secs...)
|
||||||
|
all = append(all, nameTable)
|
||||||
|
shstrndx := len(all) - 1
|
||||||
|
|
||||||
|
// the name table's body is the names of every section, so it has to be built before the payloads
|
||||||
|
var names bytes.Buffer
|
||||||
|
names.WriteByte(0)
|
||||||
|
nameOff := make([]uint32, len(all))
|
||||||
|
for i, s := range all {
|
||||||
|
if s.name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nameOff[i] = uint32(names.Len())
|
||||||
|
names.WriteString(s.name)
|
||||||
|
names.WriteByte(0)
|
||||||
|
}
|
||||||
|
all[shstrndx].body = names.Bytes()
|
||||||
|
// a fixture that compresses the name table without rigging a size gets an honest one, so the file is
|
||||||
|
// readable rather than merely parseable
|
||||||
|
if all[shstrndx].compressed && all[shstrndx].declaredSize == 0 {
|
||||||
|
all[shstrndx].declaredSize = uint64(names.Len())
|
||||||
|
all[shstrndx].body = deflate(t, names.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
is32 := class == elf.ELFCLASS32
|
||||||
|
ehsize, shentsize := binary.Size(elf.Header64{}), binary.Size(elf.Section64{})
|
||||||
|
machine := elf.EM_X86_64
|
||||||
|
if is32 {
|
||||||
|
ehsize, shentsize = binary.Size(elf.Header32{}), binary.Size(elf.Section32{})
|
||||||
|
machine = elf.EM_386
|
||||||
|
}
|
||||||
|
|
||||||
|
shoff := ehsize
|
||||||
|
dataOff := shoff + len(all)*shentsize
|
||||||
|
|
||||||
|
// lay the payloads out and remember where each landed, so an SHT_NOBITS fixture can point at one
|
||||||
|
payloads := make([][]byte, len(all))
|
||||||
|
offsets := make(map[string]uint64)
|
||||||
|
cursor := uint64(dataOff)
|
||||||
|
for i, s := range all {
|
||||||
|
payloads[i] = sectionContent(t, class, order, s)
|
||||||
|
offsets[s.name] = cursor
|
||||||
|
cursor += uint64(len(payloads[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var ident [16]byte
|
||||||
|
copy(ident[:], elf.ELFMAG)
|
||||||
|
ident[elf.EI_CLASS] = byte(class)
|
||||||
|
ident[elf.EI_DATA] = byte(elf.ELFDATA2LSB)
|
||||||
|
if order == binary.BigEndian {
|
||||||
|
ident[elf.EI_DATA] = byte(elf.ELFDATA2MSB)
|
||||||
|
}
|
||||||
|
ident[elf.EI_VERSION] = byte(elf.EV_CURRENT)
|
||||||
|
|
||||||
|
shnum := uint16(len(all))
|
||||||
|
if opts.extendedShnum {
|
||||||
|
shnum = 0
|
||||||
|
}
|
||||||
|
strndx := uint16(shstrndx)
|
||||||
|
if opts.xindexShstrndx {
|
||||||
|
strndx = uint16(elf.SHN_XINDEX)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if is32 {
|
||||||
|
write(t, buf, order, elf.Header32{
|
||||||
|
Ident: ident, Type: uint16(elf.ET_REL), Machine: uint16(machine), Version: uint32(elf.EV_CURRENT),
|
||||||
|
Shoff: uint32(shoff), Ehsize: uint16(ehsize), Shentsize: uint16(shentsize),
|
||||||
|
Shnum: shnum, Shstrndx: strndx,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
write(t, buf, order, elf.Header64{
|
||||||
|
Ident: ident, Type: uint16(elf.ET_REL), Machine: uint16(machine), Version: uint32(elf.EV_CURRENT),
|
||||||
|
Shoff: uint64(shoff), Ehsize: uint16(ehsize), Shentsize: uint16(shentsize),
|
||||||
|
Shnum: shnum, Shstrndx: strndx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
off := uint64(dataOff)
|
||||||
|
for i, s := range all {
|
||||||
|
size := uint64(len(payloads[i]))
|
||||||
|
if s.shSize != 0 {
|
||||||
|
size = s.shSize
|
||||||
|
}
|
||||||
|
secOff := off
|
||||||
|
if s.offsetOf != "" {
|
||||||
|
secOff = offsets[s.offsetOf]
|
||||||
|
}
|
||||||
|
flags := s.flags
|
||||||
|
if s.compressed {
|
||||||
|
// the content header and the flag always travel together in a real file
|
||||||
|
flags |= elf.SHF_COMPRESSED
|
||||||
|
}
|
||||||
|
link := s.link
|
||||||
|
// section 0 carries the extended count and the extended name table index
|
||||||
|
if i == 0 {
|
||||||
|
if opts.extendedShnum {
|
||||||
|
size = uint64(len(all))
|
||||||
|
}
|
||||||
|
if opts.xindexShstrndx {
|
||||||
|
link = uint32(shstrndx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is32 {
|
||||||
|
write(t, buf, order, elf.Section32{
|
||||||
|
Name: nameOff[i], Type: uint32(s.typ), Flags: uint32(flags), Link: link,
|
||||||
|
Off: uint32(secOff), Size: uint32(size), Addralign: 1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
write(t, buf, order, elf.Section64{
|
||||||
|
Name: nameOff[i], Type: uint32(s.typ), Flags: uint64(flags), Link: link,
|
||||||
|
Off: secOff, Size: size, Addralign: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
off += uint64(len(payloads[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, dataOff, buf.Len(), "fixture layout drifted from the declared offsets")
|
||||||
|
for _, p := range payloads {
|
||||||
|
buf.Write(p)
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sectionContent wraps the body in whichever compression header the fixture asks for.
|
||||||
|
func sectionContent(t *testing.T, class elf.Class, order binary.ByteOrder, s section) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case s.legacyZlib:
|
||||||
|
hdr := make([]byte, legacyZlibHeaderSize)
|
||||||
|
copy(hdr, "ZLIB")
|
||||||
|
binary.BigEndian.PutUint64(hdr[4:], s.declaredSize)
|
||||||
|
return append(hdr, s.body...)
|
||||||
|
case s.compressed:
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if class == elf.ELFCLASS32 {
|
||||||
|
write(t, buf, order, elf.Chdr32{
|
||||||
|
Type: uint32(elf.COMPRESS_ZLIB), Size: uint32(s.declaredSize), Addralign: 1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
write(t, buf, order, elf.Chdr64{
|
||||||
|
Type: uint32(elf.COMPRESS_ZLIB), Size: s.declaredSize, Addralign: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
buf.Write(s.body)
|
||||||
|
return buf.Bytes()
|
||||||
|
default:
|
||||||
|
return s.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(t *testing.T, w io.Writer, order binary.ByteOrder, v any) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, binary.Write(w, order, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// deflate returns a real zlib stream, so a fixture using it actually delivers the bytes it promises.
|
||||||
|
func deflate(t *testing.T, payload []byte) []byte {
|
||||||
|
t.Helper()
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
zw := zlib.NewWriter(buf)
|
||||||
|
_, err := zw.Write(payload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, zw.Close())
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// classes is the matrix every structural case runs under, since the header layouts differ per class and
|
||||||
|
// the compression header's size field is a different width in each.
|
||||||
|
var classes = []struct {
|
||||||
|
name string
|
||||||
|
class elf.Class
|
||||||
|
order binary.ByteOrder
|
||||||
|
}{
|
||||||
|
{"64-bit little-endian", elf.ELFCLASS64, binary.LittleEndian},
|
||||||
|
{"64-bit big-endian", elf.ELFCLASS64, binary.BigEndian},
|
||||||
|
{"32-bit little-endian", elf.ELFCLASS32, binary.LittleEndian},
|
||||||
|
{"32-bit big-endian", elf.ELFCLASS32, binary.BigEndian},
|
||||||
|
}
|
||||||
|
|
||||||
|
const overLimit = maxDeclaredSectionSize + 1
|
||||||
|
|
||||||
|
func TestNewFile_RejectsOversizedReachableSections(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
secs []section
|
||||||
|
opts buildOpts
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "compressed .symtab",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".symtab", typ: elf.SHT_SYMTAB, link: 2, compressed: true, declaredSize: overLimit},
|
||||||
|
{name: ".strtab", typ: elf.SHT_STRTAB},
|
||||||
|
},
|
||||||
|
wantErr: `".symtab"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "the string table a .symtab links to",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".symtab", typ: elf.SHT_SYMTAB, link: 2},
|
||||||
|
{name: ".strtab", typ: elf.SHT_STRTAB, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
wantErr: `".strtab"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compressed .dynsym",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".dynsym", typ: elf.SHT_DYNSYM, link: 2, compressed: true, declaredSize: overLimit},
|
||||||
|
{name: ".dynstr", typ: elf.SHT_STRTAB},
|
||||||
|
},
|
||||||
|
wantErr: `".dynsym"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compressed .dynamic",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".dynamic", typ: elf.SHT_DYNAMIC, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
wantErr: `".dynamic"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compressed .note.package, reached by name rather than type",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".note.package", typ: elf.SHT_NOTE, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
wantErr: `".note.package"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compressed .modinfo, read by the kernel module cataloger",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".modinfo", typ: elf.SHT_PROGBITS, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
wantErr: `".modinfo"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// File.DynamicSymbols reads these via gnuVersionInit without the caller naming them
|
||||||
|
name: "compressed .gnu.version alongside a .dynsym",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".dynsym", typ: elf.SHT_DYNSYM, link: 2},
|
||||||
|
{name: ".dynstr", typ: elf.SHT_STRTAB},
|
||||||
|
{name: ".gnu.version", typ: elf.SHT_GNU_VERSYM, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
wantErr: `".gnu.version"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compressed .gnu.version_r alongside a .dynsym",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".dynsym", typ: elf.SHT_DYNSYM, link: 2},
|
||||||
|
{name: ".dynstr", typ: elf.SHT_STRTAB},
|
||||||
|
{name: ".gnu.version_r", typ: elf.SHT_GNU_VERNEED, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
wantErr: `".gnu.version_r"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compressed .gnu.version_d alongside a .dynsym",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".dynsym", typ: elf.SHT_DYNSYM, link: 2},
|
||||||
|
{name: ".dynstr", typ: elf.SHT_STRTAB},
|
||||||
|
{name: ".gnu.version_d", typ: elf.SHT_GNU_VERDEF, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
wantErr: `".gnu.version_d"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// the one section elf.NewFile expands itself, so this has to be caught before the parse
|
||||||
|
name: "compressed section name table",
|
||||||
|
opts: buildOpts{nameTable: §ion{compressed: true, declaredSize: overLimit}},
|
||||||
|
wantErr: "section name table",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// debug/elf gates the legacy form on the name, so a reachable section named this way reaches it
|
||||||
|
name: "legacy .zdebug header on a section reached by type",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".zdebug_symtab", typ: elf.SHT_SYMTAB, link: 2, legacyZlib: true, declaredSize: overLimit},
|
||||||
|
{name: ".strtab", typ: elf.SHT_STRTAB},
|
||||||
|
},
|
||||||
|
wantErr: `".zdebug_symtab"`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
for _, c := range classes {
|
||||||
|
t.Run(tt.name+"/"+c.name, func(t *testing.T) {
|
||||||
|
data := buildELF(t, c.class, c.order, tt.secs, tt.opts)
|
||||||
|
_, err := NewFile(bytes.NewReader(data))
|
||||||
|
require.Error(t, err)
|
||||||
|
// the fixtures are hand-assembled ELF bytes, so a bad one would satisfy require.Error on
|
||||||
|
// its own. Naming the section proves the rejection is the one we set up, and naming the
|
||||||
|
// limit proves it came from this package rather than from debug/elf.
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
assert.Contains(t, err.Error(), fmt.Sprint(maxDeclaredSectionSize))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewFile_AcceptsWhatDebugELFWouldNotExpand covers the false-negative direction: rejecting a whole
|
||||||
|
// binary over a section debug/elf is never driven to decompress drops real packages from the SBOM.
|
||||||
|
//
|
||||||
|
// Every fixture here is a file debug/elf parses without complaint, and every one must come back from
|
||||||
|
// NewFile without complaint too. An oversized claim is only worth rejecting when debug/elf can actually
|
||||||
|
// be made to allocate against it, so a claim it will never act on has to be ignored outright rather than
|
||||||
|
// merely tolerated.
|
||||||
|
func TestNewFile_AcceptsWhatDebugELFWouldNotExpand(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
secs []section
|
||||||
|
why string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// only File.DWARF expands a .debug_* section, and nothing in syft calls it, so this claim is
|
||||||
|
// never acted on. reachableSections leaves DWARF out on purpose: compressed debug info is
|
||||||
|
// routinely enormous in a legitimate binary, and bounding it would skip the binary entirely.
|
||||||
|
//
|
||||||
|
// non-allocable and SHF_COMPRESSED is deliberately the one shape debug/elf will expand, so the
|
||||||
|
// name is the only thing keeping this accepted. Adding SHF_ALLOC would make it pass for the
|
||||||
|
// allocable case's reason below and stop saying anything about DWARF.
|
||||||
|
name: "oversized compressed DWARF",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".debug_info", typ: elf.SHT_PROGBITS, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
why: "syft never calls File.DWARF, so debug/elf is never asked to expand it",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// the same section in the older form toolchains emitted before SHF_COMPRESSED existed. The
|
||||||
|
// header is different, the reader that would expand it is the same one syft never calls.
|
||||||
|
//
|
||||||
|
// SHF_COMPRESSED is deliberately absent: with it set, debug/elf takes the modern path and the
|
||||||
|
// legacy header is never consulted, so the .zdebug name gate would go untested.
|
||||||
|
name: "oversized legacy .zdebug DWARF",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".zdebug_info", typ: elf.SHT_PROGBITS, legacyZlib: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
why: "same, via the legacy form",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// reachable by type and named the way debug/elf gates the legacy form on, so the name half of
|
||||||
|
// that gate passes and the magic is the only thing left to reject the claim. Without the magic
|
||||||
|
// check the 0xff bytes behind it decode as a declared size of 2^64-1.
|
||||||
|
name: "a .zdebug-named reachable section whose bytes are not a legacy header",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".zdebug_symtab", typ: elf.SHT_SYMTAB, link: 2,
|
||||||
|
body: append([]byte("NOPE"), bytes.Repeat([]byte{0xff}, 32)...)},
|
||||||
|
{name: ".strtab", typ: elf.SHT_STRTAB},
|
||||||
|
},
|
||||||
|
why: "the legacy form needs the magic as well as the name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// the other half of the same gate: fewer bytes present than a legacy header needs. The magic is
|
||||||
|
// intact and some of the size field is present, so a short read has to read as "no claim"
|
||||||
|
// rather than as a size decoded out of a partly-filled buffer, which here would be huge.
|
||||||
|
name: "a .zdebug-named reachable section too short to hold a legacy header",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".zdebug_symtab", typ: elf.SHT_SYMTAB, link: 2,
|
||||||
|
body: append([]byte("ZLIB"), bytes.Repeat([]byte{0xff}, 4)...)},
|
||||||
|
{name: ".strtab", typ: elf.SHT_STRTAB},
|
||||||
|
},
|
||||||
|
why: "a truncated legacy header is not a claim",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// an ordinary uncompressed note that happens to open with those four bytes. debug/elf takes
|
||||||
|
// the legacy path only for a .zdebug-prefixed name, so it reads this as content and nothing
|
||||||
|
// here is a size claim at all. A check that sniffed for the magic instead would decode the
|
||||||
|
// 0xff padding behind it as a declared size and throw the binary away.
|
||||||
|
//
|
||||||
|
// .note.package because it has to be a section syft actually reads, or reachableSections skips
|
||||||
|
// it and the case passes without exercising anything. No flags for the same reason the name is
|
||||||
|
// not .zdebug-prefixed: either one would put debug/elf on a path where the bytes are a header.
|
||||||
|
name: "an uncompressed section whose bytes happen to start with ZLIB",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".note.package", typ: elf.SHT_NOTE, body: append([]byte("ZLIB"), bytes.Repeat([]byte{0xff}, 32)...)},
|
||||||
|
},
|
||||||
|
why: "debug/elf gates the legacy form on the section name, not the magic, so this is not a claim at all",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// a section that occupies memory at runtime cannot be compressed on disk, so (*Section).Open
|
||||||
|
// hands back the raw bytes rather than expanding one that says it is. The claim is real and
|
||||||
|
// oversized, and still costs nothing.
|
||||||
|
//
|
||||||
|
// this is the ".symtab" reject case with one bit added: .data is read by name just as .symtab
|
||||||
|
// is, so SHF_ALLOC is the only reason the two outcomes differ.
|
||||||
|
name: "an allocable compressed section",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".data", typ: elf.SHT_PROGBITS, flags: elf.SHF_COMPRESSED | elf.SHF_ALLOC,
|
||||||
|
compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
why: "debug/elf refuses to decompress an allocable section",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// the hardest one to see: sh_offset points into .rodata's payload, so elf.NewFile reads that
|
||||||
|
// compression header and .data comes back carrying an oversized Size of its own. Reading Size
|
||||||
|
// alone would reject here, but a SHT_NOBITS section holds no file bytes and its reader yields
|
||||||
|
// none, so the size is never allocated against. sh_size is set by hand because such a section
|
||||||
|
// has a size without occupying any of the file, and a zero one is a file debug/elf rejects.
|
||||||
|
//
|
||||||
|
// SHF_COMPRESSED has to be set for the size to be a claim at all, and .data has to be the
|
||||||
|
// borrower rather than the donor, since the donor is unreachable by name and never checked.
|
||||||
|
name: "an SHT_NOBITS section borrowing another section's compression header",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".rodata", typ: elf.SHT_PROGBITS, compressed: true, declaredSize: overLimit},
|
||||||
|
{name: ".data", typ: elf.SHT_NOBITS, flags: elf.SHF_COMPRESSED, offsetOf: ".rodata",
|
||||||
|
shSize: 4096},
|
||||||
|
},
|
||||||
|
why: "debug/elf never yields bytes for SHT_NOBITS, whatever Size it ends up carrying",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// a string table is reached through some symbol table's sh_link, never on its own, so one
|
||||||
|
// nothing points at is one nothing reads. This is the reject test's linked-.strtab case with
|
||||||
|
// the .symtab removed, and it is the case an implementation that bounded every SHT_STRTAB
|
||||||
|
// rather than following the links would fail.
|
||||||
|
name: "an oversized string table that no symbol table links",
|
||||||
|
secs: []section{
|
||||||
|
{name: ".strtab", typ: elf.SHT_STRTAB, compressed: true, declaredSize: overLimit},
|
||||||
|
},
|
||||||
|
why: "reachability runs through sh_link, not section type",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
for _, c := range classes {
|
||||||
|
t.Run(tt.name+"/"+c.name, func(t *testing.T) {
|
||||||
|
data := buildELF(t, c.class, c.order, tt.secs, buildOpts{})
|
||||||
|
|
||||||
|
// "elfutil accepted it" only means something if debug/elf accepts it too, so a fixture
|
||||||
|
// that drifts into being malformed fails here rather than passing for the wrong reason
|
||||||
|
_, err := elf.NewFile(bytes.NewReader(data))
|
||||||
|
require.NoError(t, err, "fixture is supposed to be a file debug/elf accepts")
|
||||||
|
|
||||||
|
_, err = NewFile(bytes.NewReader(data))
|
||||||
|
require.NoError(t, err, tt.why)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewFile_AcceptsAndReadsSectionUnderTheLimit guards the risk the bound introduces: a legitimately
|
||||||
|
// compressed section must still be readable, not merely accepted.
|
||||||
|
func TestNewFile_AcceptsAndReadsSectionUnderTheLimit(t *testing.T) {
|
||||||
|
for _, c := range classes {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
payload := bytes.Repeat([]byte("syft"), 4096)
|
||||||
|
data := buildELF(t, c.class, c.order, []section{
|
||||||
|
{name: ".note.package", typ: elf.SHT_NOTE, flags: elf.SHF_COMPRESSED,
|
||||||
|
compressed: true, declaredSize: uint64(len(payload)), body: deflate(t, payload)},
|
||||||
|
}, buildOpts{})
|
||||||
|
|
||||||
|
f, err := NewFile(bytes.NewReader(data))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err := f.Section(".note.package").Data()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, payload, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewFile_AcceptsAndReadsCompressedSectionNameTable covers the other half of CheckSectionNameTable.
|
||||||
|
// That check runs before the parse, off a hand-decoded section header rather than anything debug/elf has
|
||||||
|
// resolved, so a version of it that rejected every compressed name table outright, or that read the size
|
||||||
|
// out of the wrong offset, would still pass every rejection case. Resolving the names proves the file was
|
||||||
|
// left intact and not merely let through.
|
||||||
|
func TestNewFile_AcceptsAndReadsCompressedSectionNameTable(t *testing.T) {
|
||||||
|
for _, c := range classes {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
data := buildELF(t, c.class, c.order,
|
||||||
|
[]section{{name: ".note.package", typ: elf.SHT_NOTE}},
|
||||||
|
buildOpts{nameTable: §ion{compressed: true}})
|
||||||
|
|
||||||
|
f, err := NewFile(bytes.NewReader(data))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, f.Section(".note.package"), "section names did not survive the name table")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewFile_BombDoesNotAllocate is the end-to-end case: a real zlib stream that genuinely delivers
|
||||||
|
// every byte its header promises, so without the bound debug/elf allocates all of them.
|
||||||
|
func TestNewFile_BombDoesNotAllocate(t *testing.T) {
|
||||||
|
const declared = maxDeclaredSectionSize + 1
|
||||||
|
compressed := deflate(t, make([]byte, declared))
|
||||||
|
t.Logf("%d declared bytes compress to %d", declared, len(compressed))
|
||||||
|
|
||||||
|
data := buildELF(t, elf.ELFCLASS64, binary.LittleEndian, []section{
|
||||||
|
{name: ".symtab", typ: elf.SHT_SYMTAB, link: 2, flags: elf.SHF_COMPRESSED,
|
||||||
|
compressed: true, declaredSize: declared, body: compressed},
|
||||||
|
{name: ".strtab", typ: elf.SHT_STRTAB},
|
||||||
|
}, buildOpts{})
|
||||||
|
|
||||||
|
var before, after runtime.MemStats
|
||||||
|
runtime.GC()
|
||||||
|
runtime.ReadMemStats(&before)
|
||||||
|
|
||||||
|
_, err := NewFile(bytes.NewReader(data))
|
||||||
|
|
||||||
|
runtime.ReadMemStats(&after)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "over the")
|
||||||
|
|
||||||
|
// the guard has to reject before the allocation, not after it. The margin is loose on purpose: this
|
||||||
|
// is asserting "nothing near the declared size was allocated", not a precise budget.
|
||||||
|
const margin = 32 * 1024 * 1024
|
||||||
|
assert.Less(t, after.TotalAlloc-before.TotalAlloc, uint64(margin),
|
||||||
|
"parsing allocated far more than the input warrants")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewFile_MatchesDebugELFOnStructuralEdges pins the invariant the passthrough returns depend on:
|
||||||
|
// wherever elfutil declines to check, elf.NewFile must still be the one deciding.
|
||||||
|
func TestNewFile_MatchesDebugELFOnStructuralEdges(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
secs []section
|
||||||
|
opts buildOpts
|
||||||
|
}{
|
||||||
|
{name: "plain file", secs: []section{{name: ".text", typ: elf.SHT_PROGBITS, flags: elf.SHF_ALLOC}}},
|
||||||
|
{name: "extended section count", opts: buildOpts{extendedShnum: true}},
|
||||||
|
{name: "extended name table index", opts: buildOpts{xindexShstrndx: true}},
|
||||||
|
{name: "both extended forms", opts: buildOpts{extendedShnum: true, xindexShstrndx: true}},
|
||||||
|
{
|
||||||
|
name: "symtab linking a nonexistent string table",
|
||||||
|
secs: []section{{name: ".symtab", typ: elf.SHT_SYMTAB, link: 99}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
for _, c := range classes {
|
||||||
|
t.Run(tt.name+"/"+c.name, func(t *testing.T) {
|
||||||
|
data := buildELF(t, c.class, c.order, tt.secs, tt.opts)
|
||||||
|
|
||||||
|
_, want := elf.NewFile(bytes.NewReader(data))
|
||||||
|
_, got := NewFile(bytes.NewReader(data))
|
||||||
|
|
||||||
|
if want == nil {
|
||||||
|
assert.NoError(t, got, "elfutil rejected a file debug/elf accepts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Error(t, got, "elfutil accepted a file debug/elf rejects")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewFile_ExtendedShnumOverflowStillChecks covers the case a signed conversion used to swallow: a
|
||||||
|
// count that does not fit an int64 must not read as "no sections" and skip the check.
|
||||||
|
func TestNewFile_ExtendedShnumOverflowStillChecks(t *testing.T) {
|
||||||
|
data := buildELF(t, elf.ELFCLASS64, binary.LittleEndian, nil,
|
||||||
|
buildOpts{nameTable: §ion{compressed: true, declaredSize: overLimit}})
|
||||||
|
|
||||||
|
// rewrite section 0's size to a count that overflows int64, and zero e_shnum so it is consulted
|
||||||
|
// e_shnum sits at 0x3c in an Elf64_Ehdr, and sh_size at +32 in the Elf64_Shdr that follows it
|
||||||
|
shoff := binary.Size(elf.Header64{})
|
||||||
|
binary.LittleEndian.PutUint16(data[0x3c:], 0)
|
||||||
|
binary.LittleEndian.PutUint64(data[shoff+32:], uint64(1)<<63)
|
||||||
|
|
||||||
|
_, err := NewFile(bytes.NewReader(data))
|
||||||
|
require.Error(t, err, "a bogus section count must not disable the check")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewFile_PassesThroughNonELF(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
}{
|
||||||
|
{"empty", nil},
|
||||||
|
{"shorter than e_ident", []byte{0x7f, 'E'}},
|
||||||
|
{"bad magic", bytes.Repeat([]byte{0xab}, 128)},
|
||||||
|
{"bad class", func() []byte {
|
||||||
|
b := bytes.Repeat([]byte{0}, 128)
|
||||||
|
copy(b, elf.ELFMAG)
|
||||||
|
b[elf.EI_CLASS] = 9
|
||||||
|
return b
|
||||||
|
}()},
|
||||||
|
{"bad byte order", func() []byte {
|
||||||
|
b := bytes.Repeat([]byte{0}, 128)
|
||||||
|
copy(b, elf.ELFMAG)
|
||||||
|
b[elf.EI_CLASS] = byte(elf.ELFCLASS64)
|
||||||
|
b[elf.EI_DATA] = 9
|
||||||
|
return b
|
||||||
|
}()},
|
||||||
|
{"truncated after the header", func() []byte {
|
||||||
|
data := buildELF(t, elf.ELFCLASS64, binary.LittleEndian, nil, buildOpts{})
|
||||||
|
return data[:binary.Size(elf.Header64{})+4]
|
||||||
|
}()},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, want := elf.NewFile(bytes.NewReader(tt.data))
|
||||||
|
_, got := NewFile(bytes.NewReader(tt.data))
|
||||||
|
|
||||||
|
require.Error(t, want, "fixture is supposed to be rejected by debug/elf")
|
||||||
|
require.Error(t, got)
|
||||||
|
// the rejection must be debug/elf's, phrased its way, not ours
|
||||||
|
assert.NotContains(t, got.Error(), "over the")
|
||||||
|
assert.Equal(t, fmt.Sprint(want), fmt.Sprint(got))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -53,8 +53,8 @@ func (r *ContainerImageAllLayers) HasPath(path string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqueFileIDs stereoscopeFile.ReferenceSet, layerIdx int) ([]stereoscopeFile.Reference, error) {
|
func (r *ContainerImageAllLayers) locationsByRef(ref stereoscopeFile.Reference, accessPath string, uniqueFileIDs stereoscopeFile.ReferenceSet, layerPos int) ([]file.Location, error) {
|
||||||
uniqueFiles := make([]stereoscopeFile.Reference, 0)
|
uniqueLocations := make([]file.Location, 0)
|
||||||
|
|
||||||
// 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
|
// 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)
|
entry, err := r.img.FileCatalog.Get(ref)
|
||||||
@ -65,22 +65,22 @@ func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqu
|
|||||||
if entry.Type == stereoscopeFile.TypeHardLink || entry.Type == stereoscopeFile.TypeSymLink {
|
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
|
// 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
|
// 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)
|
resolvedRef, err := r.img.ResolveLinkByLayerSquash(ref, subLayerIdx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to resolve link from layer (layer=%d ref=%+v): %w", subLayerIdx, ref, err)
|
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) {
|
if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) {
|
||||||
uniqueFileIDs.Add(*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) {
|
} else if !uniqueFileIDs.Contains(ref) {
|
||||||
uniqueFileIDs.Add(ref)
|
uniqueFileIDs.Add(ref)
|
||||||
uniqueFiles = append(uniqueFiles, ref)
|
uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(accessPath, ref, r.img))
|
||||||
}
|
}
|
||||||
|
|
||||||
return uniqueFiles, nil
|
return uniqueLocations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilesByPath returns all file.References that match the given paths from any layer in the image.
|
// FilesByPath returns all file.References that match the given paths from any layer in the image.
|
||||||
@ -112,14 +112,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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, result := range results {
|
for i := range locations {
|
||||||
l := file.NewLocationFromImage(path, result, r.img)
|
r.annotateLocation(&locations[i])
|
||||||
r.annotateLocation(&l)
|
uniqueLocations = append(uniqueLocations, locations[i])
|
||||||
uniqueLocations = append(uniqueLocations, l)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,14 +157,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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, refResult := range refResults {
|
for i := range locations {
|
||||||
l := file.NewLocationFromImage(string(result.RequestPath), refResult, r.img)
|
r.annotateLocation(&locations[i])
|
||||||
r.annotateLocation(&l)
|
uniqueLocations = append(uniqueLocations, locations[i])
|
||||||
uniqueLocations = append(uniqueLocations, l)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,14 +231,13 @@ func (r *ContainerImageAllLayers) FilesByMIMEType(types ...string) ([]file.Locat
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
refResults, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
|
locations, err := r.locationsByRef(*ref.Reference, string(ref.RequestPath), uniqueFileIDs, idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, refResult := range refResults {
|
for i := range locations {
|
||||||
l := file.NewLocationFromImage(string(ref.RequestPath), refResult, r.img)
|
r.annotateLocation(&locations[i])
|
||||||
r.annotateLocation(&l)
|
uniqueLocations = append(uniqueLocations, locations[i])
|
||||||
uniqueLocations = append(uniqueLocations, l)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,26 +279,22 @@ func (r *ContainerImageAllLayers) annotateLocation(l *file.Location) {
|
|||||||
annotation := file.VisibleAnnotation
|
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
|
// 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 !r.pathResolvesToRef(l.RealPath, givenRef) {
|
||||||
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
|
annotation = file.HiddenAnnotation
|
||||||
}
|
}
|
||||||
|
|
||||||
// not only should the real path to the file exist, but the way we took to get there should also exist
|
// 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
|
// (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)
|
// make certain that /etc/passwd-1 exists)
|
||||||
if annotation == file.VisibleAnnotation && l.AccessPath != "" {
|
if annotation == file.VisibleAnnotation && l.AccessPath != "" && !r.pathResolvesToRef(l.AccessPath, givenRef) {
|
||||||
ref, err := r.img.SquashedSearchContext.SearchByPath(l.AccessPath, filetree.DoNotFollowDeadBasenameLinks)
|
|
||||||
if err != nil || !ref.HasReference() {
|
|
||||||
annotation = file.HiddenAnnotation
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Annotations[file.VisibleAnnotationKey] = annotation
|
l.Annotations[file.VisibleAnnotationKey] = annotation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pathResolvesToRef reports whether the given path in the squashed tree resolves to the given reference.
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
)
|
)
|
||||||
@ -574,3 +575,105 @@ 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()))
|
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("FilesByMIMEType surfaces every hardlink name", func(t *testing.T) {
|
||||||
|
// this is the surface #5029 could not fix and stereoscope's index-time adoption does: MIME-driven
|
||||||
|
// catalogers see each name. it also pins that the names are separately addressable (distinct
|
||||||
|
// references), since this resolver dedups on reference and would return one entry if they shared it.
|
||||||
|
locs, err := resolver.FilesByMIMEType("text/plain")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
byPath := map[string]file.Location{}
|
||||||
|
for _, loc := range locs {
|
||||||
|
byPath[loc.RealPath] = loc
|
||||||
|
}
|
||||||
|
for _, path := range []string{"/file.txt", "/hardlink-a", "/hardlink-b"} {
|
||||||
|
loc, ok := byPath[path]
|
||||||
|
require.Truef(t, ok, "expected MIME search to surface %s (got %v)", path, byPath)
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -126,6 +126,14 @@ func (r *directoryIndexer) indexTree(root string, stager *progress.AtomicStage)
|
|||||||
|
|
||||||
shouldIndexFullTree, err := isRealPath(root)
|
shouldIndexFullTree, err := isRealPath(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// a symlink can resolve into a directory we don't have permission to traverse (or into a path
|
||||||
|
// that no longer exists). Like any other inaccessible path, this should be recorded and skipped
|
||||||
|
// with a warning rather than aborting the entire scan (see #3286).
|
||||||
|
var pathErr *os.PathError
|
||||||
|
if errors.As(err, &pathErr) {
|
||||||
|
r.isFileAccessErr(root, err)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -286,6 +286,42 @@ func TestDirectoryIndexer_index_survive_badSymlink(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDirectoryIndexer_index_survive_inaccessibleSymlinkTarget(t *testing.T) {
|
||||||
|
// a symlink that resolves into a directory we don't have permission to traverse should be skipped
|
||||||
|
// with a warning like any other inaccessible path, not abort the entire scan (see #3286).
|
||||||
|
if os.Geteuid() == 0 {
|
||||||
|
t.Skip("cannot run as root: root bypasses the directory permissions that trigger the bug")
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve any symlinks in the temp path (e.g. macOS /var -> /private/var) so the indexer root is a
|
||||||
|
// real path -- otherwise indexing takes the symlinked-branch code path and never reaches the bug.
|
||||||
|
root, err := filepath.EvalSymlinks(t.TempDir())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// root/
|
||||||
|
// ├── restricted/ (chmod 0000 -- cannot be traversed)
|
||||||
|
// │ └── nested/
|
||||||
|
// │ └── target.txt
|
||||||
|
// └── link -> root/restricted/nested/target.txt
|
||||||
|
nested := filepath.Join(root, "restricted", "nested")
|
||||||
|
require.NoError(t, os.MkdirAll(nested, 0o755))
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(nested, "target.txt"), []byte("hello"), 0o644))
|
||||||
|
require.NoError(t, os.Symlink(filepath.Join(root, "restricted", "nested", "target.txt"), filepath.Join(root, "link")))
|
||||||
|
|
||||||
|
restricted := filepath.Join(root, "restricted")
|
||||||
|
require.NoError(t, os.Chmod(restricted, 0o000))
|
||||||
|
// restore permissions so t.TempDir cleanup can remove the tree
|
||||||
|
t.Cleanup(func() { _ = os.Chmod(restricted, 0o755) })
|
||||||
|
|
||||||
|
// base is empty, matching a plain `syft <path>` invocation (the reporter's scenario)
|
||||||
|
indexer := newDirectoryIndexer(root, "")
|
||||||
|
_, _, err = indexer.build()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// the inaccessible path should be recorded rather than silently dropped
|
||||||
|
require.NotEmpty(t, indexer.errPaths)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDirectoryIndexer_SkipsAlreadyVisitedLinkDestinations(t *testing.T) {
|
func TestDirectoryIndexer_SkipsAlreadyVisitedLinkDestinations(t *testing.T) {
|
||||||
var observedPaths []string
|
var observedPaths []string
|
||||||
pathObserver := func(_, p string, _ os.FileInfo, _ error) error {
|
pathObserver := func(_, p string, _ os.FileInfo, _ error) error {
|
||||||
|
|||||||
12
syft/internal/fileresolver/testdata/image-hardlinks/Dockerfile
vendored
Normal file
12
syft/internal/fileresolver/testdata/image-hardlinks/Dockerfile
vendored
Normal file
@ -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)
|
||||||
@ -120,9 +120,19 @@ func (r *readerAtAdapter) ReadAt(p []byte, off int64) (n int, err error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err = r.ReadSeekCloser.Read(p) // read from that absolute position
|
// io.ReaderAt requires len(p) bytes or a non-nil error, and the underlying reader honors neither half of
|
||||||
|
// that: a squashfs block that decompresses short returns fewer bytes with err == nil, and a read landing
|
||||||
|
// exactly on the end of the file returns a full buffer alongside io.EOF. io.ReadFull normalizes both.
|
||||||
|
n, err = io.ReadFull(r.ReadSeekCloser, p)
|
||||||
|
// ReadAt reports a short read at the end of the file as io.EOF. compared by identity on purpose:
|
||||||
|
// io.ReadFull returns this sentinel bare, and errors.Is would also downgrade a wrapped one from the
|
||||||
|
// underlying reader, turning genuine stream corruption into a benign end of file
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
// restore the position for the stateful read/seek operations
|
// restore the position for the stateful read/seek operations. a read error wins over a restore failure:
|
||||||
|
// callers compare against io.EOF, and masking it would strand them without the data they did get
|
||||||
if restoreErr := r.restorePosition(currentPos); restoreErr != nil {
|
if restoreErr := r.restorePosition(currentPos); restoreErr != nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = restoreErr
|
err = restoreErr
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package unionreader
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -120,12 +121,14 @@ func TestReaderAtAdapter_ReadAt(t *testing.T) {
|
|||||||
expectedStr: "",
|
expectedStr: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// io.ReaderAt requires a non-nil error whenever it returns fewer than len(p) bytes, so a
|
||||||
|
// buffer that runs off the end of the file reports io.EOF alongside what it did read
|
||||||
name: "partial read",
|
name: "partial read",
|
||||||
data: "Hello",
|
data: "Hello",
|
||||||
offset: 2,
|
offset: 2,
|
||||||
bufSize: 10,
|
bufSize: 10,
|
||||||
expectedN: 3,
|
expectedN: 3,
|
||||||
expectedErr: nil,
|
expectedErr: io.EOF,
|
||||||
expectedStr: "llo",
|
expectedStr: "llo",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -335,3 +338,111 @@ func (r *readSeekCloser) Close() error {
|
|||||||
r.closed = true
|
r.closed = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scriptedReadSeeker replays a fixed sequence of Read results so tests can express the read shapes a
|
||||||
|
// squashfs-backed reader actually produces, none of which an io.ReaderAt may pass through to callers:
|
||||||
|
// a short count with a nil error, a full count paired with io.EOF, or a failure partway through a buffer.
|
||||||
|
type scriptedReadSeeker struct {
|
||||||
|
reads []scriptedRead
|
||||||
|
next int
|
||||||
|
offset int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type scriptedRead struct {
|
||||||
|
data string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *scriptedReadSeeker) Read(p []byte) (int, error) {
|
||||||
|
if r.next >= len(r.reads) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
read := r.reads[r.next]
|
||||||
|
r.next++
|
||||||
|
n := copy(p, read.data)
|
||||||
|
r.offset += int64(n)
|
||||||
|
return n, read.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *scriptedReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if whence == io.SeekCurrent {
|
||||||
|
return r.offset, nil
|
||||||
|
}
|
||||||
|
r.offset = offset
|
||||||
|
return offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *scriptedReadSeeker) Close() error { return nil }
|
||||||
|
|
||||||
|
func TestReaderAtAdapter_ReadAtHonorsReaderAtContract(t *testing.T) {
|
||||||
|
errBoom := errors.New("boom")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
reads []scriptedRead
|
||||||
|
bufSize int
|
||||||
|
expectedN int
|
||||||
|
expectedErr error
|
||||||
|
expectedStr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// before io.ReadFull the caller got the first 3 bytes and a nil error, and any parser sizing a
|
||||||
|
// struct off the result read zero padding it had no way to detect
|
||||||
|
name: "fills the buffer across short reads",
|
||||||
|
reads: []scriptedRead{{data: "abc"}, {data: "def"}, {data: "ghi"}},
|
||||||
|
bufSize: 9,
|
||||||
|
expectedN: 9,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedStr: "abcdefghi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// squashfs pairs io.EOF with a full buffer when a read lands exactly on the end of the file;
|
||||||
|
// bytes.Reader.ReadAt returns nil there, and callers that treat any error as fatal rely on it
|
||||||
|
name: "drops io.EOF when the read still filled the buffer",
|
||||||
|
reads: []scriptedRead{{data: "abc"}, {data: "def", err: io.EOF}},
|
||||||
|
bufSize: 6,
|
||||||
|
expectedN: 6,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedStr: "abcdef",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// a short tail must surface as io.EOF rather than io.ErrUnexpectedEOF so callers comparing
|
||||||
|
// against io.EOF keep working
|
||||||
|
name: "reports a short tail as io.EOF",
|
||||||
|
reads: []scriptedRead{{data: "abc"}, {data: "de", err: io.EOF}},
|
||||||
|
bufSize: 10,
|
||||||
|
expectedN: 5,
|
||||||
|
expectedErr: io.EOF,
|
||||||
|
expectedStr: "abcde",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// the io.ErrUnexpectedEOF remap must not swallow a real read failure, and the byte count has to
|
||||||
|
// survive it, otherwise callers cannot tell how much of the buffer is trustworthy
|
||||||
|
name: "propagates a failure partway through the buffer",
|
||||||
|
reads: []scriptedRead{{data: "abc"}, {data: "", err: errBoom}},
|
||||||
|
bufSize: 10,
|
||||||
|
expectedN: 3,
|
||||||
|
expectedErr: errBoom,
|
||||||
|
expectedStr: "abc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
reader := &scriptedReadSeeker{reads: tt.reads}
|
||||||
|
adapter := newReaderAtAdapter(reader)
|
||||||
|
|
||||||
|
buf := make([]byte, tt.bufSize)
|
||||||
|
n, err := adapter.ReadAt(buf, 7)
|
||||||
|
|
||||||
|
require.ErrorIs(t, err, tt.expectedErr)
|
||||||
|
assert.Equal(t, tt.expectedN, n)
|
||||||
|
assert.Equal(t, tt.expectedStr, string(buf[:n]))
|
||||||
|
|
||||||
|
// the position must be restored even though the read spanned multiple underlying calls
|
||||||
|
pos, err := adapter.Seek(0, io.SeekCurrent)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Zero(t, pos)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -32,12 +32,15 @@ type parsedData struct {
|
|||||||
pkg.ApkDBEntry
|
pkg.ApkDBEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxApkDBFieldSize = 10 * 1024 * 1024
|
||||||
|
|
||||||
// parseApkDB parses packages from a given APK "installed" flat-file DB. For more
|
// 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.
|
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
|
||||||
//
|
//
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func parseApkDB(ctx context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func parseApkDB(ctx context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
|
scanner.Buffer(nil, maxApkDBFieldSize)
|
||||||
|
|
||||||
var errs error
|
var errs error
|
||||||
var apks []parsedData
|
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) {
|
func Test_parseApkDB_expectedPkgNames(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
fixture string
|
fixture string
|
||||||
|
|||||||
@ -433,6 +433,16 @@ catalogers:
|
|||||||
cpes:
|
cpes:
|
||||||
- cpe:2.3:a:deno:deno:*:*:*:*:*:*:*:*
|
- cpe:2.3:a:deno:deno:*:*:*:*:*:*:*:*
|
||||||
type: BinaryPkg
|
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
|
- method: glob
|
||||||
criteria:
|
criteria:
|
||||||
- '**/ghc*'
|
- '**/ghc*'
|
||||||
|
|||||||
@ -1499,6 +1499,28 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
|||||||
Metadata: metadata("deno-binary"),
|
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",
|
logicalFixture: "deno/1.16.4/linux-amd64",
|
||||||
expected: pkg.Package{
|
expected: pkg.Package{
|
||||||
@ -1565,6 +1587,61 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
|||||||
Metadata: metadata("deno-binary"),
|
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",
|
logicalFixture: "haskell-ghc/7.10.3/linux-amd64",
|
||||||
expected: pkg.Package{
|
expected: pkg.Package{
|
||||||
@ -2536,6 +2613,39 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
|||||||
Metadata: metadata("ingress-nginx-binary"),
|
Metadata: metadata("ingress-nginx-binary"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
logicalFixture: "nginx-ingress-controller/1.9.6/linux-arm64",
|
||||||
|
expected: pkg.Package{
|
||||||
|
Name: "nginx-ingress-controller",
|
||||||
|
Version: "1.9.6",
|
||||||
|
Type: "binary",
|
||||||
|
PURL: "pkg:generic/nginx-ingress-controller@1.9.6",
|
||||||
|
Locations: locations("nginx-ingress-controller"),
|
||||||
|
Metadata: metadata("ingress-nginx-binary"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
logicalFixture: "nginx-ingress-controller/1.9.6/linux-arm",
|
||||||
|
expected: pkg.Package{
|
||||||
|
Name: "nginx-ingress-controller",
|
||||||
|
Version: "1.9.6",
|
||||||
|
Type: "binary",
|
||||||
|
PURL: "pkg:generic/nginx-ingress-controller@1.9.6",
|
||||||
|
Locations: locations("nginx-ingress-controller"),
|
||||||
|
Metadata: metadata("ingress-nginx-binary"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
logicalFixture: "nginx-ingress-controller/1.9.6/linux-s390x",
|
||||||
|
expected: pkg.Package{
|
||||||
|
Name: "nginx-ingress-controller",
|
||||||
|
Version: "1.9.6",
|
||||||
|
Type: "binary",
|
||||||
|
PURL: "pkg:generic/nginx-ingress-controller@1.9.6",
|
||||||
|
Locations: locations("nginx-ingress-controller"),
|
||||||
|
Metadata: metadata("ingress-nginx-binary"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
logicalFixture: "nginx-ingress-controller/1.7.1/linux-amd64",
|
logicalFixture: "nginx-ingress-controller/1.7.1/linux-amd64",
|
||||||
expected: pkg.Package{
|
expected: pkg.Package{
|
||||||
@ -2992,8 +3102,8 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
|
|||||||
Name: "busybox",
|
Name: "busybox",
|
||||||
Version: "1.35.0",
|
Version: "1.35.0",
|
||||||
PURL: "pkg:generic/busybox@1.35.0",
|
PURL: "pkg:generic/busybox@1.35.0",
|
||||||
Locations: locations("/bin/["),
|
Locations: locations("/bin/busybox"),
|
||||||
Metadata: metadata("busybox-binary", "/bin/[", "/bin/busybox"),
|
Metadata: metadata("busybox-binary", "/bin/busybox"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -588,11 +588,20 @@ func DefaultClassifiers() []binutils.Classifier {
|
|||||||
Class: "deno-binary",
|
Class: "deno-binary",
|
||||||
FileGlob: "**/deno",
|
FileGlob: "**/deno",
|
||||||
EvidenceMatcher: binutils.MatchAny(
|
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(
|
m.FileContentsVersionMatcher(
|
||||||
// Deno/2.6.3
|
// Deno/2.6.3
|
||||||
// Deno/1.41.0
|
// Deno/1.41.0
|
||||||
`Deno/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
`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(
|
m.FileContentsVersionMatcher(
|
||||||
// deno::tools::standalonedeno-65db94feba9d4d51a09b74629f566dbc90484fbarelease/v1.29.4windows
|
// deno::tools::standalonedeno-65db94feba9d4d51a09b74629f566dbc90484fbarelease/v1.29.4windows
|
||||||
// cli/tools/standalone.rsdeno-74064c9d8c222b33b2a552ea0af1054f57002a96release/v1.28.3windows
|
// cli/tools/standalone.rsdeno-74064c9d8c222b33b2a552ea0af1054f57002a96release/v1.28.3windows
|
||||||
@ -611,6 +620,20 @@ func DefaultClassifiers() []binutils.Classifier {
|
|||||||
PURL: mustPURL("pkg:generic/deno@version"),
|
PURL: mustPURL("pkg:generic/deno@version"),
|
||||||
CPEs: singleCPE("cpe:2.3:a:deno:deno:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
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",
|
Class: "haskell-ghc-binary",
|
||||||
FileGlob: "**/ghc*",
|
FileGlob: "**/ghc*",
|
||||||
@ -876,6 +899,11 @@ func DefaultClassifiers() []binutils.Classifier {
|
|||||||
Class: "gzip-binary",
|
Class: "gzip-binary",
|
||||||
FileGlob: "**/gzip",
|
FileGlob: "**/gzip",
|
||||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||||
|
// GNU gzip keeps the version as a bare NUL-delimited token, so require an identifying
|
||||||
|
// string from the program as well. Without it any binary that happens to be named gzip
|
||||||
|
// (such as the busybox multicall binary behind a gzip applet symlink) would have an
|
||||||
|
// arbitrary NUL-delimited "N.N" token reported as a GNU gzip version.
|
||||||
|
`%s: %s: not in gzip format`,
|
||||||
`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
|
`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
|
||||||
),
|
),
|
||||||
Package: "gzip",
|
Package: "gzip",
|
||||||
@ -1130,6 +1158,11 @@ func DefaultClassifiers() []binutils.Classifier {
|
|||||||
Class: "ingress-nginx-binary",
|
Class: "ingress-nginx-binary",
|
||||||
FileGlob: "**/nginx-ingress-controller",
|
FileGlob: "**/nginx-ingress-controller",
|
||||||
EvidenceMatcher: binutils.MatchAny(
|
EvidenceMatcher: binutils.MatchAny(
|
||||||
|
// the release is injected with -ldflags -X, which lands it in its own NUL-padded data symbol.
|
||||||
|
// the surrounding bytes are an arch-specific float constant pool, so only the padding is portable.
|
||||||
|
// e.g. v1.9.6[NUL][NUL] on each of linux/amd64, linux/arm, linux/arm64, and linux/s390x
|
||||||
|
// note: one trailing NUL is not enough -- on s390x that matches a vendored "v1.19.0" earlier in the file
|
||||||
|
m.FileContentsVersionMatcher(`v(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9]+)?)\x00\x00`),
|
||||||
// [NUL][NUL]v1.15.1[NUL][NUL]@e[ETX][NUL][NUL][NUL][NUL]go1.26.1[NUL][NUL][NUL]
|
// [NUL][NUL]v1.15.1[NUL][NUL]@e[ETX][NUL][NUL][NUL][NUL]go1.26.1[NUL][NUL][NUL]
|
||||||
// <20>v1.15.1[NUL][NUL]<5D>z[ETX][NUL][NUL][NUL][NUL]go1.24.4[NUL][NUL][NUL]
|
// <20>v1.15.1[NUL][NUL]<5D>z[ETX][NUL][NUL][NUL][NUL]go1.24.4[NUL][NUL][NUL]
|
||||||
m.FileContentsVersionMatcher(`v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+.{0,50}go[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9])?\.[0-9]+\x00+`),
|
m.FileContentsVersionMatcher(`v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+.{0,50}go[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9])?\.[0-9]+\x00+`),
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package binary
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"debug/elf"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -14,6 +13,7 @@ import (
|
|||||||
"github.com/anchore/syft/internal/unknown"
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
@ -146,7 +146,7 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
|
|||||||
return nil, fmt.Errorf("unable to get union reader for binary: %w", err)
|
return nil, fmt.Errorf("unable to get union reader for binary: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := elf.NewFile(unionReader)
|
f, err := elfutil.NewFile(unionReader)
|
||||||
if f == nil || err != nil {
|
if f == nil || err != nil {
|
||||||
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to parse binary as ELF")
|
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to parse binary as ELF")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"go.yaml.in/yaml/v3"
|
"go.yaml.in/yaml/v3"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/binary/internal/manager/internal"
|
"github.com/anchore/syft/syft/pkg/cataloger/binary/internal/manager/internal"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/binary/internal/manager/internal/config"
|
"github.com/anchore/syft/syft/pkg/cataloger/binary/internal/manager/internal/config"
|
||||||
)
|
)
|
||||||
@ -203,7 +204,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getPlatformElf(f *os.File) string {
|
func getPlatformElf(f *os.File) string {
|
||||||
elfFile, err := elf.NewFile(f)
|
elfFile, err := elfutil.NewFile(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -214,6 +215,14 @@ func getPlatformElf(f *os.File) string {
|
|||||||
arch = amd64
|
arch = amd64
|
||||||
case elf.EM_AARCH64:
|
case elf.EM_AARCH64:
|
||||||
arch = arm64
|
arch = arm64
|
||||||
|
case elf.EM_ARM:
|
||||||
|
arch = "arm"
|
||||||
|
case elf.EM_S390:
|
||||||
|
arch = "s390x"
|
||||||
|
case elf.EM_386:
|
||||||
|
arch = "386"
|
||||||
|
case elf.EM_RISCV:
|
||||||
|
arch = "riscv64"
|
||||||
// TODO...
|
// TODO...
|
||||||
default:
|
default:
|
||||||
arch = fmt.Sprintf("unknown-%x", elfFile.Machine)
|
arch = fmt.Sprintf("unknown-%x", elfFile.Machine)
|
||||||
|
|||||||
BIN
syft/pkg/cataloger/binary/testdata/classifiers/negative/gzip
vendored
Normal file
BIN
syft/pkg/cataloger/binary/testdata/classifiers/negative/gzip
vendored
Normal file
Binary file not shown.
12
syft/pkg/cataloger/binary/testdata/classifiers/snippets/bun/0.5.9/linux-amd64/bun
vendored
Normal file
12
syft/pkg/cataloger/binary/testdata/classifiers/snippets/bun/0.5.9/linux-amd64/bun
vendored
Normal file
@ -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
syft/pkg/cataloger/binary/testdata/classifiers/snippets/bun/1.0.0/linux-amd64/bun
vendored
Normal file
12
syft/pkg/cataloger/binary/testdata/classifiers/snippets/bun/1.0.0/linux-amd64/bun
vendored
Normal file
@ -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
syft/pkg/cataloger/binary/testdata/classifiers/snippets/bun/1.1.0/linux-amd64/bun
vendored
Normal file
12
syft/pkg/cataloger/binary/testdata/classifiers/snippets/bun/1.1.0/linux-amd64/bun
vendored
Normal file
@ -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
syft/pkg/cataloger/binary/testdata/classifiers/snippets/bun/1.3.5/linux-amd64/bun
vendored
Normal file
12
syft/pkg/cataloger/binary/testdata/classifiers/snippets/bun/1.3.5/linux-amd64/bun
vendored
Normal file
@ -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
syft/pkg/cataloger/binary/testdata/classifiers/snippets/deno/1.11.3/linux-amd64/deno
vendored
Normal file
9
syft/pkg/cataloger/binary/testdata/classifiers/snippets/deno/1.11.3/linux-amd64/deno
vendored
Normal file
@ -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
syft/pkg/cataloger/binary/testdata/classifiers/snippets/deno/1.11.4/linux-amd64/deno
vendored
Normal file
9
syft/pkg/cataloger/binary/testdata/classifiers/snippets/deno/1.11.4/linux-amd64/deno
vendored
Normal file
@ -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
syft/pkg/cataloger/binary/testdata/classifiers/snippets/deno/2.9.2/linux-amd64/deno
vendored
Normal file
BIN
syft/pkg/cataloger/binary/testdata/classifiers/snippets/deno/2.9.2/linux-amd64/deno
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
34
syft/pkg/cataloger/binary/testdata/config.yaml
vendored
34
syft/pkg/cataloger/binary/testdata/config.yaml
vendored
@ -140,6 +140,22 @@ from-images:
|
|||||||
paths:
|
paths:
|
||||||
- /usr/bin/deno
|
- /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
|
- name: deno
|
||||||
version: 1.16.4
|
version: 1.16.4
|
||||||
images:
|
images:
|
||||||
@ -188,6 +204,14 @@ from-images:
|
|||||||
paths:
|
paths:
|
||||||
- /usr/bin/deno
|
- /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.
|
# haskell:7.10.3 detail information is missing.
|
||||||
- name: haskell-ghc
|
- name: haskell-ghc
|
||||||
version: 7.10.3
|
version: 7.10.3
|
||||||
@ -1546,8 +1570,16 @@ from-images:
|
|||||||
|
|
||||||
- version: 1.9.6
|
- version: 1.9.6
|
||||||
images:
|
images:
|
||||||
- ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:1405cc613bd95b2c6edd8b2a152510ae91c7e62aea4698500d23b2145960ab9c
|
# note: per-platform manifest digests (not the index digest) -- the extraction step creates a
|
||||||
|
# container from the ref alone, so each platform needs its own uniquely addressable ref
|
||||||
|
- ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:0939639a1f338a9eaaa490fd38b4a7881e47a7fd1a473baf8749ce15952b55b8
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
|
- ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:5fb823cc617cbfbee4b250ad5e059c23078ea63e9a7e6acb6d4d30e4456eb0be
|
||||||
|
platform: linux/arm64
|
||||||
|
- ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:dcea8d021e15cf27f59249ee0cfd211559ef9eb60b10651c3af299499848cf8b
|
||||||
|
platform: linux/arm
|
||||||
|
- ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:fc53a604eb2d17fa0cf7a3f266d052c2f32fb97209869057d6826d1815cf622d
|
||||||
|
platform: linux/s390x
|
||||||
paths:
|
paths:
|
||||||
- /nginx-ingress-controller
|
- /nginx-ingress-controller
|
||||||
|
|
||||||
|
|||||||
@ -101,8 +101,8 @@ func parseDotnetPackagesLock(_ context.Context, _ file.Resolver, _ *generic.Envi
|
|||||||
}
|
}
|
||||||
|
|
||||||
rel := artifact.Relationship{
|
rel := artifact.Relationship{
|
||||||
From: parentPkg,
|
From: childPkg,
|
||||||
To: childPkg,
|
To: parentPkg,
|
||||||
Type: artifact.DependencyOfRelationship,
|
Type: artifact.DependencyOfRelationship,
|
||||||
}
|
}
|
||||||
relationships = append(relationships, rel)
|
relationships = append(relationships, rel)
|
||||||
|
|||||||
@ -169,28 +169,28 @@ func TestParseDotnetPackagesLock(t *testing.T) {
|
|||||||
|
|
||||||
expectedRelationships := []artifact.Relationship{
|
expectedRelationships := []artifact.Relationship{
|
||||||
{
|
{
|
||||||
From: autoMapperPkg,
|
From: extensionOptionsPkg,
|
||||||
|
To: autoMapperPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: dependencyInjectionAbstractionsPkg,
|
||||||
To: extensionOptionsPkg,
|
To: extensionOptionsPkg,
|
||||||
Type: artifact.DependencyOfRelationship,
|
Type: artifact.DependencyOfRelationship,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
From: extensionOptionsPkg,
|
|
||||||
To: dependencyInjectionAbstractionsPkg,
|
|
||||||
Type: artifact.DependencyOfRelationship,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
From: extensionOptionsPkg,
|
|
||||||
To: extensionPrimitivesPkg,
|
|
||||||
Type: artifact.DependencyOfRelationship,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
From: extensionPrimitivesPkg,
|
From: extensionPrimitivesPkg,
|
||||||
To: compilerServicesUnsafePkg,
|
To: extensionOptionsPkg,
|
||||||
Type: artifact.DependencyOfRelationship,
|
Type: artifact.DependencyOfRelationship,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
From: microsoftLoggingPkg,
|
From: compilerServicesUnsafePkg,
|
||||||
To: extensionOptionsPkg,
|
To: extensionPrimitivesPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: extensionOptionsPkg,
|
||||||
|
To: microsoftLoggingPkg,
|
||||||
Type: artifact.DependencyOfRelationship,
|
Type: artifact.DependencyOfRelationship,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,7 +156,7 @@ func parseErlangString(data []byte, i *int) (erlangNode, error) {
|
|||||||
}
|
}
|
||||||
if c == '\\' {
|
if c == '\\' {
|
||||||
*i++
|
*i++
|
||||||
if len(data) >= *i {
|
if *i >= len(data) {
|
||||||
return node(nil), fmt.Errorf("invalid escape without closed string at %d", *i)
|
return node(nil), fmt.Errorf("invalid escape without closed string at %d", *i)
|
||||||
}
|
}
|
||||||
c = data[*i]
|
c = data[*i]
|
||||||
|
|||||||
@ -96,6 +96,11 @@ func Test_parseErlang(t *testing.T) {
|
|||||||
{ foo, bar }
|
{ foo, bar }
|
||||||
]}`,
|
]}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "string with an escaped quote",
|
||||||
|
content: `
|
||||||
|
{escaped, ["a\"b"]}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
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())
|
||||||
|
}
|
||||||
|
|||||||
@ -25,8 +25,11 @@ configs: # AUTO-GENERATED - config structs and their fields
|
|||||||
description: NoProxy is a list of glob patterns that match go module names that should not be fetched from the go proxy. When not set, syft will use the GOPRIVATE and GONOPROXY env vars.
|
description: NoProxy is a list of glob patterns that match go module names that should not be fetched from the go proxy. When not set, syft will use the GOPRIVATE and GONOPROXY env vars.
|
||||||
app_key: golang.no-proxy
|
app_key: golang.no-proxy
|
||||||
- key: CaptureSymbols
|
- key: CaptureSymbols
|
||||||
description: CaptureSymbols controls extracting function symbols from the binary symbol table (pclntab). Valid values are "none" (disabled), "stdlib" (only the synthetic stdlib package), and "all" (all module packages plus stdlib).
|
description: CaptureSymbols controls extracting function symbols from the binary symbol table (pclntab). Valid values are "none" (disabled), "stdlib" (only the synthetic stdlib package), "extended-stdlib" (stdlib plus every module under golang.org/x/), and "all" (all module packages plus stdlib).
|
||||||
app_key: golang.capture-symbols
|
app_key: golang.capture-symbols
|
||||||
|
- key: CaptureSymbolsModules
|
||||||
|
description: 'CaptureSymbolsModules is a list of glob patterns (doublestar syntax, where ** crosses path separators and * does not) matched against go module paths. Matching modules get symbols in addition to whatever CaptureSymbols selects, so this can only widen the selection and never narrow it. It has no effect under the "none" scope. A trailing major version suffix is not part of a module''s identity, so a pattern matches with or without it: both "github.com/foo/bar" and "github.com/foo/*" select "github.com/foo/bar/v2". A pattern that spells out a suffix selects only that major version.'
|
||||||
|
app_key: golang.capture-symbols-modules
|
||||||
catalogers:
|
catalogers:
|
||||||
- ecosystem: go # MANUAL
|
- ecosystem: go # MANUAL
|
||||||
name: go-module-binary-cataloger # AUTO-GENERATED
|
name: go-module-binary-cataloger # AUTO-GENERATED
|
||||||
|
|||||||
@ -51,10 +51,20 @@ type CatalogerConfig struct {
|
|||||||
MainModuleVersion MainModuleVersionConfig `yaml:"main-module-version" json:"main-module-version" mapstructure:"main-module-version"`
|
MainModuleVersion MainModuleVersionConfig `yaml:"main-module-version" json:"main-module-version" mapstructure:"main-module-version"`
|
||||||
|
|
||||||
// CaptureSymbols controls extracting function symbols from the binary symbol table (pclntab). Valid values are
|
// CaptureSymbols controls extracting function symbols from the binary symbol table (pclntab). Valid values are
|
||||||
// "none" (disabled), "stdlib" (only the synthetic stdlib package), and "all" (all module packages plus stdlib).
|
// "none" (disabled), "stdlib" (only the synthetic stdlib package), "extended-stdlib" (stdlib plus every module
|
||||||
|
// under golang.org/x/), and "all" (all module packages plus stdlib).
|
||||||
// app-config: golang.capture-symbols
|
// app-config: golang.capture-symbols
|
||||||
CaptureSymbols cataloging.SymbolScope `yaml:"capture-symbols" json:"capture-symbols" mapstructure:"capture-symbols"`
|
CaptureSymbols cataloging.SymbolScope `yaml:"capture-symbols" json:"capture-symbols" mapstructure:"capture-symbols"`
|
||||||
|
|
||||||
|
// CaptureSymbolsModules is a list of glob patterns (doublestar syntax, where ** crosses path separators
|
||||||
|
// and * does not) matched against go module paths. Matching modules get symbols in addition to whatever
|
||||||
|
// CaptureSymbols selects, so this can only widen the selection and never narrow it. It has no effect
|
||||||
|
// under the "none" scope. A trailing major version suffix is not part of a module's identity, so a
|
||||||
|
// pattern matches with or without it: both "github.com/foo/bar" and "github.com/foo/*" select
|
||||||
|
// "github.com/foo/bar/v2". A pattern that spells out a suffix selects only that major version.
|
||||||
|
// app-config: golang.capture-symbols-modules
|
||||||
|
CaptureSymbolsModules []string `yaml:"capture-symbols-modules,omitempty" json:"capture-symbols-modules,omitempty" mapstructure:"capture-symbols-modules"`
|
||||||
|
|
||||||
// Whether to use the golang.org/x/tools/go/packages, which executes golang tooling found on the path in addition to potential network access
|
// Whether to use the golang.org/x/tools/go/packages, which executes golang tooling found on the path in addition to potential network access
|
||||||
UsePackagesLib bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"`
|
UsePackagesLib bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"`
|
||||||
}
|
}
|
||||||
@ -196,6 +206,11 @@ func (g CatalogerConfig) WithCaptureSymbols(input cataloging.SymbolScope) Catalo
|
|||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g CatalogerConfig) WithCaptureSymbolsModules(input []string) CatalogerConfig {
|
||||||
|
g.CaptureSymbolsModules = input
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
func (g CatalogerConfig) WithUsePackagesLib(useLib bool) CatalogerConfig {
|
func (g CatalogerConfig) WithUsePackagesLib(useLib bool) CatalogerConfig {
|
||||||
g.UsePackagesLib = useLib
|
g.UsePackagesLib = useLib
|
||||||
return g
|
return g
|
||||||
|
|||||||
@ -111,8 +111,9 @@ func (c *goLicenseResolver) getLicenses(ctx context.Context, resolver file.Resol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// download from remote sources
|
// download from remote sources; standard library and toolchain paths are not publishable
|
||||||
if c.opts.SearchRemoteLicenses {
|
// module paths, so neither a proxy nor a repository has anything to resolve for them
|
||||||
|
if c.opts.SearchRemoteLicenses && !isStandardImportPath(moduleName) {
|
||||||
pkgLicenses, err = c.getLicensesFromRemote(ctx, moduleName, moduleVersion)
|
pkgLicenses, err = c.getLicensesFromRemote(ctx, moduleName, moduleVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "module", moduleName, "version", moduleVersion).Debug("unable to read golang licenses remote")
|
log.WithFields("error", err, "module", moduleName, "version", moduleVersion).Debug("unable to read golang licenses remote")
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -235,6 +236,35 @@ func Test_LicenseSearch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_remoteLicenseSearchSkipsStandardLibrary(t *testing.T) {
|
||||||
|
ctx := pkgtest.Context(t)
|
||||||
|
|
||||||
|
var requests atomic.Int32
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
requests.Add(1)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// module paths whose first element carries no dot are never publishable, so there is
|
||||||
|
// nothing for a proxy or a repository to resolve
|
||||||
|
for _, moduleName := range []string{"cmd/cgo", "std", "runtime", "internal/abi", "command-line-arguments"} {
|
||||||
|
t.Run(moduleName, func(t *testing.T) {
|
||||||
|
requests.Store(0)
|
||||||
|
|
||||||
|
l := newGoLicenseResolver("", CatalogerConfig{
|
||||||
|
SearchRemoteLicenses: true,
|
||||||
|
Proxies: []string{server.URL},
|
||||||
|
})
|
||||||
|
|
||||||
|
lics := l.getLicenses(ctx, fileresolver.Empty{}, moduleName, "(devel)")
|
||||||
|
|
||||||
|
require.Empty(t, lics)
|
||||||
|
require.Zero(t, requests.Load(), "expected no remote lookup for a standard library module path")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_processCaps(t *testing.T) {
|
func Test_processCaps(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package golang
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"debug/elf"
|
|
||||||
"debug/macho"
|
"debug/macho"
|
||||||
"debug/pe"
|
"debug/pe"
|
||||||
"errors"
|
"errors"
|
||||||
@ -21,8 +20,8 @@ import (
|
|||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/cataloging"
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
@ -51,7 +50,7 @@ const devel = "(devel)"
|
|||||||
type goBinaryCataloger struct {
|
type goBinaryCataloger struct {
|
||||||
licenseResolver goLicenseResolver
|
licenseResolver goLicenseResolver
|
||||||
mainModuleVersion MainModuleVersionConfig
|
mainModuleVersion MainModuleVersionConfig
|
||||||
symbolScope cataloging.SymbolScope
|
symbolSelector symbolSelector
|
||||||
|
|
||||||
// stdlibSymbols holds the standard-library function symbols discovered per binary (keyed by the
|
// stdlibSymbols holds the standard-library function symbols discovered per binary (keyed by the
|
||||||
// binary's location), grouped by import path, populated during parsing and consumed by stdlibProcessor
|
// binary's location), grouped by import path, populated during parsing and consumed by stdlibProcessor
|
||||||
@ -65,7 +64,7 @@ func newGoBinaryCataloger(opts CatalogerConfig) *goBinaryCataloger {
|
|||||||
return &goBinaryCataloger{
|
return &goBinaryCataloger{
|
||||||
licenseResolver: newGoLicenseResolver(binaryCatalogerName, opts),
|
licenseResolver: newGoLicenseResolver(binaryCatalogerName, opts),
|
||||||
mainModuleVersion: opts.MainModuleVersion,
|
mainModuleVersion: opts.MainModuleVersion,
|
||||||
symbolScope: opts.CaptureSymbols,
|
symbolSelector: newSymbolSelector(opts.CaptureSymbols, opts.CaptureSymbolsModules),
|
||||||
stdlibSymbols: make(map[file.Coordinates]map[string][]string),
|
stdlibSymbols: make(map[file.Coordinates]map[string][]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,7 +120,7 @@ func (c *goBinaryCataloger) parseGoBinary(ctx context.Context, resolver file.Res
|
|||||||
}
|
}
|
||||||
defer internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
|
defer internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
|
||||||
|
|
||||||
mods, errs := scanFile(reader.Location, unionReader, c.symbolScope != cataloging.SymbolScopeNone)
|
mods, errs := scanFile(reader.Location, unionReader, c.symbolSelector.enabled())
|
||||||
|
|
||||||
var rels []artifact.Relationship
|
var rels []artifact.Relationship
|
||||||
for _, mod := range mods {
|
for _, mod := range mods {
|
||||||
@ -184,11 +183,10 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Re
|
|||||||
symbolsByModule, stdlibSymbols := moduleSymbols(mod.symbols, &mod.Main, mod.Deps)
|
symbolsByModule, stdlibSymbols := moduleSymbols(mod.symbols, &mod.Main, mod.Deps)
|
||||||
c.recordStdlibSymbols(location.Coordinates, stdlibSymbols)
|
c.recordStdlibSymbols(location.Coordinates, stdlibSymbols)
|
||||||
|
|
||||||
if c.symbolScope != cataloging.SymbolScopeAll {
|
// keep only the modules the selector covers; the main module goes through the same map, so this
|
||||||
// only the "all" scope attaches per-module symbols; for the "stdlib" scope we keep just the
|
// incidentally decides the main module too (which is intended: it is treated like any dependency).
|
||||||
// recorded stdlib symbols. nil map lookups below then yield nil symbol lists for each module.
|
// unselected modules fall out entirely, so the lookups below yield nil rather than an empty map.
|
||||||
symbolsByModule = nil
|
symbolsByModule = c.symbolSelector.filter(symbolsByModule)
|
||||||
}
|
|
||||||
|
|
||||||
var pkgs []pkg.Package
|
var pkgs []pkg.Package
|
||||||
for _, dep := range mod.Deps {
|
for _, dep := range mod.Deps {
|
||||||
@ -400,7 +398,7 @@ func getGOARCHFromBin(r io.ReaderAt) (string, error) {
|
|||||||
var arch string
|
var arch string
|
||||||
switch {
|
switch {
|
||||||
case bytes.HasPrefix(ident, []byte("\x7FELF")):
|
case bytes.HasPrefix(ident, []byte("\x7FELF")):
|
||||||
f, err := elf.NewFile(r)
|
f, err := elfutil.NewFile(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("unrecognized file format: %w", err)
|
return "", fmt.Errorf("unrecognized file format: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,14 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -1441,9 +1443,12 @@ func Test_buildGoPkgInfo_symbolScope(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
scope cataloging.SymbolScope
|
scope cataloging.SymbolScope
|
||||||
|
modules []string
|
||||||
|
extraDeps []*debug.Module
|
||||||
symbols []binarySymbol
|
symbols []binarySymbol
|
||||||
wantMainSyms map[string][]string
|
wantMainSyms map[string][]string
|
||||||
wantDepSyms map[string][]string
|
wantDepSyms map[string][]string
|
||||||
|
wantExtraSyms []map[string][]string
|
||||||
wantStdlibSyms map[string][]string
|
wantStdlibSyms map[string][]string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -1468,6 +1473,84 @@ func Test_buildGoPkgInfo_symbolScope(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantStdlibSyms: map[string][]string{"net/http": {"(*Client).Do"}},
|
wantStdlibSyms: map[string][]string{"net/http": {"(*Client).Do"}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// golang.org/x/net is reached through a vendored import path here, which must still be
|
||||||
|
// attributed to (and selected by) the module that owns it
|
||||||
|
name: "extended-stdlib captures golang.org/x modules and stdlib only",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
extraDeps: extendedDeps,
|
||||||
|
symbols: slices.Concat(populatedSymbols, extendedSymbols),
|
||||||
|
wantExtraSyms: []map[string][]string{
|
||||||
|
{"golang.org/x/net/http2": {"NewClientConn"}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
wantStdlibSyms: map[string][]string{"net/http": {"(*Client).Do"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "module patterns widen extended-stdlib",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/**"},
|
||||||
|
extraDeps: extendedDeps,
|
||||||
|
symbols: slices.Concat(populatedSymbols, extendedSymbols),
|
||||||
|
wantExtraSyms: []map[string][]string{
|
||||||
|
{"golang.org/x/net/http2": {"NewClientConn"}},
|
||||||
|
{"github.com/klauspost/compress/zstd": {"NewReader"}},
|
||||||
|
},
|
||||||
|
wantStdlibSyms: map[string][]string{"net/http": {"(*Client).Do"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "module patterns can select the main module",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/anchore/**"},
|
||||||
|
extraDeps: extendedDeps,
|
||||||
|
symbols: slices.Concat(populatedSymbols, extendedSymbols),
|
||||||
|
// the main package is keyed by the "main" import path the linker assigns, not its real path
|
||||||
|
wantMainSyms: map[string][]string{"main": {"main"}},
|
||||||
|
wantExtraSyms: []map[string][]string{nil, nil},
|
||||||
|
wantStdlibSyms: map[string][]string{"net/http": {"(*Client).Do"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "module patterns are inert under the none scope",
|
||||||
|
scope: cataloging.SymbolScopeNone,
|
||||||
|
modules: []string{"github.com/**", "golang.org/x/**"},
|
||||||
|
extraDeps: extendedDeps,
|
||||||
|
// scanFile never runs under "none", so the build info carries no symbols to begin with
|
||||||
|
symbols: nil,
|
||||||
|
wantExtraSyms: []map[string][]string{nil, nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// all short-circuits ahead of the pattern walk, so a narrow module pattern list cannot subtract
|
||||||
|
// from it. this is also the guard on that short-circuit still existing.
|
||||||
|
name: "module patterns cannot narrow the all scope",
|
||||||
|
scope: cataloging.SymbolScopeAll,
|
||||||
|
modules: []string{"golang.org/x/**"},
|
||||||
|
extraDeps: extendedDeps,
|
||||||
|
symbols: slices.Concat(populatedSymbols, extendedSymbols),
|
||||||
|
wantMainSyms: map[string][]string{"main": {"main"}},
|
||||||
|
wantDepSyms: map[string][]string{
|
||||||
|
"github.com/foo/bar": {"Parse"},
|
||||||
|
"github.com/foo/bar/baz": {"Helper"},
|
||||||
|
},
|
||||||
|
wantExtraSyms: []map[string][]string{
|
||||||
|
{"golang.org/x/net/http2": {"NewClientConn"}},
|
||||||
|
{"github.com/klauspost/compress/zstd": {"NewReader"}},
|
||||||
|
},
|
||||||
|
wantStdlibSyms: map[string][]string{"net/http": {"(*Client).Do"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// a module pattern that restates what the preset already covers must not duplicate or drop anything:
|
||||||
|
// selection is a per-module boolean, so overlap is idempotent
|
||||||
|
name: "a module pattern overlapping the preset changes nothing",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modules: []string{"golang.org/x/**"},
|
||||||
|
extraDeps: extendedDeps,
|
||||||
|
symbols: slices.Concat(populatedSymbols, extendedSymbols),
|
||||||
|
wantExtraSyms: []map[string][]string{
|
||||||
|
{"golang.org/x/net/http2": {"NewClientConn"}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
wantStdlibSyms: map[string][]string{"net/http": {"(*Client).Do"}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -1476,27 +1559,52 @@ func Test_buildGoPkgInfo_symbolScope(t *testing.T) {
|
|||||||
BuildInfo: &debug.BuildInfo{
|
BuildInfo: &debug.BuildInfo{
|
||||||
GoVersion: "go1.22.0",
|
GoVersion: "go1.22.0",
|
||||||
Main: debug.Module{Path: "github.com/anchore/syft", Version: "v1.0.0"},
|
Main: debug.Module{Path: "github.com/anchore/syft", Version: "v1.0.0"},
|
||||||
Deps: []*debug.Module{{Path: "github.com/foo/bar", Version: "v1.2.3"}},
|
Deps: append([]*debug.Module{{Path: "github.com/foo/bar", Version: "v1.2.3"}}, tt.extraDeps...),
|
||||||
},
|
},
|
||||||
arch: "amd64",
|
arch: "amd64",
|
||||||
symbols: tt.symbols,
|
symbols: tt.symbols,
|
||||||
}
|
}
|
||||||
|
|
||||||
c := newGoBinaryCataloger(CatalogerConfig{CaptureSymbols: tt.scope})
|
c := newGoBinaryCataloger(CatalogerConfig{CaptureSymbols: tt.scope, CaptureSymbolsModules: tt.modules})
|
||||||
reader, err := unionreader.GetUnionReader(io.NopCloser(strings.NewReader("")))
|
reader, err := unionreader.GetUnionReader(io.NopCloser(strings.NewReader("")))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mainPkg, pkgs := c.buildGoPkgInfo(context.Background(), fileresolver.Empty{}, location, mod, mod.arch, reader)
|
mainPkg, pkgs := c.buildGoPkgInfo(context.Background(), fileresolver.Empty{}, location, mod, mod.arch, reader)
|
||||||
require.NotNil(t, mainPkg)
|
require.NotNil(t, mainPkg)
|
||||||
require.Len(t, pkgs, 1)
|
require.Len(t, pkgs, 1+len(tt.extraDeps))
|
||||||
|
|
||||||
assert.Equal(t, tt.wantMainSyms, mainPkg.Metadata.(pkg.GolangBinaryBuildinfoEntry).Symbols, "main module symbols")
|
assert.Equal(t, tt.wantMainSyms, mainPkg.Metadata.(pkg.GolangBinaryBuildinfoEntry).Symbols, "main module symbols")
|
||||||
assert.Equal(t, tt.wantDepSyms, pkgs[0].Metadata.(pkg.GolangBinaryBuildinfoEntry).Symbols, "dependency symbols")
|
assert.Equal(t, tt.wantDepSyms, pkgs[0].Metadata.(pkg.GolangBinaryBuildinfoEntry).Symbols, "dependency symbols")
|
||||||
|
for i, want := range tt.wantExtraSyms {
|
||||||
|
assert.Equal(t, want, pkgs[1+i].Metadata.(pkg.GolangBinaryBuildinfoEntry).Symbols, "symbols for %s", pkgs[1+i].Name)
|
||||||
|
}
|
||||||
assert.Equal(t, tt.wantStdlibSyms, c.stdlibSymbolsFor(location.Coordinates), "recorded stdlib symbols")
|
assert.Equal(t, tt.wantStdlibSyms, c.stdlibSymbolsFor(location.Coordinates), "recorded stdlib symbols")
|
||||||
|
|
||||||
|
// a module selected by nothing must carry no symbols key at all: assert on the serialized form,
|
||||||
|
// since an empty (non-nil) map would still emit "symbols":{} despite the omitempty tag
|
||||||
|
for _, p := range append([]pkg.Package{*mainPkg}, pkgs...) {
|
||||||
|
encoded, err := json.Marshal(p.Metadata)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if p.Metadata.(pkg.GolangBinaryBuildinfoEntry).Symbols == nil {
|
||||||
|
assert.NotContains(t, string(encoded), `"symbols"`, "expected no symbols key for %s", p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
extendedDeps = []*debug.Module{
|
||||||
|
{Path: "golang.org/x/net", Version: "v0.30.0"},
|
||||||
|
{Path: "github.com/klauspost/compress", Version: "v1.17.0"},
|
||||||
|
}
|
||||||
|
|
||||||
|
extendedSymbols = []binarySymbol{
|
||||||
|
{packagePath: "vendor/golang.org/x/net/http2", name: "vendor/golang.org/x/net/http2.NewClientConn"},
|
||||||
|
{packagePath: "github.com/klauspost/compress/zstd", name: "github.com/klauspost/compress/zstd.NewReader"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Test_recordStdlibSymbols_merge covers the merge path where the same binary location records stdlib
|
// Test_recordStdlibSymbols_merge covers the merge path where the same binary location records stdlib
|
||||||
// symbols more than once. This happens in production for universal/fat Mach-O binaries: scanFile yields
|
// symbols more than once. This happens in production for universal/fat Mach-O binaries: scanFile yields
|
||||||
// one build info per architecture and each is recorded under the same location coordinates.
|
// one build info per architecture and each is recorded under the same location coordinates.
|
||||||
|
|||||||
@ -72,7 +72,7 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
|
|||||||
|
|
||||||
// only use go.mod packages NOT found in source analysis
|
// only use go.mod packages NOT found in source analysis
|
||||||
goModPackages := c.createGoModPackages(ctx, resolver, modFile, sourceModules, reader, digests)
|
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)
|
c.applyExcludeDirectives(modFile, goModPackages)
|
||||||
|
|
||||||
pkgs = c.assembleResults(catalogedModules, 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
|
// 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 {
|
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)
|
lics := c.licenseResolver.getLicenses(ctx, resolver, m.New.Path, m.New.Version)
|
||||||
var finalPath string
|
var finalPath string
|
||||||
if !strings.HasPrefix(m.New.Path, ".") && !strings.HasPrefix(m.New.Path, "/") {
|
if !strings.HasPrefix(m.New.Path, ".") && !strings.HasPrefix(m.New.Path, "/") {
|
||||||
|
|||||||
44
syft/pkg/cataloger/golang/parse_go_mod_replace_test.go
Normal file
44
syft/pkg/cataloger/golang/parse_go_mod_replace_test.go
Normal file
@ -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"])
|
||||||
|
}
|
||||||
@ -5,12 +5,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kastenhq/goversion/version"
|
"github.com/kastenhq/goversion/version"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/internal/unknown"
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,6 +53,7 @@ func scanFile(location file.Location, reader unionreader.UnionReader, captureSym
|
|||||||
// we can still catalog packages, even if we can't get the crypto information
|
// we can still catalog packages, even if we can't get the crypto information
|
||||||
errs = unknown.Appendf(errs, location, "unable to read golang version info: %w", err)
|
errs = unknown.Appendf(errs, location, "unable to read golang version info: %w", err)
|
||||||
}
|
}
|
||||||
|
v = append(v, getNativeFIPSSettings(bi.Settings)...)
|
||||||
arch := getGOARCH(bi.Settings)
|
arch := getGOARCH(bi.Settings)
|
||||||
if arch == "" {
|
if arch == "" {
|
||||||
arch, err = getGOARCHFromBin(r)
|
arch, err = getGOARCHFromBin(r)
|
||||||
@ -101,6 +104,36 @@ func getCryptoSettingsFromVersion(v version.Version) []string {
|
|||||||
return cryptoSettings
|
return cryptoSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getNativeFIPSSettings(settings []debug.BuildSetting) []string {
|
||||||
|
var cryptoSettings []string
|
||||||
|
for _, s := range settings {
|
||||||
|
switch s.Key {
|
||||||
|
case "GOFIPS140":
|
||||||
|
if s.Value != "" {
|
||||||
|
cryptoSettings = append(cryptoSettings, "GOFIPS140="+s.Value)
|
||||||
|
}
|
||||||
|
case "DefaultGODEBUG":
|
||||||
|
for _, kv := range strings.Split(s.Value, ",") {
|
||||||
|
if setting, val, ok := strings.Cut(kv, "="); ok && setting == "fips140" {
|
||||||
|
cryptoSettings = append(cryptoSettings, "GODEBUG=fips140="+val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cryptoSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBuildInfo bounds the reader before handing it to debug/buildinfo, which opens ELF files with
|
||||||
|
// debug/elf itself rather than through elfutil. elf.NewFile expands the section-name string table as it
|
||||||
|
// parses, so an unbounded read here is reachable no matter how little of the file buildinfo goes on to
|
||||||
|
// look at.
|
||||||
|
func readBuildInfo(r io.ReaderAt) (*debug.BuildInfo, error) {
|
||||||
|
if err := elfutil.CheckSectionNameTable(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buildinfo.Read(r)
|
||||||
|
}
|
||||||
|
|
||||||
func getBuildInfo(r io.ReaderAt, location file.Location) (bi *debug.BuildInfo, err error) {
|
func getBuildInfo(r io.ReaderAt, location file.Location) (bi *debug.BuildInfo, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -112,7 +145,7 @@ func getBuildInfo(r io.ReaderAt, location file.Location) (bi *debug.BuildInfo, e
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// try to read buildinfo from the binary directly
|
// try to read buildinfo from the binary directly
|
||||||
bi, err = buildinfo.Read(r)
|
bi, err = readBuildInfo(r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return bi, nil
|
return bi, nil
|
||||||
}
|
}
|
||||||
@ -123,7 +156,7 @@ func getBuildInfo(r io.ReaderAt, location file.Location) (bi *debug.BuildInfo, e
|
|||||||
log.WithFields("path", location.RealPath).Trace("detected UPX-compressed Go binary, attempting decompression to read the build info")
|
log.WithFields("path", location.RealPath).Trace("detected UPX-compressed Go binary, attempting decompression to read the build info")
|
||||||
decompressed, decompErr := decompressUPX(r)
|
decompressed, decompErr := decompressUPX(r)
|
||||||
if decompErr == nil {
|
if decompErr == nil {
|
||||||
bi, err = buildinfo.Read(decompressed)
|
bi, err = readBuildInfo(decompressed)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return bi, nil
|
return bi, nil
|
||||||
}
|
}
|
||||||
|
|||||||
101
syft/pkg/cataloger/golang/scan_binary_bomb_test.go
Normal file
101
syft/pkg/cataloger/golang/scan_binary_bomb_test.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package golang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"debug/buildinfo"
|
||||||
|
"debug/elf"
|
||||||
|
"encoding/binary"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test_getBuildInfo_compressedSectionBomb covers the reason readBuildInfo exists: debug/buildinfo opens
|
||||||
|
// ELF files with debug/elf itself, and elf.NewFile expands the section-name string table as it parses,
|
||||||
|
// so an oversized compression header there is an unbounded allocation on a path elfutil.NewFile never
|
||||||
|
// sees. The fixture is a real zlib stream, so it delivers every byte its header promises.
|
||||||
|
func Test_getBuildInfo_compressedSectionBomb(t *testing.T) {
|
||||||
|
const declared = 256 << 20 // comfortably over elfutil's bound, small enough to allocate in a test
|
||||||
|
|
||||||
|
bomb := elfWithCompressedNameTable(t, declared)
|
||||||
|
t.Logf("%d byte fixture declares a %d byte section name table", len(bomb), declared)
|
||||||
|
|
||||||
|
// the unguarded path is the thing being defended against: prove the fixture really is a bomb
|
||||||
|
unguarded := measureAlloc(t, func() {
|
||||||
|
_, err := buildinfo.Read(bytes.NewReader(bomb))
|
||||||
|
t.Logf("buildinfo.Read err: %v", err)
|
||||||
|
})
|
||||||
|
assert.Greater(t, unguarded, uint64(declared), "fixture did not actually deliver the declared bytes")
|
||||||
|
|
||||||
|
guarded := measureAlloc(t, func() {
|
||||||
|
_, err := getBuildInfo(bytes.NewReader(bomb), file.NewLocation("bomb"))
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "over the")
|
||||||
|
})
|
||||||
|
assert.Less(t, guarded, uint64(32<<20), "getBuildInfo allocated far more than the input warrants")
|
||||||
|
t.Logf("unguarded allocated %d bytes, guarded allocated %d bytes", unguarded, guarded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func measureAlloc(t *testing.T, fn func()) uint64 {
|
||||||
|
t.Helper()
|
||||||
|
var before, after runtime.MemStats
|
||||||
|
runtime.GC()
|
||||||
|
runtime.ReadMemStats(&before)
|
||||||
|
fn()
|
||||||
|
runtime.ReadMemStats(&after)
|
||||||
|
return after.TotalAlloc - before.TotalAlloc
|
||||||
|
}
|
||||||
|
|
||||||
|
// elfWithCompressedNameTable builds a minimal ELF64 whose only real section is a SHF_COMPRESSED
|
||||||
|
// .shstrtab declaring `declared` decompressed bytes and genuinely delivering them.
|
||||||
|
func elfWithCompressedNameTable(t *testing.T, declared uint64) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// the decompressed name table only has to start with the section names; the rest is padding that
|
||||||
|
// exists purely to make the declared size real
|
||||||
|
payload := make([]byte, declared)
|
||||||
|
copy(payload, "\x00.shstrtab\x00")
|
||||||
|
|
||||||
|
var compressed bytes.Buffer
|
||||||
|
zw := zlib.NewWriter(&compressed)
|
||||||
|
_, err := zw.Write(payload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, zw.Close())
|
||||||
|
|
||||||
|
ehsize := uint64(binary.Size(elf.Header64{}))
|
||||||
|
shentsize := uint64(binary.Size(elf.Section64{}))
|
||||||
|
chdrsize := uint64(binary.Size(elf.Chdr64{}))
|
||||||
|
shoff := ehsize
|
||||||
|
bodyOff := shoff + 2*shentsize
|
||||||
|
|
||||||
|
var ident [16]byte
|
||||||
|
copy(ident[:], elf.ELFMAG)
|
||||||
|
ident[elf.EI_CLASS] = byte(elf.ELFCLASS64)
|
||||||
|
ident[elf.EI_DATA] = byte(elf.ELFDATA2LSB)
|
||||||
|
ident[elf.EI_VERSION] = byte(elf.EV_CURRENT)
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
require.NoError(t, binary.Write(buf, binary.LittleEndian, elf.Header64{
|
||||||
|
Ident: ident, Type: uint16(elf.ET_REL), Machine: uint16(elf.EM_X86_64),
|
||||||
|
Version: uint32(elf.EV_CURRENT), Shoff: shoff, Ehsize: uint16(ehsize),
|
||||||
|
Shentsize: uint16(shentsize), Shnum: 2, Shstrndx: 1,
|
||||||
|
}))
|
||||||
|
// the null section
|
||||||
|
require.NoError(t, binary.Write(buf, binary.LittleEndian, elf.Section64{}))
|
||||||
|
// the name table: sh_size is the on-disk size, so it has to cover the whole zlib stream
|
||||||
|
require.NoError(t, binary.Write(buf, binary.LittleEndian, elf.Section64{
|
||||||
|
Name: 1, Type: uint32(elf.SHT_STRTAB), Flags: uint64(elf.SHF_COMPRESSED),
|
||||||
|
Off: bodyOff, Size: chdrsize + uint64(compressed.Len()), Addralign: 1,
|
||||||
|
}))
|
||||||
|
require.NoError(t, binary.Write(buf, binary.LittleEndian, elf.Chdr64{
|
||||||
|
Type: uint32(elf.COMPRESS_ZLIB), Size: declared, Addralign: 1,
|
||||||
|
}))
|
||||||
|
buf.Write(compressed.Bytes())
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
@ -110,3 +110,50 @@ func Test_getCryptoSettingsFromVersion(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_getNativeFIPSSettings(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
settings []debug.BuildSetting
|
||||||
|
result []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "not set",
|
||||||
|
settings: []debug.BuildSetting{{Key: "GOARCH", Value: "arm64"}},
|
||||||
|
result: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GOFIPS140=off is reported verbatim",
|
||||||
|
settings: []debug.BuildSetting{{Key: "GOFIPS140", Value: "off"}},
|
||||||
|
result: []string{"GOFIPS140=off"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pinned module version with mode on",
|
||||||
|
settings: []debug.BuildSetting{
|
||||||
|
{Key: "GOFIPS140", Value: "v1.0.0"},
|
||||||
|
{Key: "DefaultGODEBUG", Value: "fips140=on"},
|
||||||
|
},
|
||||||
|
result: []string{"GOFIPS140=v1.0.0", "GODEBUG=fips140=on"},
|
||||||
|
},
|
||||||
|
{ // GOFIPS140=latest enables FIPS mode without pinning a module version
|
||||||
|
name: "latest with mode on",
|
||||||
|
settings: []debug.BuildSetting{
|
||||||
|
{Key: "GOFIPS140", Value: "latest"},
|
||||||
|
{Key: "DefaultGODEBUG", Value: "fips140=on"},
|
||||||
|
},
|
||||||
|
result: []string{"GOFIPS140=latest", "GODEBUG=fips140=on"},
|
||||||
|
},
|
||||||
|
{ // fips140 is one entry among many in DefaultGODEBUG
|
||||||
|
name: "fips140 among other godebug defaults",
|
||||||
|
settings: []debug.BuildSetting{
|
||||||
|
{Key: "DefaultGODEBUG", Value: "asynctimerchan=1,fips140=on,tlssha1=1"},
|
||||||
|
},
|
||||||
|
result: []string{"GODEBUG=fips140=on"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
res := getNativeFIPSSettings(tt.settings)
|
||||||
|
assert.ElementsMatch(t, res, tt.result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
113
syft/pkg/cataloger/golang/symbol_selector.go
Normal file
113
syft/pkg/cataloger/golang/symbol_selector.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package golang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
"golang.org/x/mod/module"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/cataloging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scopePatterns maps a capture-symbols scope onto the go module path globs it selects. The "none" and
|
||||||
|
// "all" scopes are intentionally absent: both are answered without matching. The "stdlib" scope selects
|
||||||
|
// no modules at all (the synthetic stdlib package is not a module and is handled separately).
|
||||||
|
var scopePatterns = map[cataloging.SymbolScope][]string{
|
||||||
|
cataloging.SymbolScopeExtendedStdlib: {"golang.org/x/**"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// symbolSelector decides which go module paths get function symbols attached to their metadata. The scope
|
||||||
|
// preset and any user-supplied module patterns are compiled into a single glob list so there is exactly
|
||||||
|
// one matcher answering "does this module path get symbols" (two matchers over the same subject drift).
|
||||||
|
type symbolSelector struct {
|
||||||
|
scope cataloging.SymbolScope
|
||||||
|
patterns []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSymbolSelector(scope cataloging.SymbolScope, modules []string) symbolSelector {
|
||||||
|
// normalize here rather than trusting the caller: the CLI runs Parse in PostLoad, but a library
|
||||||
|
// consumer setting CaptureSymbols directly (or via WithCaptureSymbols) does not, and an unnormalized
|
||||||
|
// value falls through both switches below into stdlib-only capture rather than the intended scope.
|
||||||
|
scope = scope.Parse()
|
||||||
|
|
||||||
|
var patterns []string
|
||||||
|
for _, pattern := range slices.Concat(scopePatterns[scope], modules) {
|
||||||
|
if !doublestar.ValidatePattern(pattern) {
|
||||||
|
// a typo in a filter that decides what gets vulnerability-scanned must not pass quietly:
|
||||||
|
// someone would believe they captured symbols they did not. Drop just this pattern and keep
|
||||||
|
// the rest of the selection in force.
|
||||||
|
log.WithFields("pattern", pattern).Warn("ignoring malformed golang capture-symbols-modules pattern")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
patterns = append(patterns, pattern)
|
||||||
|
}
|
||||||
|
return symbolSelector{scope: scope, patterns: patterns}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enabled reports whether symbols should be extracted from the binary at all.
|
||||||
|
func (s symbolSelector) enabled() bool {
|
||||||
|
return s.scope != cataloging.SymbolScopeNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// selects reports whether the given go module path gets symbols. The binary's own main module is treated
|
||||||
|
// exactly like a dependency.
|
||||||
|
func (s symbolSelector) selects(modulePath string) bool {
|
||||||
|
switch s.scope {
|
||||||
|
case cataloging.SymbolScopeNone:
|
||||||
|
return false
|
||||||
|
case cataloging.SymbolScopeAll:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// a major version suffix is part of a module's path but not part of its identity, so patterns are matched
|
||||||
|
// against the path both with and without it: `github.com/foo/bar` and `github.com/foo/*` each select
|
||||||
|
// `github.com/foo/bar/v2`, which is what whoever wrote either one meant, and a config does not quietly
|
||||||
|
// stop covering a module the day it bumps a major version. Spelling a suffix out in the pattern still
|
||||||
|
// selects that major version alone, since the unsuffixed path cannot match a pattern carrying one.
|
||||||
|
// Only a trailing suffix is a version: in `github.com/foo/v2/bar` the `v2` is an ordinary path element,
|
||||||
|
// and SplitPathVersion leaves it there.
|
||||||
|
unversioned, major, ok := module.SplitPathVersion(modulePath)
|
||||||
|
versioned := ok && major != ""
|
||||||
|
|
||||||
|
for _, pattern := range s.patterns {
|
||||||
|
if globMatches(pattern, modulePath) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if versioned && globMatches(pattern, unversioned) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// globMatches treats a pattern that cannot compile as no match, which newSymbolSelector has already warned about.
|
||||||
|
func globMatches(pattern, modulePath string) bool {
|
||||||
|
matched, err := doublestar.Match(pattern, modulePath)
|
||||||
|
if err != nil {
|
||||||
|
// unreachable: newSymbolSelector rejects (and warns about) patterns that cannot compile
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter drops the entries of a module-path-keyed symbol map that the selector does not select. It returns
|
||||||
|
// nil when nothing is selected so callers attach no symbols map at all (rather than an empty one, which
|
||||||
|
// would defeat the omitempty JSON tag).
|
||||||
|
func (s symbolSelector) filter(byModule map[string]map[string][]string) map[string]map[string][]string {
|
||||||
|
switch s.scope {
|
||||||
|
case cataloging.SymbolScopeNone:
|
||||||
|
return nil
|
||||||
|
case cataloging.SymbolScopeAll:
|
||||||
|
return byModule
|
||||||
|
}
|
||||||
|
for modulePath := range byModule {
|
||||||
|
if !s.selects(modulePath) {
|
||||||
|
delete(byModule, modulePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(byModule) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return byModule
|
||||||
|
}
|
||||||
280
syft/pkg/cataloger/golang/symbol_selector_test.go
Normal file
280
syft/pkg/cataloger/golang/symbol_selector_test.go
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
package golang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/cataloging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_symbolSelector_selects(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
scope cataloging.SymbolScope
|
||||||
|
modules []string
|
||||||
|
modulePath string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "none selects nothing",
|
||||||
|
scope: cataloging.SymbolScopeNone,
|
||||||
|
modulePath: "github.com/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none is inert even with module patterns",
|
||||||
|
scope: cataloging.SymbolScopeNone,
|
||||||
|
modules: []string{"github.com/foo/**"},
|
||||||
|
modulePath: "github.com/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all selects everything",
|
||||||
|
scope: cataloging.SymbolScopeAll,
|
||||||
|
modulePath: "github.com/foo/bar",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stdlib selects no modules",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modulePath: "golang.org/x/crypto",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extended-stdlib matches a golang.org/x module",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modulePath: "golang.org/x/crypto",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ** crosses path separators, which is what lets one pattern cover the whole subtree
|
||||||
|
name: "extended-stdlib matches a nested golang.org/x module",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modulePath: "golang.org/x/tools/gopls",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// the pattern must not match on a bare prefix, only at a path boundary
|
||||||
|
name: "extended-stdlib does not match golang.org/xtra",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modulePath: "golang.org/xtra",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extended-stdlib does not match an unrelated module",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modulePath: "github.com/klauspost/compress",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single star matches one path segment",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/*"},
|
||||||
|
modulePath: "github.com/klauspost/compress",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// a single star does not cross "/", but a major version suffix is not a path segment for this
|
||||||
|
// purpose: the module is matched with the suffix stripped as well, so a config written before a
|
||||||
|
// major bump keeps covering the module after it
|
||||||
|
name: "single star reaches across a major version suffix",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/*"},
|
||||||
|
modulePath: "github.com/klauspost/compress/v2",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single star does not cross a separator that is not a version suffix",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/*"},
|
||||||
|
modulePath: "github.com/klauspost/compress/internal/thing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// only a trailing suffix is a version. here "v2" is an ordinary path element naming the major
|
||||||
|
// subdirectory a nested module lives in, so it is matched literally and ** is the way to reach it
|
||||||
|
name: "a mid-path version-like element is an ordinary segment",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/anchore/*/thing"},
|
||||||
|
modulePath: "github.com/anchore/syft/v2/thing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "doublestar reaches a mid-path version-like element",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/anchore/**/thing"},
|
||||||
|
modulePath: "github.com/anchore/syft/v2/thing",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "doublestar crosses a separator",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/**"},
|
||||||
|
modulePath: "github.com/klauspost/compress/v2",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "an exact path covers every major version of that module",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/compress"},
|
||||||
|
modulePath: "github.com/klauspost/compress/v2",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// spelling the suffix out is how a single major version is targeted: v1's path carries no suffix,
|
||||||
|
// so there is nothing for a suffixed pattern to match
|
||||||
|
name: "a pattern naming a major version selects only that one",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/compress/v2"},
|
||||||
|
modulePath: "github.com/klauspost/compress",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "a pattern naming a major version selects it",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/compress/v2"},
|
||||||
|
modulePath: "github.com/klauspost/compress/v2",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// gopkg.in spells the major version as a .vN suffix on the last element, which SplitPathVersion
|
||||||
|
// understands, so it strips the same way
|
||||||
|
name: "gopkg.in style suffixes strip too",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"gopkg.in/yaml"},
|
||||||
|
modulePath: "gopkg.in/yaml.v2",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// /v1 and /v0 are not valid major version suffixes, so this is not a versioned path at all and
|
||||||
|
// nothing is stripped from it
|
||||||
|
name: "a v1 element is not a version suffix",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/compress"},
|
||||||
|
modulePath: "github.com/klauspost/compress/v1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "module patterns widen a preset",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/**"},
|
||||||
|
modulePath: "github.com/klauspost/compress",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "module patterns cannot narrow a preset",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/**"},
|
||||||
|
modulePath: "golang.org/x/net",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact module path",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{"google.golang.org/grpc"},
|
||||||
|
modulePath: "google.golang.org/grpc",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "an empty module pattern list is a no-op",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
modules: []string{},
|
||||||
|
modulePath: "github.com/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// all short-circuits before any matching, so a module pattern list cannot subtract from it
|
||||||
|
name: "module patterns cannot narrow all",
|
||||||
|
scope: cataloging.SymbolScopeAll,
|
||||||
|
modules: []string{"github.com/klauspost/**"},
|
||||||
|
modulePath: "github.com/foo/bar",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// selection is a per-module boolean, so a pattern overlapping the preset is idempotent
|
||||||
|
name: "a module pattern overlapping the preset is idempotent",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modules: []string{"golang.org/x/**"},
|
||||||
|
modulePath: "golang.org/x/net",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// library consumers set CaptureSymbols directly without going through PostLoad, so the
|
||||||
|
// selector normalizes rather than falling through to stdlib-only capture
|
||||||
|
name: "unnormalized scope is parsed",
|
||||||
|
scope: "All",
|
||||||
|
modulePath: "github.com/foo/bar",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unrecognized scope resolves to none",
|
||||||
|
scope: "bogus",
|
||||||
|
modules: []string{"github.com/foo/**"},
|
||||||
|
modulePath: "github.com/foo/bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, newSymbolSelector(tt.scope, tt.modules).selects(tt.modulePath))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a malformed pattern must be dropped at construction, leaving the remaining patterns in force rather
|
||||||
|
// than aborting the selection or silently matching nothing
|
||||||
|
func Test_symbolSelector_malformedPattern(t *testing.T) {
|
||||||
|
s := newSymbolSelector(cataloging.SymbolScopeExtendedStdlib, []string{"[", "github.com/klauspost/**"})
|
||||||
|
|
||||||
|
require.Equal(t, []string{"golang.org/x/**", "github.com/klauspost/**"}, s.patterns)
|
||||||
|
assert.True(t, s.selects("golang.org/x/net"), "preset still applies")
|
||||||
|
assert.True(t, s.selects("github.com/klauspost/compress"), "valid sibling pattern still applies")
|
||||||
|
assert.False(t, s.selects("github.com/foo"), "malformed pattern selects nothing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_symbolSelector_filter(t *testing.T) {
|
||||||
|
symbols := func() map[string]map[string][]string {
|
||||||
|
return map[string]map[string][]string{
|
||||||
|
"golang.org/x/net": {"golang.org/x/net/http2": {"NewClientConn"}},
|
||||||
|
"github.com/klauspost/compress": {"github.com/klauspost/compress/zstd": {"NewReader"}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
scope cataloging.SymbolScope
|
||||||
|
modules []string
|
||||||
|
want []string // remaining module paths
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "none drops everything",
|
||||||
|
scope: cataloging.SymbolScopeNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stdlib drops every module",
|
||||||
|
scope: cataloging.SymbolScopeStdlib,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extended-stdlib keeps only golang.org/x",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
want: []string{"golang.org/x/net"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "module patterns widen the selection",
|
||||||
|
scope: cataloging.SymbolScopeExtendedStdlib,
|
||||||
|
modules: []string{"github.com/klauspost/**"},
|
||||||
|
want: []string{"golang.org/x/net", "github.com/klauspost/compress"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all keeps everything",
|
||||||
|
scope: cataloging.SymbolScopeAll,
|
||||||
|
want: []string{"golang.org/x/net", "github.com/klauspost/compress"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := newSymbolSelector(tt.scope, tt.modules).filter(symbols())
|
||||||
|
if len(tt.want) == 0 {
|
||||||
|
// nil, not an empty map, so the omitempty JSON tag keeps the field out of output
|
||||||
|
assert.Nil(t, got)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, tt.want, slices.Collect(maps.Keys(got)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,6 @@ package golang
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"debug/elf"
|
|
||||||
"debug/gosym"
|
"debug/gosym"
|
||||||
"debug/macho"
|
"debug/macho"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
@ -11,11 +10,17 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mainPackage is the import path the linker assigns to the binary's main package.
|
// mainPackage is the import path the linker assigns to the binary's main package.
|
||||||
const mainPackage = "main"
|
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.
|
// binarySymbol represents a single function symbol extracted from a go binary's pclntab.
|
||||||
type binarySymbol struct {
|
type binarySymbol struct {
|
||||||
// packagePath is the import path of the package that owns the symbol (e.g. "github.com/foo/bar/internal/baz")
|
// packagePath is the import path of the package that owns the symbol (e.g. "github.com/foo/bar/internal/baz")
|
||||||
@ -52,10 +57,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[fn.Name] = struct{}{}
|
seen[fn.Name] = struct{}{}
|
||||||
syms = append(syms, binarySymbol{
|
syms = append(syms, makeBinarySymbol(fn.Name, fn.PackageName()))
|
||||||
packagePath: fn.PackageName(),
|
|
||||||
name: fn.Name,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug/gosym only exposes top-level functions; functions that the compiler inlined into their
|
// debug/gosym only exposes top-level functions; functions that the compiler inlined into their
|
||||||
@ -71,7 +73,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[name] = struct{}{}
|
seen[name] = struct{}{}
|
||||||
syms = append(syms, binarySymbol{packagePath: pkgPath, name: name})
|
syms = append(syms, makeBinarySymbol(name, pkgPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
return syms, nil
|
return syms, nil
|
||||||
@ -94,6 +96,55 @@ func packagePathFromSymbolName(name string) string {
|
|||||||
return name[:slash+1+dot]
|
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.
|
// 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
|
// "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
|
// corrupt package-path derivation (yielding "foo/bar.Do[net" for the example above). Mirrors
|
||||||
@ -224,7 +275,7 @@ func readPclntab(r io.ReaderAt) (pclntab []byte, textStart uint64, err error) {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(string(ident), "\x7FELF"):
|
case strings.HasPrefix(string(ident), "\x7FELF"):
|
||||||
f, err := elf.NewFile(r)
|
f, err := elfutil.NewFile(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fmt.Errorf("unable to parse ELF binary: %w", err)
|
return nil, 0, fmt.Errorf("unable to parse ELF binary: %w", err)
|
||||||
}
|
}
|
||||||
@ -273,8 +324,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"
|
// "(*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
|
// 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
|
// 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
|
// synthetic "stdlib" package. Vendored packages carry a "vendor/" import-path prefix: such symbols match
|
||||||
// recognizable stdlib import path are dropped.
|
// 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) {
|
func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Module) (byModule map[string]map[string][]string, stdlib map[string][]string) {
|
||||||
if len(symbols) == 0 {
|
if len(symbols) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -302,16 +358,17 @@ func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Mod
|
|||||||
attrPath = main.Path
|
attrPath = main.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
var best string
|
best := findBestMatch(modulePaths, attrPath)
|
||||||
for _, modPath := range modulePaths {
|
|
||||||
if len(modPath) > len(best) && (attrPath == modPath || strings.HasPrefix(attrPath, modPath+"/")) {
|
// the vendor/ prefix is only retained when the owning module itself is named "vendor/...";
|
||||||
best = modPath
|
// 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)
|
local := localSymbolName(sym.name, importPath)
|
||||||
if best == "" {
|
if best == "" {
|
||||||
if importPath != mainPackage && isStandardImportPath(importPath) {
|
if importPath != mainPackage && isStandardImportPath(importPath) { // drop stdlib vendored packages
|
||||||
stdlib[importPath] = append(stdlib[importPath], local)
|
stdlib[importPath] = append(stdlib[importPath], local)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@ -333,11 +390,36 @@ func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Mod
|
|||||||
return results, stdlib
|
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.
|
// 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
|
// "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.
|
// unchanged when it does not carry the expected prefix.
|
||||||
func localSymbolName(name, importPath string) string {
|
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[len(importPath)+1:]
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
|
|||||||
@ -16,6 +16,8 @@ func Test_moduleSymbols(t *testing.T) {
|
|||||||
deps := []*debug.Module{
|
deps := []*debug.Module{
|
||||||
{Path: "github.com/foo/bar"},
|
{Path: "github.com/foo/bar"},
|
||||||
{Path: "github.com/foo/bar/v2"},
|
{Path: "github.com/foo/bar/v2"},
|
||||||
|
{Path: "vendor/github.com/vendored/mod"},
|
||||||
|
{Path: "github.com/vendored/mod"},
|
||||||
nil,
|
nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +76,63 @@ func Test_moduleSymbols(t *testing.T) {
|
|||||||
"runtime": {"main"},
|
"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",
|
name: "duplicate symbols are deduplicated",
|
||||||
symbols: []binarySymbol{
|
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) {
|
func Test_localSymbolName(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -239,6 +350,9 @@ func Test_packagePathFromSymbolName(t *testing.T) {
|
|||||||
// module paths that begin with "go." are not compiler-generated
|
// module paths that begin with "go." are not compiler-generated
|
||||||
{"go.uber.org/zap.(*Logger).Info", "go.uber.org/zap"},
|
{"go.uber.org/zap.(*Logger).Info", "go.uber.org/zap"},
|
||||||
{"go.opentelemetry.io/otel.Tracer", "go.opentelemetry.io/otel"},
|
{"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 {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|||||||
15
syft/pkg/cataloger/golang/testdata/go-source-replacements/go.mod
vendored
Normal file
15
syft/pkg/cataloger/golang/testdata/go-source-replacements/go.mod
vendored
Normal file
@ -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
|
||||||
|
)
|
||||||
6
syft/pkg/cataloger/golang/testdata/go-source-replacements/go.sum
vendored
Normal file
6
syft/pkg/cataloger/golang/testdata/go-source-replacements/go.sum
vendored
Normal file
@ -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=
|
||||||
11
syft/pkg/cataloger/golang/testdata/go-source-replacements/main.go
vendored
Normal file
11
syft/pkg/cataloger/golang/testdata/go-source-replacements/main.go
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"rsc.io/quote"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(quote.Hello())
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"debug/macho"
|
"debug/macho"
|
||||||
"debug/pe"
|
"debug/pe"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"maps"
|
"maps"
|
||||||
@ -22,6 +23,7 @@ import (
|
|||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/cpe"
|
"github.com/anchore/syft/syft/cpe"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
@ -365,7 +367,17 @@ func sharedLibraries(context MatcherContext) ([]string, error) {
|
|||||||
}
|
}
|
||||||
defer internal.CloseAndLogError(contents, context.Location.RealPath)
|
defer internal.CloseAndLogError(contents, context.Location.RealPath)
|
||||||
|
|
||||||
e, _ := elf.NewFile(contents)
|
e, err := elfutil.NewFile(contents)
|
||||||
|
if err != nil {
|
||||||
|
// this function tries ELF, then Mach-O, then PE, so "not an ELF" is the expected case and stays
|
||||||
|
// quiet. debug/elf reports a wrong magic as an *elf.FormatError, but a file too short to hold a
|
||||||
|
// header as a bare EOF, so both count as "not an ELF" here. Anything else means a real ELF was
|
||||||
|
// dropped, and nothing downstream would report it.
|
||||||
|
var fmtErr *elf.FormatError
|
||||||
|
if !errors.As(err, &fmtErr) && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
log.WithFields("file", context.Location.RealPath, "error", err).Debug("unable to parse ELF binary")
|
||||||
|
}
|
||||||
|
}
|
||||||
if e != nil {
|
if e != nil {
|
||||||
symbols, err := e.ImportedLibraries()
|
symbols, err := e.ImportedLibraries()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -32,6 +32,11 @@ func candidateVendorsForPE(p pkg.Package) fieldCandidateSet {
|
|||||||
candidates.addValue("artifex")
|
candidates.addValue("artifex")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if product == "git" || fileDesc == "git setup" || company == "the git development community" {
|
||||||
|
candidates.addValue("git_for_windows_project")
|
||||||
|
candidates.addValue("gitforwindows")
|
||||||
|
}
|
||||||
|
|
||||||
return candidates
|
return candidates
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,5 +64,10 @@ func candidateProductsForPE(p pkg.Package) fieldCandidateSet {
|
|||||||
candidates.addValue("ghostscript")
|
candidates.addValue("ghostscript")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if product == "git" || fileDesc == "git setup" {
|
||||||
|
candidates.addValue("git_for_windows")
|
||||||
|
candidates.addValue("git")
|
||||||
|
}
|
||||||
|
|
||||||
return candidates
|
return candidates
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -184,9 +184,13 @@ func FromPackageAttributes(p pkg.Package) []cpe.CPE {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func candidateTargetSw(p pkg.Package) []string {
|
func candidateTargetSw(p pkg.Package) []string {
|
||||||
if p.Type == pkg.WordpressPluginPkg {
|
switch p.Type {
|
||||||
|
case pkg.WordpressPluginPkg:
|
||||||
return []string{"wordpress"}
|
return []string{"wordpress"}
|
||||||
|
case pkg.RustPkg:
|
||||||
|
return []string{"rust"}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []string{cpe.Any}
|
return []string{cpe.Any}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +221,50 @@ func candidateVendors(p pkg.Package) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vendors = candidateVendorsByType(p, vendors)
|
||||||
|
|
||||||
|
if p.Type == pkg.BinaryPkg && endsWithNumber(p.Name) {
|
||||||
|
// add binary package digit-suffix variations (e.g. Qt5 -> Qt)
|
||||||
|
addBinaryPackageDigitVariations(vendors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should no longer be generating vendor candidates with these values ["" and "*"]
|
||||||
|
// (since CPEs will match any other value)
|
||||||
|
vendors.removeByValue("")
|
||||||
|
vendors.removeByValue("*")
|
||||||
|
|
||||||
|
// try swapping hyphens for underscores, vice versa, and removing separators altogether
|
||||||
|
addDelimiterVariations(vendors)
|
||||||
|
|
||||||
|
// rust vendor name needs to be added after the `addDelimiterVariations` call as `-project` suffix is used otherwise
|
||||||
|
if p.Language == pkg.Rust {
|
||||||
|
vendors.addValue(p.Name + "_project")
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate sub-selections of each candidate based on separators (e.g. jenkins-ci -> [jenkins, jenkins-ci])
|
||||||
|
addAllSubSelections(vendors)
|
||||||
|
|
||||||
|
// add more candidates based on the package info for each vendor candidate
|
||||||
|
for _, vendor := range vendors.uniqueValues() {
|
||||||
|
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, p.Type, p.Name, vendor)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove known mis
|
||||||
|
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, p.Type, p.Name)...)
|
||||||
|
|
||||||
|
uniqueVendors := vendors.uniqueValues()
|
||||||
|
|
||||||
|
// if any known vendor was detected, pick that one.
|
||||||
|
for _, vendor := range uniqueVendors {
|
||||||
|
if knownVendors.Has(vendor) {
|
||||||
|
return []string{vendor}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniqueVendors
|
||||||
|
}
|
||||||
|
|
||||||
|
func candidateVendorsByType(p pkg.Package, vendors fieldCandidateSet) fieldCandidateSet {
|
||||||
switch p.Metadata.(type) {
|
switch p.Metadata.(type) {
|
||||||
case pkg.DotnetDepsEntry, pkg.DotnetPackagesLockEntry, pkg.DotnetPortableExecutableEntry:
|
case pkg.DotnetDepsEntry, pkg.DotnetPackagesLockEntry, pkg.DotnetPortableExecutableEntry:
|
||||||
vendors.clear()
|
vendors.clear()
|
||||||
@ -240,41 +288,7 @@ func candidateVendors(p pkg.Package) []string {
|
|||||||
vendors.clear()
|
vendors.clear()
|
||||||
vendors.union(candidateVendorsForWordpressPlugin(p))
|
vendors.union(candidateVendorsForWordpressPlugin(p))
|
||||||
}
|
}
|
||||||
|
return vendors
|
||||||
if p.Type == pkg.BinaryPkg && endsWithNumber(p.Name) {
|
|
||||||
// add binary package digit-suffix variations (e.g. Qt5 -> Qt)
|
|
||||||
addBinaryPackageDigitVariations(vendors)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should no longer be generating vendor candidates with these values ["" and "*"]
|
|
||||||
// (since CPEs will match any other value)
|
|
||||||
vendors.removeByValue("")
|
|
||||||
vendors.removeByValue("*")
|
|
||||||
|
|
||||||
// try swapping hyphens for underscores, vice versa, and removing separators altogether
|
|
||||||
addDelimiterVariations(vendors)
|
|
||||||
|
|
||||||
// generate sub-selections of each candidate based on separators (e.g. jenkins-ci -> [jenkins, jenkins-ci])
|
|
||||||
addAllSubSelections(vendors)
|
|
||||||
|
|
||||||
// add more candidates based on the package info for each vendor candidate
|
|
||||||
for _, vendor := range vendors.uniqueValues() {
|
|
||||||
vendors.addValue(findAdditionalVendors(defaultCandidateAdditions, p.Type, p.Name, vendor)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove known mis
|
|
||||||
vendors.removeByValue(findVendorsToRemove(defaultCandidateRemovals, p.Type, p.Name)...)
|
|
||||||
|
|
||||||
uniqueVendors := vendors.uniqueValues()
|
|
||||||
|
|
||||||
// if any known vendor was detected, pick that one.
|
|
||||||
for _, vendor := range uniqueVendors {
|
|
||||||
if knownVendors.Has(vendor) {
|
|
||||||
return []string{vendor}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return uniqueVendors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func candidateProducts(p pkg.Package) []string {
|
func candidateProducts(p pkg.Package) []string {
|
||||||
|
|||||||
@ -855,6 +855,25 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: []string{},
|
expected: []string{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "rust package",
|
||||||
|
p: pkg.Package{
|
||||||
|
Name: "rust-package",
|
||||||
|
Version: "0.5.0",
|
||||||
|
Type: pkg.RustPkg,
|
||||||
|
Language: pkg.Rust,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"cpe:2.3:a:rust-package_project:rust-package:0.5.0:*:*:*:*:rust:*:*",
|
||||||
|
"cpe:2.3:a:rust-package_project:rust_package:0.5.0:*:*:*:*:rust:*:*",
|
||||||
|
"cpe:2.3:a:rust-package:rust-package:0.5.0:*:*:*:*:rust:*:*",
|
||||||
|
"cpe:2.3:a:rust-package:rust_package:0.5.0:*:*:*:*:rust:*:*",
|
||||||
|
"cpe:2.3:a:rust:rust-package:0.5.0:*:*:*:*:rust:*:*",
|
||||||
|
"cpe:2.3:a:rust:rust_package:0.5.0:*:*:*:*:rust:*:*",
|
||||||
|
"cpe:2.3:a:rust_package:rust-package:0.5.0:*:*:*:*:rust:*:*",
|
||||||
|
"cpe:2.3:a:rust_package:rust_package:0.5.0:*:*:*:*:rust:*:*",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@ -1921,4 +1921,16 @@ var DefaultArtifactIDToGroupID = map[string]string{
|
|||||||
"kafka_2.8.2": "org.apache.kafka",
|
"kafka_2.8.2": "org.apache.kafka",
|
||||||
"kafka_2.9.1": "org.apache.kafka",
|
"kafka_2.9.1": "org.apache.kafka",
|
||||||
"kafka_2.9.2": "org.apache.kafka",
|
"kafka_2.9.2": "org.apache.kafka",
|
||||||
|
|
||||||
|
// legacy "Jackson 1.x" (aka "jackson-asl") artifacts predate the convention of embedding
|
||||||
|
// META-INF/maven/.../pom.properties in the jar, so groupIDFromKnownPackageList is the only
|
||||||
|
// way to recover the correct group ID for jars built without that metadata (e.g. Ant-built
|
||||||
|
// jars from before ~2014). Without this, the group ID falls back to the artifact name itself,
|
||||||
|
// producing purls that do not match the vulnerability database's namespace.
|
||||||
|
// See https://github.com/anchore/syft/issues/4598
|
||||||
|
"jackson-core-asl": "org.codehaus.jackson",
|
||||||
|
"jackson-mapper-asl": "org.codehaus.jackson",
|
||||||
|
"jackson-jaxrs": "org.codehaus.jackson",
|
||||||
|
"jackson-xc": "org.codehaus.jackson",
|
||||||
|
"jackson-smile": "org.codehaus.jackson",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,49 @@
|
|||||||
package cpegenerate
|
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 {
|
func candidateVendorsForRPM(p pkg.Package) fieldCandidateSet {
|
||||||
vendors := newFieldCandidateSet()
|
vendors := newFieldCandidateSet()
|
||||||
|
var vendor string
|
||||||
|
|
||||||
switch m := p.Metadata.(type) {
|
switch m := p.Metadata.(type) {
|
||||||
case pkg.RpmDBEntry:
|
case pkg.RpmDBEntry:
|
||||||
if m.Vendor != "" {
|
vendor = m.Vendor
|
||||||
vendors.add(fieldCandidate{
|
|
||||||
value: normalizeName(m.Vendor),
|
|
||||||
disallowSubSelections: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case pkg.RpmArchive:
|
case pkg.RpmArchive:
|
||||||
if m.Vendor != "" {
|
vendor = m.Vendor
|
||||||
|
}
|
||||||
|
|
||||||
|
vendor = stripTrailingURL(vendor)
|
||||||
|
if vendor != "" {
|
||||||
vendors.add(fieldCandidate{
|
vendors.add(fieldCandidate{
|
||||||
value: normalizeName(m.Vendor),
|
value: normalizeName(vendor),
|
||||||
disallowSubSelections: true,
|
disallowSubSelections: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return vendors
|
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])
|
||||||
|
}
|
||||||
|
|||||||
60
syft/pkg/cataloger/internal/cpegenerate/rpm_test.go
Normal file
60
syft/pkg/cataloger/internal/cpegenerate/rpm_test.go
Normal file
@ -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())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ func ExtractDepsJSONFromELFBundle(r unionreader.UnionReader) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findBundleHeaderOffsetInELF(r unionreader.UnionReader) (int64, error) {
|
func findBundleHeaderOffsetInELF(r unionreader.UnionReader) (int64, error) {
|
||||||
elfFile, err := elf.NewFile(r)
|
elfFile, err := elfutil.NewFile(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/format/cyclonedxjson"
|
"github.com/anchore/syft/syft/format/cyclonedxjson"
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
@ -141,10 +142,11 @@ func fileError(filename string, err error) (nativeImage, error) {
|
|||||||
// newElf reads a Native Image from an ELF executable.
|
// newElf reads a Native Image from an ELF executable.
|
||||||
func newElf(filename string, r io.ReaderAt) (nativeImage, error) {
|
func newElf(filename string, r io.ReaderAt) (nativeImage, error) {
|
||||||
// First attempt to read an ELF file.
|
// First attempt to read an ELF file.
|
||||||
bi, err := elf.NewFile(r)
|
bi, err := elfutil.NewFile(r)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var fmtErr *elf.FormatError
|
var fmtErr *elf.FormatError
|
||||||
|
// note: a size rejection from elfutil is not an *elf.FormatError, so it takes the branch below
|
||||||
if errors.As(err, &fmtErr) {
|
if errors.As(err, &fmtErr) {
|
||||||
// this is not an elf file
|
// this is not an elf file
|
||||||
log.WithFields("filename", filename, "error", err).Trace("not an ELF binary")
|
log.WithFields("filename", filename, "error", err).Trace("not an ELF binary")
|
||||||
|
|||||||
@ -185,6 +185,23 @@ func Test_groupIDFromJavaMetadata(t *testing.T) {
|
|||||||
metadata: pkg.JavaArchive{},
|
metadata: pkg.JavaArchive{},
|
||||||
expect: "org.springframework.ldap",
|
expect: "org.springframework.ldap",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// regression for github.com/anchore/syft/issues/4598: legacy Jackson 1.x ("-asl")
|
||||||
|
// jars built before ~2014 have no embedded pom.properties, so without the known
|
||||||
|
// package list the group ID falls back to the artifact name itself, producing a
|
||||||
|
// purl that doesn't match the vulnerability database's namespace (e.g. the correct
|
||||||
|
// group for jackson-mapper-asl is org.codehaus.jackson, not jackson-mapper-asl).
|
||||||
|
name: "known package list jackson-mapper-asl",
|
||||||
|
pkgName: "jackson-mapper-asl",
|
||||||
|
metadata: pkg.JavaArchive{},
|
||||||
|
expect: "org.codehaus.jackson",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "known package list jackson-core-asl",
|
||||||
|
pkgName: "jackson-core-asl",
|
||||||
|
metadata: pkg.JavaArchive{},
|
||||||
|
expect: "org.codehaus.jackson",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "java manifest",
|
name: "java manifest",
|
||||||
metadata: pkg.JavaArchive{
|
metadata: pkg.JavaArchive{
|
||||||
|
|||||||
@ -31,6 +31,7 @@ type lockDependency struct {
|
|||||||
Resolved string `json:"resolved"`
|
Resolved string `json:"resolved"`
|
||||||
Integrity string `json:"integrity"`
|
Integrity string `json:"integrity"`
|
||||||
Dev bool `json:"dev"`
|
Dev bool `json:"dev"`
|
||||||
|
Dependencies map[string]lockDependency `json:"dependencies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type lockPackage struct {
|
type lockPackage struct {
|
||||||
@ -77,14 +78,7 @@ func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolve
|
|||||||
}
|
}
|
||||||
|
|
||||||
if lock.LockfileVersion == 1 {
|
if lock.LockfileVersion == 1 {
|
||||||
for name, pkgMeta := range lock.Dependencies {
|
pkgs = append(pkgs, a.packageLockV1Packages(ctx, resolver, reader.Location, 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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if lock.LockfileVersion == 2 || lock.LockfileVersion == 3 {
|
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")
|
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) {
|
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||||
// The license field could be either a string or an array.
|
// 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)
|
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) {
|
func TestParsePackageLockV2(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
fixture := "testdata/pkg-lock/package-lock-2.json"
|
fixture := "testdata/pkg-lock/package-lock-2.json"
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package javascript
|
|||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"iter"
|
"iter"
|
||||||
@ -33,8 +34,10 @@ type pnpmPackage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pnpmLockfileParser defines the interface for parsing different versions of pnpm lockfiles.
|
// pnpmLockfileParser defines the interface for parsing different versions of pnpm lockfiles.
|
||||||
|
// Implementations decode into themselves, so a parser is single-use: construct a new one
|
||||||
|
// per document rather than reusing one across a multi-document stream.
|
||||||
type pnpmLockfileParser interface {
|
type pnpmLockfileParser interface {
|
||||||
Parse(version float64, data []byte) ([]pnpmPackage, error)
|
Parse(version float64, doc *yaml.Node) ([]pnpmPackage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type pnpmV6PackageEntry struct {
|
type pnpmV6PackageEntry struct {
|
||||||
@ -81,11 +84,13 @@ func newGenericPnpmLockAdapter(cfg CatalogerConfig) genericPnpmLockAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse implements the pnpmLockfileParser interface for v6-v8 lockfiles.
|
// Parse implements the pnpmLockfileParser interface for v6-v8 lockfiles.
|
||||||
func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, error) {
|
func (p *pnpmV6LockYaml) Parse(version float64, doc *yaml.Node) ([]pnpmPackage, error) {
|
||||||
if err := yaml.Unmarshal(data, p); err != nil {
|
if err := doc.Decode(p); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal pnpm v6 lockfile: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal pnpm v6 lockfile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isV5 := version < 6.0
|
||||||
|
|
||||||
packages := make(map[string]pnpmPackage)
|
packages := make(map[string]pnpmPackage)
|
||||||
|
|
||||||
// Direct dependencies — use sorted keys for deterministic output
|
// Direct dependencies — use sorted keys for deterministic output
|
||||||
@ -95,6 +100,9 @@ func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, err
|
|||||||
log.WithFields("package", name, "error", err).Trace("unable to parse pnpm dependency")
|
log.WithFields("package", name, "error", err).Trace("unable to parse pnpm dependency")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if isV5 {
|
||||||
|
ver = stripPnpmV5PeerSuffix(ver)
|
||||||
|
}
|
||||||
key := name + "@" + ver
|
key := name + "@" + ver
|
||||||
packages[key] = pnpmPackage{Name: name, Version: ver}
|
packages[key] = pnpmPackage{Name: name, Version: ver}
|
||||||
}
|
}
|
||||||
@ -111,6 +119,9 @@ func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, err
|
|||||||
log.WithFields("key", key).Trace("unable to parse pnpm package key")
|
log.WithFields("key", key).Trace("unable to parse pnpm package key")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if isV5 {
|
||||||
|
ver = stripPnpmV5PeerSuffix(ver)
|
||||||
|
}
|
||||||
pkgKey := name + "@" + ver
|
pkgKey := name + "@" + ver
|
||||||
|
|
||||||
integrity := ""
|
integrity := ""
|
||||||
@ -121,6 +132,9 @@ func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, err
|
|||||||
dependencies := make(map[string]string)
|
dependencies := make(map[string]string)
|
||||||
for depName, depVersion := range sortedIter(pkgInfo.Dependencies) {
|
for depName, depVersion := range sortedIter(pkgInfo.Dependencies) {
|
||||||
var normalizedVersion = strings.SplitN(depVersion, "(", 2)[0]
|
var normalizedVersion = strings.SplitN(depVersion, "(", 2)[0]
|
||||||
|
if isV5 {
|
||||||
|
normalizedVersion = stripPnpmV5PeerSuffix(normalizedVersion)
|
||||||
|
}
|
||||||
dependencies[depName] = normalizedVersion
|
dependencies[depName] = normalizedVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +145,8 @@ func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse implements the PnpmLockfileParser interface for v9+ lockfiles.
|
// Parse implements the PnpmLockfileParser interface for v9+ lockfiles.
|
||||||
func (p *pnpmV9LockYaml) Parse(_ float64, data []byte) ([]pnpmPackage, error) {
|
func (p *pnpmV9LockYaml) Parse(_ float64, doc *yaml.Node) ([]pnpmPackage, error) {
|
||||||
if err := yaml.Unmarshal(data, p); err != nil {
|
if err := doc.Decode(p); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal pnpm v9 lockfile: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal pnpm v9 lockfile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,38 +197,117 @@ func newPnpmLockfileParser(version float64) pnpmLockfileParser {
|
|||||||
|
|
||||||
// parsePnpmLock is the main parser function for pnpm-lock.yaml files.
|
// parsePnpmLock is the main parser function for pnpm-lock.yaml files.
|
||||||
func (a genericPnpmLockAdapter) parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func (a genericPnpmLockAdapter) parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
data, err := io.ReadAll(reader) //nolint:gocritic // multi-pass parse requires []byte
|
// pnpm-lock.yaml can be a multi-document YAML stream: pnpm keeps config dependencies
|
||||||
if err != nil {
|
// and the pinned package-manager version in a leading document and the project's
|
||||||
return nil, nil, fmt.Errorf("failed to load pnpm-lock.yaml file: %w", err)
|
// dependency graph in the next one. Reading only the first document yields a
|
||||||
}
|
// well-formed SBOM that contains pnpm's own release binaries and none of the
|
||||||
|
// project's dependencies, with nothing to signal that it is wrong.
|
||||||
|
// See https://github.com/anchore/syft/issues/5168.
|
||||||
|
//
|
||||||
|
// Every document is cataloged, not just the project's. The packages pnpm records for
|
||||||
|
// itself (pnpm and its @pnpm/exe.* release binaries) are really installed on disk, so
|
||||||
|
// they are reported as components like any other dependency. The alternative, keeping
|
||||||
|
// only the last document, would drop them from the SBOM entirely.
|
||||||
|
pnpmPkgs, errs := parsePnpmLockStream(reader)
|
||||||
|
|
||||||
var lockfile struct {
|
// left nil when nothing parses, so a failed lockfile reports no packages rather than an empty set
|
||||||
Version string `yaml:"lockfileVersion"`
|
var packages []pkg.Package
|
||||||
}
|
for _, p := range toSortedSlice(pnpmPkgs) {
|
||||||
if err := yaml.Unmarshal(data, &lockfile); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to parse pnpm-lock.yaml version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
version, err := strconv.ParseFloat(lockfile.Version, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("invalid lockfile version %q: %w", lockfile.Version, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parser := newPnpmLockfileParser(version)
|
|
||||||
pnpmPkgs, err := parser.Parse(version, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to parse pnpm-lock.yaml file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
packages := make([]pkg.Package, 0, len(pnpmPkgs))
|
|
||||||
for _, p := range pnpmPkgs {
|
|
||||||
if p.Dev && !a.cfg.IncludeDevDependencies {
|
if p.Dev && !a.cfg.IncludeDevDependencies {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
packages = append(packages, newPnpmPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version, p.Integrity, p.Dependencies))
|
packages = append(packages, newPnpmPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version, p.Integrity, p.Dependencies))
|
||||||
}
|
}
|
||||||
|
|
||||||
return packages, dependency.Resolve(pnpmLockDependencySpecifier, packages), unknown.IfEmptyf(packages, "unable to determine packages")
|
errs = unknown.Join(errs, unknown.IfEmptyf(packages, "unable to determine packages"))
|
||||||
|
|
||||||
|
return packages, dependency.Resolve(pnpmLockDependencySpecifier, packages), errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePnpmLockStream reads every document of the lockfile stream, keyed by name@version.
|
||||||
|
// A document that fails to parse is reported as unknown rather than discarding the
|
||||||
|
// documents that did parse.
|
||||||
|
func parsePnpmLockStream(reader file.LocationReadCloser) (map[string]pnpmPackage, error) {
|
||||||
|
dec := yaml.NewDecoder(reader)
|
||||||
|
|
||||||
|
pnpmPkgs := make(map[string]pnpmPackage)
|
||||||
|
var firstVersion float64
|
||||||
|
var errs error
|
||||||
|
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
var doc yaml.Node
|
||||||
|
if err := dec.Decode(&doc); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// a malformed document leaves the decoder unusable, so stop reading and keep
|
||||||
|
// what earlier documents contributed rather than dropping the whole lockfile
|
||||||
|
errs = unknown.Appendf(errs, reader, "failed to parse pnpm-lock.yaml document %d: %v", i, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// an empty or comment-only document decodes to a null node; skipping it keeps a
|
||||||
|
// leading separator from failing the stream on a missing lockfileVersion
|
||||||
|
if len(doc.Content) == 0 || doc.Content[0].Tag == "!!null" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := pnpmDocumentVersion(&doc, firstVersion)
|
||||||
|
if err != nil {
|
||||||
|
// no document has yielded a usable version yet, so there is nothing to parse
|
||||||
|
return nil, unknown.Join(errs, err)
|
||||||
|
}
|
||||||
|
if firstVersion == 0 {
|
||||||
|
firstVersion = version
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs, err := newPnpmLockfileParser(version).Parse(version, &doc)
|
||||||
|
if err != nil {
|
||||||
|
errs = unknown.Appendf(errs, reader, "failed to parse pnpm-lock.yaml document %d: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mergePnpmPackages(pnpmPkgs, pkgs, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pnpmPkgs, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// pnpmDocumentVersion reads lockfileVersion from a single document of the stream.
|
||||||
|
// Documents after the first are expected to repeat it, but one that omits it inherits the
|
||||||
|
// version already established rather than being dropped.
|
||||||
|
func pnpmDocumentVersion(doc *yaml.Node, established float64) (float64, error) {
|
||||||
|
var lockfile struct {
|
||||||
|
Version string `yaml:"lockfileVersion"`
|
||||||
|
}
|
||||||
|
if err := doc.Decode(&lockfile); err != nil {
|
||||||
|
if established != 0 {
|
||||||
|
return established, nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("failed to parse pnpm-lock.yaml version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := strconv.ParseFloat(lockfile.Version, 64)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return version, nil
|
||||||
|
case established != 0:
|
||||||
|
return established, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("invalid lockfile version %q: %w", lockfile.Version, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergePnpmPackages folds one document's packages into the running set. The whole stream
|
||||||
|
// follows a single collision rule, the same one used within a document: the last entry to
|
||||||
|
// appear wins.
|
||||||
|
func mergePnpmPackages(into map[string]pnpmPackage, pkgs []pnpmPackage, doc int) {
|
||||||
|
for _, p := range pkgs {
|
||||||
|
key := p.Name + "@" + p.Version
|
||||||
|
if existing, ok := into[key]; ok && existing.Integrity != "" && p.Integrity != "" && existing.Integrity != p.Integrity {
|
||||||
|
log.WithFields("package", key, "document", doc).Trace("conflicting integrity across pnpm-lock.yaml documents")
|
||||||
|
}
|
||||||
|
into[key] = p
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseVersionField extracts the version string from a dependency entry.
|
// parseVersionField extracts the version string from a dependency entry.
|
||||||
@ -233,6 +326,22 @@ func parseVersionField(name string, info any) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stripPnpmV5PeerSuffix removes the underscore-delimited peer dependency suffix used by
|
||||||
|
// pnpm v5 lockfiles, e.g. "5.3.2_acorn@8.8.0" or "4.10.0_fzn43tb6bdtdxy2s3aqevve2su" -> "5.3.2" / "4.10.0".
|
||||||
|
// Lockfile v6+ encodes the same information in parentheses, which is stripped separately.
|
||||||
|
// Only values that look like a registry version (leading digit) are stripped, so that
|
||||||
|
// link:/file:/git specifiers are left untouched.
|
||||||
|
func stripPnpmV5PeerSuffix(version string) string {
|
||||||
|
idx := strings.Index(version, "_")
|
||||||
|
if idx <= 0 {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
if version[0] < '0' || version[0] > '9' {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
return version[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
// parsePnpmPackageKey extracts the package name and version from a lockfile package key.
|
// parsePnpmPackageKey extracts the package name and version from a lockfile package key.
|
||||||
// Handles formats like:
|
// Handles formats like:
|
||||||
// - /@babel/runtime/7.16.7
|
// - /@babel/runtime/7.16.7
|
||||||
|
|||||||
@ -6,10 +6,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.yaml.in/yaml/v3"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
@ -307,6 +310,66 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
|||||||
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParsePnpmLockV5PeerSuffix(t *testing.T) {
|
||||||
|
// pnpm v5 lockfiles encode resolved peer dependencies as an underscore-delimited
|
||||||
|
// suffix of the dependency path, either readable (e.g. "5.3.2_acorn@8.8.0") or
|
||||||
|
// hashed (e.g. "4.10.0_fzn43tb6bdtdxy2s3aqevve2su"). The suffix must be stripped
|
||||||
|
// from reported versions, just like the parenthesized form in v6+ lockfiles.
|
||||||
|
fixture := "testdata/pnpm-v5-peer-suffix/pnpm-lock.yaml"
|
||||||
|
|
||||||
|
locationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||||
|
|
||||||
|
expectedPkgs := []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "acorn",
|
||||||
|
Version: "8.8.0",
|
||||||
|
PURL: "pkg:npm/acorn@8.8.0",
|
||||||
|
Locations: locationSet,
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.PnpmLockEntry{
|
||||||
|
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w=="},
|
||||||
|
Dependencies: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "acorn-jsx",
|
||||||
|
Version: "5.3.2",
|
||||||
|
PURL: "pkg:npm/acorn-jsx@5.3.2",
|
||||||
|
Locations: locationSet,
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.PnpmLockEntry{
|
||||||
|
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="},
|
||||||
|
Dependencies: map[string]string{
|
||||||
|
"acorn": "8.8.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "webpack-cli",
|
||||||
|
Version: "4.10.0",
|
||||||
|
PURL: "pkg:npm/webpack-cli@4.10.0",
|
||||||
|
Locations: locationSet,
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.PnpmLockEntry{
|
||||||
|
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w=="},
|
||||||
|
Dependencies: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expectedRelationships := []artifact.Relationship{
|
||||||
|
{
|
||||||
|
From: expectedPkgs[0],
|
||||||
|
To: expectedPkgs[1],
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
|
||||||
|
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParsePnpmLockV9(t *testing.T) {
|
func TestParsePnpmLockV9(t *testing.T) {
|
||||||
var expectedRelationships []artifact.Relationship
|
var expectedRelationships []artifact.Relationship
|
||||||
fixture := "testdata/pnpm-v9/pnpm-lock.yaml"
|
fixture := "testdata/pnpm-v9/pnpm-lock.yaml"
|
||||||
@ -527,6 +590,15 @@ func Test_corruptPnpmLock(t *testing.T) {
|
|||||||
TestParser(t, adapter.parsePnpmLock)
|
TestParser(t, adapter.parsePnpmLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// yamlDocument decodes a single YAML document for the parsers, which take the node the
|
||||||
|
// lockfile stream decoder hands them rather than raw bytes.
|
||||||
|
func yamlDocument(t *testing.T, data string) *yaml.Node {
|
||||||
|
t.Helper()
|
||||||
|
var doc yaml.Node
|
||||||
|
require.NoError(t, yaml.Unmarshal([]byte(data), &doc))
|
||||||
|
return &doc
|
||||||
|
}
|
||||||
|
|
||||||
func TestParsePnpmLock_DeterministicWithCollidingPeerDeps(t *testing.T) {
|
func TestParsePnpmLock_DeterministicWithCollidingPeerDeps(t *testing.T) {
|
||||||
// this test verifies that when multiple lockfile keys collapse to the same
|
// this test verifies that when multiple lockfile keys collapse to the same
|
||||||
// package key after peer-dep stripping (e.g., pkg@1.0.0(peer-a@1) and
|
// package key after peer-dep stripping (e.g., pkg@1.0.0(peer-a@1) and
|
||||||
@ -535,7 +607,7 @@ func TestParsePnpmLock_DeterministicWithCollidingPeerDeps(t *testing.T) {
|
|||||||
// the last key lexicographically wins.
|
// the last key lexicographically wins.
|
||||||
|
|
||||||
// v9 lockfile with two entries that collapse to the same key
|
// v9 lockfile with two entries that collapse to the same key
|
||||||
lockfileV9 := []byte(`
|
lockfileV9 := `
|
||||||
lockfileVersion: '9.0'
|
lockfileVersion: '9.0'
|
||||||
packages:
|
packages:
|
||||||
some-pkg@1.0.0(peer-b@2.0.0):
|
some-pkg@1.0.0(peer-b@2.0.0):
|
||||||
@ -545,12 +617,12 @@ packages:
|
|||||||
snapshots:
|
snapshots:
|
||||||
some-pkg@1.0.0(peer-b@2.0.0): {}
|
some-pkg@1.0.0(peer-b@2.0.0): {}
|
||||||
some-pkg@1.0.0(peer-a@1.0.0): {}
|
some-pkg@1.0.0(peer-a@1.0.0): {}
|
||||||
`)
|
`
|
||||||
|
|
||||||
// run multiple times to catch nondeterminism
|
// run multiple times to catch nondeterminism
|
||||||
for range 10 {
|
for range 10 {
|
||||||
parser := &pnpmV9LockYaml{}
|
parser := &pnpmV9LockYaml{}
|
||||||
pkgs, err := parser.Parse(9.0, lockfileV9)
|
pkgs, err := parser.Parse(9.0, yamlDocument(t, lockfileV9))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, pkgs, 1, "expected exactly one package after key collision")
|
require.Len(t, pkgs, 1, "expected exactly one package after key collision")
|
||||||
|
|
||||||
@ -561,18 +633,39 @@ snapshots:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// v6 lockfile with two entries that collapse to the same key
|
// v6 lockfile with two entries that collapse to the same key
|
||||||
lockfileV6 := []byte(`
|
lockfileV6 := `
|
||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
packages:
|
packages:
|
||||||
/some-pkg@1.0.0(peer-b@2.0.0):
|
/some-pkg@1.0.0(peer-b@2.0.0):
|
||||||
resolution: {integrity: sha512-BBB}
|
resolution: {integrity: sha512-BBB}
|
||||||
/some-pkg@1.0.0(peer-a@1.0.0):
|
/some-pkg@1.0.0(peer-a@1.0.0):
|
||||||
resolution: {integrity: sha512-AAA}
|
resolution: {integrity: sha512-AAA}
|
||||||
`)
|
`
|
||||||
|
|
||||||
for range 10 {
|
for range 10 {
|
||||||
parser := &pnpmV6LockYaml{}
|
parser := &pnpmV6LockYaml{}
|
||||||
pkgs, err := parser.Parse(6.0, lockfileV6)
|
pkgs, err := parser.Parse(6.0, yamlDocument(t, lockfileV6))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, pkgs, 1, "expected exactly one package after key collision")
|
||||||
|
|
||||||
|
assert.Equal(t, "some-pkg", pkgs[0].Name)
|
||||||
|
assert.Equal(t, "1.0.0", pkgs[0].Version)
|
||||||
|
assert.Equal(t, "sha512-BBB", pkgs[0].Integrity, "expected last lexicographic key to win")
|
||||||
|
}
|
||||||
|
|
||||||
|
// v5 lockfile with two entries that collapse to the same key (underscore peer-dep suffixes)
|
||||||
|
lockfileV5 := `
|
||||||
|
lockfileVersion: 5.4
|
||||||
|
packages:
|
||||||
|
/some-pkg/1.0.0_peer-b@2.0.0:
|
||||||
|
resolution: {integrity: sha512-BBB}
|
||||||
|
/some-pkg/1.0.0_peer-a@1.0.0:
|
||||||
|
resolution: {integrity: sha512-AAA}
|
||||||
|
`
|
||||||
|
|
||||||
|
for range 10 {
|
||||||
|
parser := &pnpmV6LockYaml{}
|
||||||
|
pkgs, err := parser.Parse(5.4, yamlDocument(t, lockfileV5))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, pkgs, 1, "expected exactly one package after key collision")
|
require.Len(t, pkgs, 1, "expected exactly one package after key collision")
|
||||||
|
|
||||||
@ -618,3 +711,201 @@ func setupNpmRegistry() (mux *http.ServeMux, serverURL string, teardown func())
|
|||||||
|
|
||||||
return mux, server.URL, server.Close
|
return mux, server.URL, server.Close
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// covers a real two-document lockfile end to end; parsePnpmLock explains why every
|
||||||
|
// document is cataloged
|
||||||
|
func TestParsePnpmLock_MultiDocument(t *testing.T) {
|
||||||
|
var expectedRelationships []artifact.Relationship
|
||||||
|
fixture := "testdata/pnpm-multi-doc/pnpm-lock.yaml"
|
||||||
|
|
||||||
|
locationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||||
|
|
||||||
|
expectedPkgs := []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "@pnpm/exe.linux-x64",
|
||||||
|
Version: "12.0.0-rc.3",
|
||||||
|
PURL: "pkg:npm/%40pnpm/exe.linux-x64@12.0.0-rc.3",
|
||||||
|
Locations: locationSet,
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.PnpmLockEntry{
|
||||||
|
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-/6xWaYfp6MEaJF+7AZIfCMp/fZX2jZlTLh2KVBEH0QOWk1ktaBlKNIJEgT4m6mieOLhNYMzCfLJImMbJDK1IwA=="},
|
||||||
|
Dependencies: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The project's actual dependency, which lives in the second document.
|
||||||
|
Name: "minimist",
|
||||||
|
Version: "1.2.0",
|
||||||
|
PURL: "pkg:npm/minimist@1.2.0",
|
||||||
|
Locations: locationSet,
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.PnpmLockEntry{
|
||||||
|
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw=="},
|
||||||
|
Dependencies: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "pnpm",
|
||||||
|
Version: "12.0.0-rc.3",
|
||||||
|
PURL: "pkg:npm/pnpm@12.0.0-rc.3",
|
||||||
|
Locations: locationSet,
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.PnpmLockEntry{
|
||||||
|
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-JZ9fDGH+WLdRdTEikN3UxeZe6bpDY7dYwV0RX0+OrJ927vc72XbId4IJXeT++ebNFA9cF3IQ1swiXHEd/Maq0Q=="},
|
||||||
|
Dependencies: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter := newGenericPnpmLockAdapter(CatalogerConfig{IncludeDevDependencies: true})
|
||||||
|
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pnpmDoc renders a minimal v9 lockfile document holding a single package.
|
||||||
|
func pnpmDoc(version, name, ver, integrity string) string {
|
||||||
|
doc := ""
|
||||||
|
if version != "" {
|
||||||
|
doc += "lockfileVersion: '" + version + "'\n"
|
||||||
|
}
|
||||||
|
return doc + "packages:\n " + name + "@" + ver + ":\n resolution: {integrity: " + integrity + "}\nsnapshots:\n " + name + "@" + ver + ": {}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePnpmLock_MultiDocumentStream(t *testing.T) {
|
||||||
|
project := pnpmDoc("9.0", "minimist", "1.2.0", "sha512-AAA")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
lockfile string
|
||||||
|
wantPkgs []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// a stream may open with a separator, which decodes to a null document; it must
|
||||||
|
// not take down the documents that follow it
|
||||||
|
name: "leading comment-only document is skipped",
|
||||||
|
lockfile: "---\n# generated by pnpm\n---\n" + project,
|
||||||
|
wantPkgs: []string{"minimist@1.2.0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trailing separator is skipped",
|
||||||
|
lockfile: project + "---\n",
|
||||||
|
wantPkgs: []string{"minimist@1.2.0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// documents after the first inherit the established lockfileVersion rather than
|
||||||
|
// being dropped for omitting one
|
||||||
|
name: "later document inherits lockfileVersion",
|
||||||
|
lockfile: project + "---\n" + pnpmDoc("", "left-pad", "1.0.0", "sha512-BBB"),
|
||||||
|
wantPkgs: []string{"left-pad@1.0.0", "minimist@1.2.0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// a broken document is reported, but the documents that already parsed are kept
|
||||||
|
name: "corrupt later document keeps earlier packages",
|
||||||
|
lockfile: project + "---\nlockfileVersion: '9.0'\npackages:\n bad@: {oops\n",
|
||||||
|
wantPkgs: []string{"minimist@1.2.0"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single document is unchanged",
|
||||||
|
lockfile: project,
|
||||||
|
wantPkgs: []string{"minimist@1.2.0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty file yields no packages",
|
||||||
|
lockfile: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
adapter := newGenericPnpmLockAdapter(CatalogerConfig{IncludeDevDependencies: true})
|
||||||
|
pkgs, _, err := adapter.parsePnpmLock(context.Background(), nil, nil, file.LocationReadCloser{
|
||||||
|
Location: file.NewLocation("pnpm-lock.yaml"),
|
||||||
|
ReadCloser: io.NopCloser(strings.NewReader(tt.lockfile)),
|
||||||
|
})
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var got []string
|
||||||
|
for _, p := range pkgs {
|
||||||
|
got = append(got, p.Name+"@"+p.Version)
|
||||||
|
}
|
||||||
|
sort.Strings(got)
|
||||||
|
assert.Equal(t, tt.wantPkgs, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_mergePnpmPackages(t *testing.T) {
|
||||||
|
// the whole stream shares one collision rule, the same one used within a document:
|
||||||
|
// the last entry to appear wins
|
||||||
|
first := pnpmPackage{Name: "a", Version: "1.0.0", Integrity: "sha512-AAA", Dev: true, Dependencies: map[string]string{"b": "2.0.0"}}
|
||||||
|
second := pnpmPackage{Name: "a", Version: "1.0.0", Integrity: "sha512-BBB"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
docs [][]pnpmPackage
|
||||||
|
want []pnpmPackage
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "disjoint documents are unioned",
|
||||||
|
docs: [][]pnpmPackage{{first}, {{Name: "c", Version: "3.0.0"}}},
|
||||||
|
want: []pnpmPackage{first, {Name: "c", Version: "3.0.0"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "later document wins on a colliding name@version",
|
||||||
|
docs: [][]pnpmPackage{{first}, {second}},
|
||||||
|
want: []pnpmPackage{second},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same package in one document only",
|
||||||
|
docs: [][]pnpmPackage{{first}},
|
||||||
|
want: []pnpmPackage{first},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := make(map[string]pnpmPackage)
|
||||||
|
for i, doc := range tt.docs {
|
||||||
|
mergePnpmPackages(got, doc, i)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, toSortedSlice(got))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_pnpmDocumentVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
doc string
|
||||||
|
established float64
|
||||||
|
want float64
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "reads its own version", doc: "lockfileVersion: '9.0'\n", want: 9.0},
|
||||||
|
{name: "inherits when omitted", doc: "packages: {}\n", established: 9.0, want: 9.0},
|
||||||
|
{name: "own version beats the established one", doc: "lockfileVersion: '6.0'\n", established: 9.0, want: 6.0},
|
||||||
|
{name: "first document must carry a version", doc: "packages: {}\n", wantErr: true},
|
||||||
|
{name: "first document must carry a usable version", doc: "lockfileVersion: 'nope'\n", wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := pnpmDocumentVersion(yamlDocument(t, tt.doc), tt.established)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
54
syft/pkg/cataloger/javascript/testdata/pkg-lock/nested-package-lock-1.json
vendored
Normal file
54
syft/pkg/cataloger/javascript/testdata/pkg-lock/nested-package-lock-1.json
vendored
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
syft/pkg/cataloger/javascript/testdata/pnpm-multi-doc/pnpm-lock.yaml
generated
vendored
Normal file
57
syft/pkg/cataloger/javascript/testdata/pnpm-multi-doc/pnpm-lock.yaml
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
configDependencies: {}
|
||||||
|
packageManagerDependencies:
|
||||||
|
pnpm:
|
||||||
|
specifier: 12.0.0-rc.3
|
||||||
|
version: 12.0.0-rc.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@pnpm/exe.linux-x64@12.0.0-rc.3':
|
||||||
|
resolution: {integrity: sha512-/6xWaYfp6MEaJF+7AZIfCMp/fZX2jZlTLh2KVBEH0QOWk1ktaBlKNIJEgT4m6mieOLhNYMzCfLJImMbJDK1IwA==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
pnpm@12.0.0-rc.3:
|
||||||
|
resolution: {integrity: sha512-JZ9fDGH+WLdRdTEikN3UxeZe6bpDY7dYwV0RX0+OrJ927vc72XbId4IJXeT++ebNFA9cF3IQ1swiXHEd/Maq0Q==}
|
||||||
|
engines: {node: '>=18.*'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@pnpm/exe.linux-x64@12.0.0-rc.3':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
pnpm@12.0.0-rc.3:
|
||||||
|
optionalDependencies:
|
||||||
|
'@pnpm/exe.linux-x64': 12.0.0-rc.3
|
||||||
|
|
||||||
|
---
|
||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
minimist:
|
||||||
|
specifier: 1.2.0
|
||||||
|
version: 1.2.0
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
minimist@1.2.0:
|
||||||
|
resolution: {integrity: sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
minimist@1.2.0: {}
|
||||||
27
syft/pkg/cataloger/javascript/testdata/pnpm-v5-peer-suffix/pnpm-lock.yaml
generated
vendored
Normal file
27
syft/pkg/cataloger/javascript/testdata/pnpm-v5-peer-suffix/pnpm-lock.yaml
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
|
specifiers:
|
||||||
|
acorn: ^8.8.0
|
||||||
|
acorn-jsx: ^5.3.2
|
||||||
|
webpack-cli: ^4.10.0
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.8.0
|
||||||
|
acorn-jsx: 5.3.2_acorn@8.8.0
|
||||||
|
webpack-cli: 4.10.0_fzn43tb6bdtdxy2s3aqevve2su
|
||||||
|
|
||||||
|
packages:
|
||||||
|
/acorn/8.8.0:
|
||||||
|
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
/acorn-jsx/5.3.2_acorn@8.8.0:
|
||||||
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
|
peerDependencies:
|
||||||
|
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.8.0
|
||||||
|
|
||||||
|
/webpack-cli/4.10.0_fzn43tb6bdtdxy2s3aqevve2su:
|
||||||
|
resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==}
|
||||||
@ -2,7 +2,6 @@ package kernel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"debug/elf"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/anchore/syft/internal/tmpdir"
|
"github.com/anchore/syft/internal/tmpdir"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/elfutil"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
@ -168,7 +168,7 @@ func parseLinuxKernelModuleMetadata(r unionreader.UnionReader) (p *pkg.LinuxKern
|
|||||||
p = &pkg.LinuxKernelModule{
|
p = &pkg.LinuxKernelModule{
|
||||||
Parameters: make(map[string]pkg.LinuxKernelModuleParameter),
|
Parameters: make(map[string]pkg.LinuxKernelModuleParameter),
|
||||||
}
|
}
|
||||||
f, err := elf.NewFile(r)
|
f, err := elfutil.NewFile(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,6 +74,11 @@ func parseRockspec(ctx context.Context, resolver file.Resolver, _ *generic.Envir
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
log.WithFields("path", reader.Path()).Trace("rockspec has no package name, skipping")
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
p := newLuaRocksPackage(
|
p := newLuaRocksPackage(
|
||||||
ctx,
|
ctx,
|
||||||
resolver,
|
resolver,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -144,6 +145,31 @@ func TestParseRockspec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// a build block before the package/version fields must not truncate the parse
|
||||||
|
Fixture: "testdata/rockspec/build-first-1.0-1.rockspec",
|
||||||
|
ExpectedPkg: pkg.Package{
|
||||||
|
Name: "foo",
|
||||||
|
Version: "1.0-1",
|
||||||
|
PURL: "pkg:luarocks/foo@1.0-1",
|
||||||
|
Type: pkg.LuaRocksPkg,
|
||||||
|
Language: pkg.Lua,
|
||||||
|
Licenses: pkg.NewLicenseSet(
|
||||||
|
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("testdata/rockspec/build-first-1.0-1.rockspec")),
|
||||||
|
),
|
||||||
|
Metadata: pkg.LuaRocksPackage{
|
||||||
|
Name: "foo",
|
||||||
|
Version: "1.0-1",
|
||||||
|
License: "MIT",
|
||||||
|
Homepage: "https://github.com/example/foo",
|
||||||
|
Description: "an example rock",
|
||||||
|
URL: "git+https://github.com/example/foo.git",
|
||||||
|
Dependencies: map[string]string{
|
||||||
|
"lua": ">= 5.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -154,6 +180,25 @@ func TestParseRockspec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseRockspec_noPackageName(t *testing.T) {
|
||||||
|
// an empty or whitespace-only rockspec has no package name, so it should
|
||||||
|
// yield no packages and no error rather than a nameless package
|
||||||
|
fixtures := []string{
|
||||||
|
"testdata/rockspec/empty.rockspec",
|
||||||
|
"testdata/rockspec/whitespace-only.rockspec",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fixture := range fixtures {
|
||||||
|
t.Run(fixture, func(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, fixture).
|
||||||
|
WithErrorAssertion(require.NoError).
|
||||||
|
Expects(nil, nil).
|
||||||
|
TestParser(t, parseRockspec)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_corruptRockspec(t *testing.T) {
|
func Test_corruptRockspec(t *testing.T) {
|
||||||
pkgtest.NewCatalogTester().
|
pkgtest.NewCatalogTester().
|
||||||
FromFile(t, "testdata/corrupt/bad-1.23.0-0.rockspec").
|
FromFile(t, "testdata/corrupt/bad-1.23.0-0.rockspec").
|
||||||
|
|||||||
@ -548,11 +548,11 @@ func skipBuildNode(data []byte, i *int) {
|
|||||||
bracesCount--
|
bracesCount--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*i++
|
||||||
|
|
||||||
if bracesCount == 0 {
|
if bracesCount == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
*i++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user