Merge remote-tracking branch 'origin/main' into add-go-symbol-extract

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2025-12-16 13:53:31 -05:00
commit 89824f0ae7
73 changed files with 7157 additions and 325 deletions

View File

@ -2,7 +2,7 @@ tools:
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!) # we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
- name: binny - name: binny
version: version:
want: v0.10.0 want: v0.11.0
method: github-release method: github-release
with: with:
repo: anchore/binny repo: anchore/binny
@ -26,7 +26,7 @@ tools:
# used for linting # used for linting
- name: golangci-lint - name: golangci-lint
version: version:
want: v2.6.2 want: v2.7.2
method: github-release method: github-release
with: with:
repo: golangci/golangci-lint repo: golangci/golangci-lint
@ -42,7 +42,7 @@ tools:
# used for signing the checksums file at release # used for signing the checksums file at release
- name: cosign - name: cosign
version: version:
want: v3.0.2 want: v3.0.3
method: github-release method: github-release
with: with:
repo: sigstore/cosign repo: sigstore/cosign
@ -58,7 +58,7 @@ tools:
# used to release all artifacts # used to release all artifacts
- name: goreleaser - name: goreleaser
version: version:
want: v2.13.0 want: v2.13.1
method: github-release method: github-release
with: with:
repo: goreleaser/goreleaser repo: goreleaser/goreleaser
@ -98,7 +98,7 @@ tools:
# used for triggering a release # used for triggering a release
- name: gh - name: gh
version: version:
want: v2.83.1 want: v2.83.2
method: github-release method: github-release
with: with:
repo: cli/cli repo: cli/cli
@ -114,7 +114,7 @@ tools:
# used to upload test fixture cache # used to upload test fixture cache
- name: yq - name: yq
version: version:
want: v4.49.2 want: v4.50.1
method: github-release method: github-release
with: with:
repo: mikefarah/yq repo: mikefarah/yq

View File

@ -37,7 +37,7 @@ runs:
- name: Restore tool cache - name: Restore tool cache
if: inputs.tools == 'true' if: inputs.tools == 'true'
id: tool-cache id: tool-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with: with:
path: ${{ github.workspace }}/.tool path: ${{ github.workspace }}/.tool
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }} key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }}
@ -63,7 +63,7 @@ runs:
- name: Restore ORAS cache from github actions - name: Restore ORAS cache from github actions
if: inputs.download-test-fixture-cache == 'true' if: inputs.download-test-fixture-cache == 'true'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with: with:
path: ${{ github.workspace }}/.tmp/oras-cache path: ${{ github.workspace }}/.tmp/oras-cache
key: ${{ inputs.cache-key-prefix }}-oras-cache key: ${{ inputs.cache-key-prefix }}-oras-cache

View File

@ -1,11 +1,10 @@
# Description ## Description
Please include a summary of the changes along with any relevant motivation and context, <!-- Please include a summary of the changes along with any relevant motivation and context -->
or link to an issue where this is explained.
<!-- If this completes an issue, please include: --> <!-- If CLI output changed, show an example (before/after if helpful) -->
- Fixes <!-- If this changes application or API configuration, describe new/changed/removed options -->
## Type of change ## Type of change
@ -18,8 +17,12 @@ or link to an issue where this is explained.
- [ ] Chore (improve the developer experience, fix a test flake, etc, without changing the visible behavior of Syft) - [ ] Chore (improve the developer experience, fix a test flake, etc, without changing the visible behavior of Syft)
- [ ] Performance (make Syft run faster or use less memory, without changing visible behavior much) - [ ] Performance (make Syft run faster or use less memory, without changing visible behavior much)
# Checklist: ## Checklist
- [ ] I have added unit tests that cover changed behavior - [ ] I have added unit tests that cover changed behavior
- [ ] I have tested my code in common scenarios and confirmed there are no regressions - [ ] I have tested my code in common scenarios and confirmed there are no regressions
- [ ] I have added comments to my code, particularly in hard-to-understand sections - [ ] I have added comments to my code, particularly in hard-to-understand sections
## Issue references
<!-- If this fixes an issue, include "Fixes #<issue-number>" or otherwise list the issue references -->

View File

@ -47,7 +47,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 #v3.29.5 uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e #v3.29.5
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -58,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@fe4161a26a8629af62121b670040955b330f9af2 #v3.29.5 uses: github/codeql-action/autobuild@1b168cd39490f61582a9beae412bb7057a6b2c4e #v3.29.5
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -72,4 +72,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 #v3.29.5 uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e #v3.29.5

View File

@ -181,7 +181,7 @@ jobs:
# for updating brew formula in anchore/homebrew-syft # for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }} GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
- uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d #v0.20.10 - uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 #v0.20.11
continue-on-error: true continue-on-error: true
with: with:
file: go.mod file: go.mod

View File

@ -31,13 +31,13 @@ jobs:
with: with:
repos: ${{ github.event.inputs.repos }} repos: ${{ github.event.inputs.repos }}
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 #v2.1.4 - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1
id: generate-token id: generate-token
with: with:
app-id: ${{ secrets.TOKEN_APP_ID }} app-id: ${{ secrets.TOKEN_APP_ID }}
private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 #v8.0.0
with: with:
signoff: true signoff: true
delete-branch: true delete-branch: true

View File

@ -45,13 +45,13 @@ jobs:
echo "\`\`\`" echo "\`\`\`"
} >> $GITHUB_STEP_SUMMARY } >> $GITHUB_STEP_SUMMARY
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 #v2.1.4 - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1
id: generate-token id: generate-token
with: with:
app-id: ${{ secrets.TOKEN_APP_ID }} app-id: ${{ secrets.TOKEN_APP_ID }}
private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 #v8.0.0
with: with:
signoff: true signoff: true
delete-branch: true delete-branch: true

View File

@ -46,13 +46,13 @@ jobs:
- name: Push updated CPE cache to registry - name: Push updated CPE cache to registry
run: make generate:cpe-index:cache:push run: make generate:cpe-index:cache:push
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 #v2.1.4 - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1
id: generate-token id: generate-token
with: with:
app-id: ${{ secrets.TOKEN_APP_ID }} app-id: ${{ secrets.TOKEN_APP_ID }}
private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 #v8.0.0
with: with:
signoff: true signoff: true
delete-branch: true delete-branch: true

View File

@ -32,7 +32,7 @@ jobs:
app_id: ${{ secrets.TOKEN_APP_ID }} app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 #v8.0.0
with: with:
signoff: true signoff: true
delete-branch: true delete-branch: true

View File

@ -1,5 +1,9 @@
name: "Validations" name: "Validations"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
@ -90,7 +94,7 @@ jobs:
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach). # why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
# see https://github.com/actions/upload-artifact/issues/199 for more info # see https://github.com/actions/upload-artifact/issues/199 for more info
- name: Upload snapshot artifacts - name: Upload snapshot artifacts
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1
with: with:
# we need to preserve the snapshot data itself as well as the task data that confirms if the # we need to preserve the snapshot data itself as well as the task data that confirms if the
# snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot # snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot
@ -118,7 +122,7 @@ jobs:
- name: Download snapshot build - name: Download snapshot build
id: snapshot-cache id: snapshot-cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1
with: with:
path: | path: |
snapshot snapshot
@ -175,7 +179,7 @@ jobs:
- name: Download snapshot build - name: Download snapshot build
id: snapshot-cache id: snapshot-cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1
with: with:
path: | path: |
snapshot snapshot
@ -225,7 +229,7 @@ jobs:
- name: Download snapshot build - name: Download snapshot build
id: snapshot-cache id: snapshot-cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1
with: with:
path: | path: |
snapshot snapshot
@ -262,7 +266,7 @@ jobs:
- name: Download snapshot build - name: Download snapshot build
id: snapshot-cache id: snapshot-cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1
with: with:
path: | path: |
snapshot snapshot

View File

@ -49,6 +49,12 @@ linters:
- common-false-positives - common-false-positives
- legacy - legacy
- std-error-handling - std-error-handling
rules:
# internal/os contains OS feature detection logic; the name reflects its purpose
- linters:
- revive
path: internal/os/
text: "var-naming: avoid package names that conflict"
paths: paths:
- third_party$ - third_party$
- builtin$ - builtin$

View File

@ -189,7 +189,8 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
WithFromContents(cfg.Golang.MainModuleVersion.FromContents). WithFromContents(cfg.Golang.MainModuleVersion.FromContents).
WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings). WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings).
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags), WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
), ).
WithUsePackagesLib(*multiLevelOption(true, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.UsePackagesLib)),
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)).

View File

@ -16,6 +16,7 @@ type golangConfig struct {
Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"` Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"`
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"` NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
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"`
} }
var _ interface { var _ interface {
@ -37,6 +38,7 @@ if unset this defaults to $GONOPROXY`)
descriptions.Add(&o.MainModuleVersion, `the go main module version discovered from binaries built with the go compiler will descriptions.Add(&o.MainModuleVersion, `the go main module version discovered from binaries built with the go compiler will
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.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`)
@ -64,5 +66,6 @@ func defaultGolangConfig() golangConfig {
FromContents: def.MainModuleVersion.FromContents, FromContents: def.MainModuleVersion.FromContents,
FromBuildSettings: def.MainModuleVersion.FromBuildSettings, FromBuildSettings: def.MainModuleVersion.FromBuildSettings,
}, },
UsePackagesLib: nil, // this defaults to true, which is the API default
} }
} }

28
go.mod
View File

@ -23,7 +23,7 @@ require (
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
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.1.1-0.20250220190351-d62adb6e1115 github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
github.com/anchore/stereoscope v0.1.13 github.com/anchore/stereoscope v0.1.16
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
@ -39,9 +39,9 @@ require (
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.3.4 github.com/github/go-spdx/v2 v2.3.5
github.com/gkampitakis/go-snaps v0.5.18 github.com/gkampitakis/go-snaps v0.5.18
github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-billy/v5 v5.7.0
github.com/go-git/go-git/v5 v5.16.3 github.com/go-git/go-git/v5 v5.16.3
github.com/go-test/deep v1.1.1 github.com/go-test/deep v1.1.1
github.com/go-viper/mapstructure/v2 v2.4.0 github.com/go-viper/mapstructure/v2 v2.4.0
@ -57,7 +57,7 @@ require (
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.7.0 github.com/invopop/jsonschema v0.7.0
github.com/jedib0t/go-pretty/v6 v6.7.5 github.com/jedib0t/go-pretty/v6 v6.7.7
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/magiconair/properties v1.8.10 github.com/magiconair/properties v1.8.10
@ -78,7 +78,7 @@ require (
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb
github.com/spdx/tools-golang v0.5.5 github.com/spdx/tools-golang v0.5.5
github.com/spf13/afero v1.15.0 github.com/spf13/afero v1.15.0
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/vbatts/go-mtree v0.6.0 github.com/vbatts/go-mtree v0.6.0
github.com/vifraa/gopom v1.0.0 github.com/vifraa/gopom v1.0.0
@ -89,8 +89,8 @@ require (
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.4
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
golang.org/x/mod v0.30.0 golang.org/x/mod v0.31.0
golang.org/x/net v0.47.0 golang.org/x/net v0.48.0
modernc.org/sqlite v1.40.1 modernc.org/sqlite v1.40.1
) )
@ -143,7 +143,7 @@ require (
github.com/containerd/typeurl/v2 v2.2.0 // indirect github.com/containerd/typeurl/v2 v2.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/cyphar/filepath-securejoin v0.6.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v29.0.3+incompatible // indirect github.com/docker/cli v29.1.2+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.5.2+incompatible // indirect github.com/docker/docker v28.5.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.4 // indirect github.com/docker/docker-credential-helpers v0.9.4 // indirect
@ -257,14 +257,14 @@ require (
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.45.0 // indirect golang.org/x/crypto v0.46.0 // indirect
golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sync v0.18.0 // indirect golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.38.0 // indirect golang.org/x/sys v0.39.0 // indirect
golang.org/x/term v0.37.0 // indirect golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.14.0 golang.org/x/time v0.14.0
golang.org/x/tools v0.39.0 golang.org/x/tools v0.40.0
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.203.0 // indirect google.golang.org/api v0.203.0 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect

56
go.sum
View File

@ -138,8 +138,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.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY= github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
github.com/anchore/stereoscope v0.1.13 h1:32GKF4+t8j0w+l6aOuEaofkPBLjlVCbsBCiVv3/+8u0= github.com/anchore/stereoscope v0.1.16 h1:kNcEH/B16DTPMaUIzJAFFaDX3N4mj06xnLFZcUC1gaE=
github.com/anchore/stereoscope v0.1.13/go.mod h1:S4FMIyKp6dh2Ez8U44m/+krKuZKb+bVY9SNK345sAKs= github.com/anchore/stereoscope v0.1.16/go.mod h1:bXzc5wCnaJzzTTX5wGyUj2bWfwJbphCYLJfFEqXjbd8=
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=
@ -327,8 +327,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/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.0.3+incompatible h1:8J+PZIcF2xLd6h5sHPsp5pvvJA+Sr2wGQxHkRl53a1E= github.com/docker/cli v29.1.2+incompatible h1:s4QI7drXpIo78OM+CwuthPsO5kCf8cpNsck5PsLVTH8=
github.com/docker/cli v29.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v29.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
@ -390,8 +390,8 @@ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
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.3.4 h1:6VNAsYWvQge+SOeoubTlH81MY21d5uekXNIRGfXMNXo= github.com/github/go-spdx/v2 v2.3.5 h1:rtRQmzDSq2sU/F2oTIvNQQ+6oInq7yxex5npgY//bJQ=
github.com/github/go-spdx/v2 v2.3.4/go.mod h1:7LYNCshU2Gj17qZ0heJ5CQUKWWmpd98K7o93K8fJSMk= github.com/github/go-spdx/v2 v2.3.5/go.mod h1:VziiWwQ/hoGS++2ifYyr/za0Ng9rlgMS+c4U7zckrDs=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-snaps v0.5.18 h1:oZaQoonWI4KX3c9LNSWsxby8SM6EL+mex4KgLjzfIWg= github.com/gkampitakis/go-snaps v0.5.18 h1:oZaQoonWI4KX3c9LNSWsxby8SM6EL+mex4KgLjzfIWg=
@ -402,8 +402,8 @@ 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.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
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.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
@ -614,8 +614,8 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
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.7.5 h1:9dJSWTJnsXJVVAbvxIFxeHf/JxoJd7GUl5o3UzhtuiM= github.com/jedib0t/go-pretty/v6 v6.7.7 h1:Y1Id3lJ3k4UB8uwWWy3l8EVFnUlx5chR5+VbsofPNX0=
github.com/jedib0t/go-pretty/v6 v6.7.5/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= github.com/jedib0t/go-pretty/v6 v6.7.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
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=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@ -877,8 +877,8 @@ github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
@ -1029,8 +1029,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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
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=
@ -1070,8 +1070,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.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
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=
@ -1116,8 +1116,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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
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=
@ -1149,8 +1149,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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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=
@ -1226,13 +1226,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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
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=
@ -1243,8 +1243,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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
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=
@ -1305,8 +1305,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.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
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=

View File

@ -3,10 +3,11 @@ package internal
const ( const (
// JSONSchemaVersion is the current schema version output by the JSON encoder // JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.1.1" JSONSchemaVersion = "16.1.2"
// Changelog // Changelog
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field). // 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
// 16.1.1 - add file executable toolchain and symbol information // 16.1.1 - correct elf package osCpe field according to the document of systemd (also add appCpe field)
// 16.1.2 - add file executable toolchain and symbol information
) )

View File

@ -1,46 +0,0 @@
package file
// GlobMatch evaluates the given glob pattern against the given "name" string, indicating if there is a match or not.
// Source: https://research.swtch.com/glob.go
func GlobMatch(pattern, name string) bool {
px := 0
nx := 0
nextPx := 0
nextNx := 0
for px < len(pattern) || nx < len(name) {
if px < len(pattern) {
c := pattern[px]
switch c {
default: // ordinary character
if nx < len(name) && name[nx] == c {
px++
nx++
continue
}
case '?': // single-character wildcard
if nx < len(name) {
px++
nx++
continue
}
case '*': // zero-or-more-character wildcard
// Try to match at nx.
// If that doesn't work out,
// restart at nx+1 next.
nextPx = px
nextNx = nx + 1
px++
continue
}
}
// Mismatch. Maybe restart.
if 0 < nextNx && nextNx <= len(name) {
px = nextPx
nx = nextNx
continue
}
return false
}
// Matched all of pattern to all of name. Success.
return true
}

View File

@ -1,39 +0,0 @@
package file
import (
"strings"
"testing"
)
func TestGlobMatch(t *testing.T) {
var tests = []struct {
pattern string
data string
ok bool
}{
{"", "", true},
{"x", "", false},
{"", "x", false},
{"abc", "abc", true},
{"*", "abc", true},
{"*c", "abc", true},
{"*b", "abc", false},
{"a*", "abc", true},
{"b*", "abc", false},
{"a*", "a", true},
{"*a", "a", true},
{"a*b*c*d*e*", "axbxcxdxe", true},
{"a*b*c*d*e*", "axbxcxdxexxx", true},
{"a*b?c*x", "abxbbxdbxebxczzx", true},
{"a*b?c*x", "abxbbxdbxebxczzy", false},
{"a*a*a*a*b", strings.Repeat("a", 100), false},
{"*x", "xxx", true},
{"/home/place/**", "/home/place/a/thing", true},
}
for _, test := range tests {
if GlobMatch(test.pattern, test.data) != test.ok {
t.Errorf("failed glob='%s' data='%s'", test.pattern, test.data)
}
}
}

View File

@ -0,0 +1 @@
this file is in a subdirectory

View File

@ -7,14 +7,14 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
var expectedZipArchiveEntries = []string{ var expectedZipArchiveEntries = []string{
"some-dir" + string(os.PathSeparator), "some-dir/",
filepath.Join("some-dir", "a-file.txt"), "some-dir/a-file.txt",
"b-file.txt", "b-file.txt",
"b-file/",
"b-file/in-subdir.txt",
"nested.zip", "nested.zip",
} }
@ -59,12 +59,6 @@ func createZipArchive(t testing.TB, sourceDirPath, destinationArchivePath string
} }
func assertNoError(t testing.TB, fn func() error) func() {
return func() {
assert.NoError(t, fn())
}
}
// setupZipFileTest encapsulates common test setup work for zip file tests. It returns a cleanup function, // setupZipFileTest encapsulates common test setup work for zip file tests. It returns a cleanup function,
// which should be called (typically deferred) by the caller, the path of the created zip archive, and an error, // which should be called (typically deferred) by the caller, the path of the created zip archive, and an error,
// which should trigger a fatal test failure in the consuming test. The returned cleanup function will never be nil // which should trigger a fatal test failure in the consuming test. The returned cleanup function will never be nil

View File

@ -6,6 +6,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/bmatcuk/doublestar/v4"
"github.com/mholt/archives" "github.com/mholt/archives"
"github.com/scylladb/go-set/strset" "github.com/scylladb/go-set/strset"
@ -44,12 +45,17 @@ func (z ZipFileManifest) Add(entry string, info os.FileInfo) {
z[entry] = info z[entry] = info
} }
// GlobMatch returns the path keys that match the given value(s). // GlobMatch returns the path keys to files (not directories) that match the given value(s).
func (z ZipFileManifest) GlobMatch(caseInsensitive bool, patterns ...string) []string { func (z ZipFileManifest) GlobMatch(caseInsensitive bool, patterns ...string) []string {
uniqueMatches := strset.New() uniqueMatches := strset.New()
for _, pattern := range patterns { for _, pattern := range patterns {
for entry := range z { for entry := range z {
fileInfo := z[entry]
if fileInfo != nil && fileInfo.IsDir() {
continue
}
// We want to match globs as if entries begin with a leading slash (akin to an absolute path) // We want to match globs as if entries begin with a leading slash (akin to an absolute path)
// so that glob logic is consistent inside and outside of ZIP archives // so that glob logic is consistent inside and outside of ZIP archives
normalizedEntry := normalizeZipEntryName(caseInsensitive, entry) normalizedEntry := normalizeZipEntryName(caseInsensitive, entry)
@ -57,7 +63,13 @@ func (z ZipFileManifest) GlobMatch(caseInsensitive bool, patterns ...string) []s
if caseInsensitive { if caseInsensitive {
pattern = strings.ToLower(pattern) pattern = strings.ToLower(pattern)
} }
if GlobMatch(pattern, normalizedEntry) {
matches, err := doublestar.Match(pattern, normalizedEntry)
if err != nil {
log.Debugf("error with match pattern '%s', including by default: %v", pattern, err)
matches = true
}
if matches {
uniqueMatches.Add(entry) uniqueMatches.Add(entry)
} }
} }

View File

@ -9,6 +9,8 @@ import (
"os" "os"
"path" "path"
"testing" "testing"
"github.com/stretchr/testify/require"
) )
func TestNewZipFileManifest(t *testing.T) { func TestNewZipFileManifest(t *testing.T) {
@ -107,23 +109,27 @@ func TestZipFileManifest_GlobMatch(t *testing.T) {
cases := []struct { cases := []struct {
glob string glob string
expected string expected []string
}{ }{
{ {
"/b*", "/b*",
"b-file.txt", []string{"b-file.txt"},
}, },
{ {
"*/a-file.txt", "/b*/**",
"some-dir/a-file.txt", []string{"b-file.txt", "b-file/in-subdir.txt"},
}, },
{ {
"*/A-file.txt", "**/a-file.txt",
"some-dir/a-file.txt", []string{"some-dir/a-file.txt"},
},
{
"**/A-file.txt",
[]string{"some-dir/a-file.txt"},
}, },
{ {
"**/*.zip", "**/*.zip",
"nested.zip", []string{"nested.zip"},
}, },
} }
@ -133,11 +139,7 @@ func TestZipFileManifest_GlobMatch(t *testing.T) {
results := z.GlobMatch(true, glob) results := z.GlobMatch(true, glob)
if len(results) == 1 && results[0] == tc.expected { require.ElementsMatch(t, tc.expected, results)
return
}
t.Errorf("unexpected results for glob '%s': %+v", glob, results)
}) })
} }
} }

View File

@ -79,6 +79,10 @@ func (i *Index) Remove(id artifact.ID) {
func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) { func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
for _, mapped := range fromMappedByID(i.fromID, ogID) { for _, mapped := range fromMappedByID(i.fromID, ogID) {
// the stale relationship(i.e. if there's an elder ID in either side) should be discarded
if len(fromMappedByID(i.toID, mapped.relationship.To.ID())) == 0 {
continue
}
i.Add(artifact.Relationship{ i.Add(artifact.Relationship{
From: replacement, From: replacement,
To: mapped.relationship.To, To: mapped.relationship.To,
@ -87,6 +91,10 @@ func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
} }
for _, mapped := range fromMappedByID(i.toID, ogID) { for _, mapped := range fromMappedByID(i.toID, ogID) {
// same as the above
if len(fromMappedByID(i.fromID, mapped.relationship.To.ID())) == 0 {
continue
}
i.Add(artifact.Relationship{ i.Add(artifact.Relationship{
From: mapped.relationship.From, From: mapped.relationship.From,
To: replacement, To: replacement,

View File

@ -1169,7 +1169,11 @@
}, },
"osCPE": { "osCPE": {
"type": "string", "type": "string",
"description": "OSCPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)" "description": "OSCPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)\n\nDeprecated: in Syft 2.0 the struct tag will be corrected to `osCpe` to match the systemd spec casing."
},
"appCpe": {
"type": "string",
"description": "AppCpe is a CPE name for the upstream Application, as found in NVD CPE search (e.g. cpe:2.3:a:gnu:coreutils:5.0)"
}, },
"os": { "os": {
"type": "string", "type": "string",
@ -1279,20 +1283,6 @@
"elfSecurityFeatures": { "elfSecurityFeatures": {
"$ref": "#/$defs/ELFSecurityFeatures", "$ref": "#/$defs/ELFSecurityFeatures",
"description": "ELFSecurityFeatures contains ELF-specific security hardening information when Format is ELF." "description": "ELFSecurityFeatures contains ELF-specific security hardening information when Format is ELF."
},
"symbolNames": {
"items": {
"type": "string"
},
"type": "array",
"description": "Symbols captures the selection from the symbol table found in the binary."
},
"toolchains": {
"items": {
"$ref": "#/$defs/Toolchain"
},
"type": "array",
"description": "Toolchains captures information about the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable."
} }
}, },
"type": "object", "type": "object",
@ -4235,27 +4225,6 @@
], ],
"description": "TerraformLockProviderEntry represents a single provider entry in a Terraform dependency lock file (.terraform.lock.hcl)." "description": "TerraformLockProviderEntry represents a single provider entry in a Terraform dependency lock file (.terraform.lock.hcl)."
}, },
"Toolchain": {
"properties": {
"name": {
"type": "string",
"description": "Name is the name of the toolchain (e.g., \"gcc\", \"clang\", \"ld\", etc.)."
},
"version": {
"type": "string",
"description": "Version is the version of the toolchain."
},
"kind": {
"type": "string",
"description": "Kind indicates the type of toolchain (e.g., compiler, linker, runtime)."
}
},
"type": "object",
"required": [
"name",
"kind"
]
},
"WordpressPluginEntry": { "WordpressPluginEntry": {
"properties": { "properties": {
"pluginInstallDirectory": { "pluginInstallDirectory": {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.1.1/document", "$id": "anchore.io/schema/syft/json/16.1.2/document",
"$ref": "#/$defs/Document", "$ref": "#/$defs/Document",
"$defs": { "$defs": {
"AlpmDbEntry": { "AlpmDbEntry": {
@ -1169,7 +1169,11 @@
}, },
"osCPE": { "osCPE": {
"type": "string", "type": "string",
"description": "OSCPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)" "description": "OSCPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)\n\nDeprecated: in Syft 2.0 the struct tag will be corrected to `osCpe` to match the systemd spec casing."
},
"appCpe": {
"type": "string",
"description": "AppCpe is a CPE name for the upstream Application, as found in NVD CPE search (e.g. cpe:2.3:a:gnu:coreutils:5.0)"
}, },
"os": { "os": {
"type": "string", "type": "string",

View File

@ -153,11 +153,12 @@ func NewLocationFromImage(accessPath string, ref file.Reference, img *image.Imag
} }
// 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, ref file.Reference) Location { func NewLocationFromDirectory(responsePath string, fd string, ref file.Reference) Location {
return Location{ return Location{
LocationData: LocationData{ LocationData: LocationData{
Coordinates: Coordinates{ Coordinates: Coordinates{
RealPath: responsePath, RealPath: responsePath,
FileSystemID: fd,
}, },
AccessPath: responsePath, AccessPath: responsePath,
ref: ref, ref: ref,

View File

@ -1,6 +1,7 @@
package helpers package helpers
import ( import (
"net/url"
"strings" "strings"
urilib "github.com/spdx/gordf/uri" urilib "github.com/spdx/gordf/uri"
@ -49,9 +50,21 @@ func isURIValid(uri string) bool {
func URIValue(uri string) string { func URIValue(uri string) string {
if strings.ToLower(uri) != "none" { if strings.ToLower(uri) != "none" {
if isURIValid(uri) { if isURIValid(uri) {
return uri return updateForGithub(url.Parse(uri))
} }
return NOASSERTION return NOASSERTION
} }
return NONE return NONE
} }
// Github repository is a valid NPM location but not a valid SPDX DownloadURL
func updateForGithub(uri *url.URL, err error) string {
if err != nil {
return NOASSERTION
}
updatedLocation := uri.String()
if uri.Scheme == "github" {
updatedLocation = "https://github.com/" + uri.Opaque
}
return updatedLocation
}

View File

@ -640,6 +640,16 @@ func Test_DownloadLocation(t *testing.T) {
}, },
expected: "bzr+https://bzr.myproject.org/MyProject/trunk@2019#src/somefile.c", expected: "bzr+https://bzr.myproject.org/MyProject/trunk@2019#src/somefile.c",
}, },
{
name: "Github Repository",
input: pkg.Package{
Metadata: pkg.NpmPackage{
URL: "github:anchore/syft",
},
},
expected: "https://github.com/anchore/syft",
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {

View File

@ -14,10 +14,11 @@ import (
// the user given root, the base path (if any) to consider as the root, and the current working directory. // the user given root, the base path (if any) to consider as the root, and the current working directory.
// Note: this only works on a real filesystem, not on a virtual filesystem (such as a stereoscope filetree). // Note: this only works on a real filesystem, not on a virtual filesystem (such as a stereoscope filetree).
type ChrootContext struct { type ChrootContext struct {
root string root string
base string rootRelativeToBase string
cwd string base string
cwdRelativeToRoot string cwd string
cwdRelativeToRoot string
} }
func NewChrootContextFromCWD(root, base string) (*ChrootContext, error) { func NewChrootContextFromCWD(root, base string) (*ChrootContext, error) {
@ -40,10 +41,26 @@ func NewChrootContext(root, base, cwd string) (*ChrootContext, error) {
return nil, err return nil, err
} }
// we need to track the relative path from root to base (if set) so that request paths can un-apply the base path
// changes from any incoming requests.
var rootRelativeToBase string
if cleanBase != cleanRoot && cleanBase != "" {
absRoot := cleanRoot
if !filepath.IsAbs(cleanRoot) {
absRoot = filepath.Join(cwd, cleanRoot)
}
rootRelativeToBase, err = filepath.Rel(absRoot, cleanBase) // validate that base is within root
if err != nil {
return nil, fmt.Errorf("base path %q is not within root path %q: %w", cleanBase, cleanRoot, err)
}
}
chroot := &ChrootContext{ chroot := &ChrootContext{
root: cleanRoot, root: cleanRoot,
base: cleanBase, rootRelativeToBase: rootRelativeToBase,
cwd: cwd, base: cleanBase,
cwd: cwd,
} }
return chroot, chroot.ChangeDirectory(cwd) return chroot, chroot.ChangeDirectory(cwd)
@ -125,8 +142,8 @@ func (r ChrootContext) ToNativePath(chrootPath string) (string, error) {
responsePath := chrootPath responsePath := chrootPath
if filepath.IsAbs(responsePath) { if filepath.IsAbs(responsePath) {
// don't allow input to potentially hop above root path // don't allow input to potentially hop above root path (and still un-apply any base paths)
responsePath = path.Join(r.root, responsePath) responsePath = path.Join(r.root, r.rootRelativeToBase, responsePath)
} else { } else {
// ensure we take into account any relative difference between the root path and the CWD for relative requests // ensure we take into account any relative difference between the root path and the CWD for relative requests
responsePath = path.Join(r.cwdRelativeToRoot, responsePath) responsePath = path.Join(r.cwdRelativeToRoot, responsePath)

View File

@ -452,6 +452,25 @@ func Test_ChrootContext_RequestResponse(t *testing.T) {
expectedNativePath: absRelOutsidePath, expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt", expectedChrootPath: "to/the/rel-outside.txt",
}, },
// base path within root cases...
// note: for absolute input paths, rootRelativeToBase is used to resolve the native path
// note: for relative input paths, cwdRelativeToRoot is used (base does not affect relative path resolution)
{
name: "relative root, abs request, with base",
root: relative,
base: filepath.Join(relative, "path", "to"),
input: "/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "/the/file.txt", // ToChrootPath trims base prefix without adding separator
},
{
name: "abs root, abs request, with base",
root: absolute,
base: filepath.Join(absolute, "path", "to"),
input: "/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "/the/file.txt", // ToChrootPath trims base prefix without adding separator
},
} }
for _, c := range cases { for _, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
@ -480,6 +499,69 @@ func Test_ChrootContext_RequestResponse(t *testing.T) {
} }
} }
func TestNewChrootContext_BaseValidation(t *testing.T) {
testDir, err := os.Getwd()
require.NoError(t, err)
relative := filepath.Join("test-fixtures", "req-resp")
absolute := filepath.Join(testDir, relative)
tests := []struct {
name string
root string
base string
cwd string
expectedRootRelativeToBase string
wantErr require.ErrorAssertionFunc
}{
{
name: "base within root",
root: absolute,
base: filepath.Join(absolute, "path", "to"),
cwd: testDir,
expectedRootRelativeToBase: filepath.Join("path", "to"),
},
{
name: "base equals root",
root: absolute,
base: absolute,
cwd: testDir,
expectedRootRelativeToBase: "",
},
{
name: "empty base",
root: absolute,
base: "",
cwd: testDir,
expectedRootRelativeToBase: "",
},
{
name: "relative root with base",
root: relative,
base: filepath.Join(absolute, "path", "to"),
cwd: testDir,
expectedRootRelativeToBase: filepath.Join("path", "to"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = require.NoError
}
ctx, err := NewChrootContext(tt.root, tt.base, tt.cwd)
tt.wantErr(t, err)
if err != nil {
return
}
assert.Equal(t, tt.expectedRootRelativeToBase, ctx.rootRelativeToBase)
})
}
}
func TestToNativeGlob(t *testing.T) { func TestToNativeGlob(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

View File

@ -190,7 +190,7 @@ func (r *FiletreeResolver) AllLocations(ctx context.Context) <-chan file.Locatio
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case results <- file.NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), ref): case results <- file.NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), "", ref):
continue continue
} }
} }

View File

@ -984,12 +984,12 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) {
}{ }{
{ {
name: "use file reference for content requests", name: "use file reference for content requests",
location: file.NewLocationFromDirectory("some/place", *existingPath.Reference), location: file.NewLocationFromDirectory("some/place", "", *existingPath.Reference),
expects: "this file has contents", expects: "this file has contents",
}, },
{ {
name: "error on empty file reference", name: "error on empty file reference",
location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), location: file.NewLocationFromDirectory("doesn't matter", "", stereoscopeFile.Reference{}),
err: true, err: true,
}, },
} }
@ -1525,12 +1525,12 @@ func Test_fileResolver_FileContentsByLocation(t *testing.T) {
}{ }{
{ {
name: "use file reference for content requests", name: "use file reference for content requests",
location: file.NewLocationFromDirectory("some/place", *existingPath.Reference), location: file.NewLocationFromDirectory("some/place", "", *existingPath.Reference),
expects: "this file has contents", expects: "this file has contents",
}, },
{ {
name: "error on empty file reference", name: "error on empty file reference",
location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), location: file.NewLocationFromDirectory("doesn't matter", "", stereoscopeFile.Reference{}),
err: true, err: true,
}, },
} }

View File

@ -950,7 +950,7 @@ func Test_UnindexedDirectoryResolver_FileContentsByLocation(t *testing.T) {
}, },
{ {
name: "error on empty file reference", name: "error on empty file reference",
location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), location: file.NewLocationFromDirectory("doesn't matter", "", stereoscopeFile.Reference{}),
err: true, err: true,
}, },
} }

View File

@ -24,8 +24,13 @@ type ELFBinaryPackageNoteJSONPayload struct {
Architecture string `json:"architecture,omitempty"` Architecture string `json:"architecture,omitempty"`
// OSCPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33) // OSCPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)
//
// Deprecated: in Syft 2.0 the struct tag will be corrected to `osCpe` to match the systemd spec casing.
OSCPE string `json:"osCPE,omitempty"` OSCPE string `json:"osCPE,omitempty"`
// AppCpe is a CPE name for the upstream Application, as found in NVD CPE search (e.g. cpe:2.3:a:gnu:coreutils:5.0)
AppCpe string `json:"appCpe,omitempty"`
// OS is the OS name, typically corresponding to ID in os-release (e.g. "fedora") // OS is the OS name, typically corresponding to ID in os-release (e.g. "fedora")
OS string `json:"os,omitempty"` OS string `json:"os,omitempty"`

View File

@ -68,11 +68,15 @@ func osNameAndVersionFromMetadata(metadata elfBinaryPackageNotes) (string, strin
return os, osVersion return os, osVersion
} }
if metadata.OSCPE == "" { if metadata.OSCPE == "" { //nolint:staticcheck
// best-effort to get the os info
if os != "" {
return os, ""
}
return "", "" return "", ""
} }
attrs, err := cpe.NewAttributes(metadata.OSCPE) attrs, err := cpe.NewAttributes(metadata.OSCPE) //nolint:staticcheck
if err != nil { if err != nil {
log.WithFields("error", err).Trace("unable to parse cpe attributes for elf binary package") log.WithFields("error", err).Trace("unable to parse cpe attributes for elf binary package")
return "", "" return "", ""

View File

@ -34,7 +34,10 @@ type elfBinaryPackageNotes struct {
CPE string `json:"cpe"` CPE string `json:"cpe"`
License string `json:"license"` License string `json:"license"`
pkg.ELFBinaryPackageNoteJSONPayload `json:",inline"` pkg.ELFBinaryPackageNoteJSONPayload `json:",inline"`
Location file.Location `json:"-"` // CorrectOSCPE has the corrected casing for the osCPE field relative to the systemd ELF package metadata "spec" https://systemd.io/ELF_PACKAGE_METADATA/ .
// Ideally in syft 2.0 this field should be replaced with the pkg.ELFBinaryPackageNoteJSONPayload.OSCPE field directly (with the struct tag corrected).
CorrectOSCPE string `json:"osCpe,omitempty"`
Location file.Location `json:"-"`
} }
type elfPackageKey struct { type elfPackageKey struct {
@ -164,9 +167,9 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
} }
{ {
var metadata elfBinaryPackageNotes var metadata *elfBinaryPackageNotes
if err := json.Unmarshal(notes, &metadata); err == nil { if metadata, err = unmarshalELFPackageNotesPayload(notes); err == nil {
return &metadata, nil return metadata, nil
} }
} }
@ -174,10 +177,10 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
var header elf64SectionHeader var header elf64SectionHeader
headerSize := binary.Size(header) / 4 headerSize := binary.Size(header) / 4
if len(notes) > headerSize { if len(notes) > headerSize {
var metadata elfBinaryPackageNotes var metadata *elfBinaryPackageNotes
newPayload := bytes.TrimRight(notes[headerSize:], "\x00") newPayload := bytes.TrimRight(notes[headerSize:], "\x00")
if err = json.Unmarshal(newPayload, &metadata); err == nil { if metadata, err = unmarshalELFPackageNotesPayload(newPayload); err == nil {
return &metadata, nil return metadata, nil
} }
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON") log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
} }
@ -186,6 +189,21 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
return nil, err return nil, err
} }
func unmarshalELFPackageNotesPayload(data []byte) (*elfBinaryPackageNotes, error) {
var metadata elfBinaryPackageNotes
if err := json.Unmarshal(data, &metadata); err != nil {
return nil, fmt.Errorf("unable to unmarshal ELF package notes payload: %w", err)
}
// normalize the os CPE field
if metadata.OSCPE == "" { //nolint:staticcheck
// ensure the public field is populated for backwards compatibility
metadata.OSCPE = metadata.CorrectOSCPE //nolint:staticcheck
}
return &metadata, nil
}
type elf64SectionHeader struct { type elf64SectionHeader struct {
ShName uint32 ShName uint32
ShType uint32 ShType uint32

View File

@ -5,6 +5,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
extFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
@ -112,6 +113,29 @@ func Test_ELFPackageCataloger(t *testing.T) {
}, },
}, },
}, },
{
name: "Debian 64 bit binaries w/o os version",
fixture: "image-wolfi-64bit-without-version",
expected: []pkg.Package{
{
Name: "glibc",
Version: "2.42-r4",
PURL: "pkg:apk/wolfi/glibc@2.42-r4?distro=wolfi",
Locations: file.NewLocationSet(
file.NewLocationFromDirectory("/lib/libBrokenLocale.so.1",
"sha256:559eaef4e501b8e7a150661a94ee8b9ebc63bfca3256953a703f9f82053346f2",
*extFile.NewFileReference("/lib/libBrokenLocale.so.1")).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Licenses: pkg.NewLicenseSet(),
Type: pkg.ApkPkg,
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "apk",
Architecture: "x86_64",
OS: "wolfi",
},
},
},
},
} }
for _, v := range cases { for _, v := range cases {
@ -126,3 +150,61 @@ func Test_ELFPackageCataloger(t *testing.T) {
} }
} }
func Test_unmarshalELFPackageNotesPayload(t *testing.T) {
tests := []struct {
name string
payload string
wantOSCPE string
wantCorrect string
wantErr require.ErrorAssertionFunc
}{
{
name: "only osCPE (incorrect) provided",
payload: `{"name":"test","version":"1.0","osCPE":"cpe:/o:fedoraproject:fedora:40"}`,
wantOSCPE: "cpe:/o:fedoraproject:fedora:40",
wantCorrect: "",
},
{
name: "only osCpe (correct) provided",
payload: `{"name":"test","version":"1.0","osCpe":"cpe:/o:fedoraproject:fedora:40"}`,
wantOSCPE: "cpe:/o:fedoraproject:fedora:40",
wantCorrect: "cpe:/o:fedoraproject:fedora:40",
},
{
name: "both osCPE and osCpe provided uses osCPE",
payload: `{"name":"test","version":"1.0","osCPE":"cpe:/o:fedoraproject:fedora:40","osCpe":"cpe:/o:redhat:rhel:9"}`,
wantOSCPE: "cpe:/o:fedoraproject:fedora:40",
wantCorrect: "cpe:/o:redhat:rhel:9",
},
{
name: "neither osCPE nor osCpe provided",
payload: `{"name":"test","version":"1.0"}`,
wantOSCPE: "",
wantCorrect: "",
},
{
name: "invalid JSON",
payload: `{invalid}`,
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = require.NoError
}
got, err := unmarshalELFPackageNotesPayload([]byte(tt.payload))
tt.wantErr(t, err)
if err != nil {
return
}
require.Equal(t, tt.wantOSCPE, got.OSCPE)
require.Equal(t, tt.wantCorrect, got.CorrectOSCPE)
})
}
}

View File

@ -0,0 +1,7 @@
FROM --platform=linux/amd64 cgr.dev/chainguard/wolfi-base AS build
FROM scratch
COPY --from=build /lib/libBrokenLocale.so.1 /lib/libBrokenLocale.so.1
CMD ["/bin/sh"]

View File

@ -489,6 +489,40 @@ func TestCataloger(t *testing.T) {
"netstandard @ 8.0.1425.11118 (/app/netstandard.dll)", "netstandard @ 8.0.1425.11118 (/app/netstandard.dll)",
) )
assertAllDepEntriesInEmbeddedExecutable := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
t.Helper()
for _, p := range pkgs {
// assert that all packages DO NOT have an executable associated with it
m, ok := p.Metadata.(pkg.DotnetDepsEntry)
if !ok {
t.Fatalf("expected metadata to be of type DotnetDepsEntry")
}
if len(m.Executables) != 0 {
t.Errorf("expected no executables for package %s, found %d", p.Name, len(m.Executables))
}
}
actual := extractMatchingPackage(t, "Newtonsoft.Json", pkgs)
expected := pkg.Package{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Locations: file.NewLocationSet(file.NewLocation("/app/dotnetapp.exe")), // important! not this is an exe
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
Metadata: pkg.DotnetDepsEntry{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Path: "newtonsoft.json/13.0.3",
Sha512: "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
HashPath: "newtonsoft.json.13.0.3.nupkg.sha512",
Executables: nil, // important!
},
}
pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual)
}
assertAllDepEntriesWithoutExecutables := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { assertAllDepEntriesWithoutExecutables := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
t.Helper() t.Helper()
for _, p := range pkgs { for _, p := range pkgs {
@ -895,12 +929,163 @@ func TestCataloger(t *testing.T) {
name: "combined cataloger (single file)", name: "combined cataloger (single file)",
fixture: "image-net8-app-single-file", fixture: "image-net8-app-single-file",
cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()),
expectedPkgs: []string{"Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
// important: no relationships should be found // extracted libraries from the embedded deps.json
expectedPkgs: []string{ "Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe)",
"dotnetapp @ 1.0.0.0 (/app/dotnetapp.exe)", "Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.exe)",
"Newtonsoft.Json @ 13.0.3 (/app/dotnetapp.exe)",
"dotnetapp @ 1.0.0 (/app/dotnetapp.exe)",
"runtimepack.Microsoft.NETCore.App.Runtime.win-x64 @ 8.0.14 (/app/dotnetapp.exe)",
}, },
assertion: assertSingleFileDeployment, expectedRels: []string{
"Humanizer @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
"Newtonsoft.Json @ 13.0.3 (/app/dotnetapp.exe) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.exe)",
"runtimepack.Microsoft.NETCore.App.Runtime.win-x64 @ 8.0.14 (/app/dotnetapp.exe) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.exe)",
},
assertion: assertAllDepEntriesInEmbeddedExecutable,
}, },
{ {
name: "pe cataloger (single file)", name: "pe cataloger (single file)",

View File

@ -3,6 +3,7 @@ package dotnet
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"path" "path"
"regexp" "regexp"
"sort" "sort"
@ -147,6 +148,18 @@ func isRuntimePackageLocation(loc file.Location) (string, bool) {
// partitionPEs pairs PE files with the deps.json based on directory containment. // partitionPEs pairs PE files with the deps.json based on directory containment.
func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDepsJSON, []logicalPE, []logicalDepsJSON) { func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDepsJSON, []logicalPE, []logicalDepsJSON) {
// if there are any embedded deps.json files in PE files, extract them and add them to the list of deps.json files to process.
consideredPEs := file.NewCoordinateSet()
for _, pe := range peFiles {
if pe.EmbeddedDepsJSON != "" {
dep := extractEmbeddedDeps(pe)
if dep != nil {
depJsons = append(depJsons, *dep)
consideredPEs.Add(pe.Location.Coordinates) // mark this PE as already considered
}
}
}
// sort deps.json paths from longest to shortest. This is so we are processing the most specific match first. // sort deps.json paths from longest to shortest. This is so we are processing the most specific match first.
sort.Slice(depJsons, func(i, j int) bool { sort.Slice(depJsons, func(i, j int) bool {
return depJsons[i].Location.RealPath > depJsons[j].Location.RealPath return depJsons[i].Location.RealPath > depJsons[j].Location.RealPath
@ -170,7 +183,9 @@ func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDep
// across multiple deps.json files. // across multiple deps.json files.
} }
} }
if !found { // if we did not find a deps.json to associate this PE with, keep track of it for later processing.
// also, if we have already considered this PE because it had an embedded deps.json, skip it.
if !found && !consideredPEs.Contains(pe.Location.Coordinates) {
remainingPeFiles = append(remainingPeFiles, pe) remainingPeFiles = append(remainingPeFiles, pe)
} }
} }
@ -489,3 +504,14 @@ func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) {
return ldpe, nil return ldpe, nil
} }
func extractEmbeddedDeps(pe logicalPE) *logicalDepsJSON {
doc, err := newDepsJSON(file.NewLocationReadCloser(pe.Location, io.NopCloser(strings.NewReader(pe.EmbeddedDepsJSON))))
if err != nil || doc == nil {
return nil
}
doc.Location = pe.Location
lDoc := getLogicalDepsJSON(*doc, nil)
return &lDoc
}

View File

@ -48,6 +48,9 @@ type CatalogerConfig struct {
NoProxy []string `yaml:"no-proxy,omitempty" json:"no-proxy,omitempty" mapstructure:"no-proxy"` NoProxy []string `yaml:"no-proxy,omitempty" json:"no-proxy,omitempty" mapstructure:"no-proxy"`
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"`
// 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"`
} }
type MainModuleVersionConfig struct { type MainModuleVersionConfig struct {
@ -70,6 +73,7 @@ type MainModuleVersionConfig struct {
// - setting the default local module cache dir if none is provided // - setting the default local module cache dir if none is provided
func DefaultCatalogerConfig() CatalogerConfig { func DefaultCatalogerConfig() CatalogerConfig {
g := CatalogerConfig{ g := CatalogerConfig{
UsePackagesLib: true,
MainModuleVersion: DefaultMainModuleVersionConfig(), MainModuleVersion: DefaultMainModuleVersionConfig(),
LocalModCacheDir: defaultGoModDir(), LocalModCacheDir: defaultGoModDir(),
} }
@ -180,6 +184,11 @@ func (g CatalogerConfig) WithMainModuleVersion(input MainModuleVersionConfig) Ca
return g return g
} }
func (g CatalogerConfig) WithUsePackagesLib(useLib bool) CatalogerConfig {
g.UsePackagesLib = useLib
return g
}
func (g MainModuleVersionConfig) WithFromLDFlags(input bool) MainModuleVersionConfig { func (g MainModuleVersionConfig) WithFromLDFlags(input bool) MainModuleVersionConfig {
g.FromLDFlags = input g.FromLDFlags = input
return g return g

View File

@ -57,6 +57,7 @@ func Test_Config(t *testing.T) {
Proxies: []string{"https://my.proxy"}, Proxies: []string{"https://my.proxy"},
NoProxy: []string{"my.private", "no.proxy"}, NoProxy: []string{"my.private", "no.proxy"},
MainModuleVersion: DefaultMainModuleVersionConfig(), MainModuleVersion: DefaultMainModuleVersionConfig(),
UsePackagesLib: true,
}, },
}, },
{ {
@ -84,6 +85,7 @@ func Test_Config(t *testing.T) {
Proxies: []string{"https://alt.proxy", "direct"}, Proxies: []string{"https://alt.proxy", "direct"},
NoProxy: []string{"alt.no.proxy"}, NoProxy: []string{"alt.no.proxy"},
MainModuleVersion: DefaultMainModuleVersionConfig(), MainModuleVersion: DefaultMainModuleVersionConfig(),
UsePackagesLib: true,
}, },
}, },
} }

View File

@ -64,27 +64,23 @@ func newBinaryMetadata(dep *debug.Module, mainModule, goVersion, architecture st
func packageURL(moduleName, moduleVersion string) string { func packageURL(moduleName, moduleVersion string) string {
// source: https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#golang // source: https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#golang
// note: "The version is often empty when a commit is not specified and should be the commit in most cases when available." // note: "The version is often empty when a commit is not specified and should be the commit in most cases when available."
if moduleName == "" {
fields := strings.Split(moduleName, "/")
if len(fields) == 0 {
return "" return ""
} }
namespace := "" namespace := ""
name := "" name := moduleName
// The subpath is used to point to a subpath inside a package (e.g. pkg:golang/google.golang.org/genproto#googleapis/api/annotations)
subpath := ""
switch len(fields) { // golang PURLs from _modules_ are constructed as pkg:golang/<module>@version, where
case 1: // the full module name often includes multiple segments including `/v#`-type versions, for example:
name = fields[0] // pkg:golang/github.com/cli/cli/v2@2.63.0
case 2: // see: https://github.com/package-url/purl-spec/issues/63
name = fields[1] // and: https://github.com/package-url/purl-spec/blob/main/types-doc/golang-definition.md#subpath-definition
namespace = fields[0] // by setting the namespace this way, it does not escape the slashes:
default: lastSlash := strings.LastIndex(moduleName, "/")
name = fields[2] if lastSlash > 0 && lastSlash < len(moduleName)-1 {
namespace = strings.Join(fields[0:2], "/") name = moduleName[lastSlash+1:]
subpath = strings.Join(fields[3:], "/") namespace = moduleName[0:lastSlash]
} }
return packageurl.NewPackageURL( return packageurl.NewPackageURL(
@ -93,6 +89,6 @@ func packageURL(moduleName, moduleVersion string) string {
name, name,
moduleVersion, moduleVersion,
nil, nil,
subpath, "", // subpath is used to reference a specific _package_ within the module
).ToString() ).ToString()
} }

View File

@ -38,14 +38,14 @@ func Test_packageURL(t *testing.T) {
Name: "github.com/coreos/go-systemd/v22", Name: "github.com/coreos/go-systemd/v22",
Version: "v22.1.0", Version: "v22.1.0",
}, },
expected: "pkg:golang/github.com/coreos/go-systemd@v22.1.0#v22", expected: "pkg:golang/github.com/coreos/go-systemd/v22@v22.1.0",
}, },
{ {
name: "golang with subpath deep", name: "golang with subpath deep",
pkg: pkg.Package{ pkg: pkg.Package{
Name: "google.golang.org/genproto/googleapis/api/annotations", Name: "google.golang.org/genproto/googleapis/api/annotations",
}, },
expected: "pkg:golang/google.golang.org/genproto/googleapis#api/annotations", expected: "pkg:golang/google.golang.org/genproto/googleapis/api/annotations",
}, },
} }

View File

@ -98,6 +98,24 @@ func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.
return relationships return relationships
} }
// moduleEqual is used to deduplicate go modules especially the sub module may be identical to the main one
func moduleEqual(lhs, rhs *debug.Module) bool {
if lhs == rhs {
return true
}
if lhs == nil || rhs == nil {
return false
}
if lhs.Path != rhs.Path ||
lhs.Version != rhs.Version ||
lhs.Sum != rhs.Sum {
return false
}
return moduleEqual(lhs.Replace, rhs.Replace)
}
var emptyModule debug.Module var emptyModule debug.Module
var moduleFromPartialPackageBuild = debug.Module{Path: "command-line-arguments"} var moduleFromPartialPackageBuild = debug.Module{Path: "command-line-arguments"}
@ -115,7 +133,9 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Re
if dep == nil { if dep == nil {
continue continue
} }
if moduleEqual(dep, &mod.Main) {
continue
}
lics := c.licenseResolver.getLicenses(ctx, resolver, dep.Path, dep.Version) lics := c.licenseResolver.getLicenses(ctx, resolver, dep.Path, dep.Version)
gover, experiments := getExperimentsFromVersion(mod.GoVersion) gover, experiments := getExperimentsFromVersion(mod.GoVersion)

View File

@ -278,7 +278,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
{ {
Name: "github.com/a/b/c", Name: "github.com/a/b/c",
Version: "", // this was (devel) but we cleared it explicitly Version: "", // this was (devel) but we cleared it explicitly
PURL: "pkg:golang/github.com/a/b#c", PURL: "pkg:golang/github.com/a/b/c",
Language: pkg.Go, Language: pkg.Go,
Type: pkg.GoModulePkg, Type: pkg.GoModulePkg,
Locations: file.NewLocationSet( Locations: file.NewLocationSet(
@ -847,6 +847,86 @@ func TestBuildGoPkgInfo(t *testing.T) {
unmodifiedMain, unmodifiedMain,
}, },
}, },
{
name: "parse a populated mod string and returns packages when a replace directive and synthetic main module 'command line arguments' exists",
mod: &extendedBuildInfo{
BuildInfo: &debug.BuildInfo{
GoVersion: goCompiledVersion,
Main: debug.Module{Path: "command-line-arguments", Version: devel},
Settings: []debug.BuildSetting{
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "linux"},
{Key: "GOAMD64", Value: "v1"},
},
Path: "command-line-arguments",
Deps: []*debug.Module{
{
Path: "example.com/mylib",
Version: "v0.0.0",
Replace: &debug.Module{
Path: "./mylib",
Version: devel,
},
},
{
Path: "command-line-arguments",
Version: devel,
},
},
},
cryptoSettings: nil,
arch: archDetails,
},
expected: []pkg.Package{
{
Name: "example.com/mylib",
Version: "",
PURL: "pkg:golang/example.com/mylib",
Language: pkg.Go,
Type: pkg.GoModulePkg,
Locations: file.NewLocationSet(
file.NewLocationFromCoordinates(
file.Coordinates{
RealPath: "/a-path",
FileSystemID: "layer-id",
},
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: goCompiledVersion,
Architecture: archDetails,
H1Digest: "",
MainModule: "command-line-arguments",
},
},
{
Name: "command-line-arguments",
Version: "",
PURL: "pkg:golang/command-line-arguments",
Language: pkg.Go,
Type: pkg.GoModulePkg,
Locations: file.NewLocationSet(
file.NewLocationFromCoordinates(
file.Coordinates{
RealPath: "/a-path",
FileSystemID: "layer-id",
},
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Metadata: pkg.GolangBinaryBuildinfoEntry{
BuildSettings: pkg.KeyValues{
{Key: "GOARCH", Value: "amd64"},
{Key: "GOOS", Value: "linux"},
{Key: "GOAMD64", Value: "v1"},
},
GoCompiledVersion: goCompiledVersion,
Architecture: archDetails,
H1Digest: "",
MainModule: "command-line-arguments",
},
},
},
},
{ {
name: "parse main mod and replace devel with pattern from binary contents", name: "parse main mod and replace devel with pattern from binary contents",
cfg: func() *CatalogerConfig { cfg: func() *CatalogerConfig {

View File

@ -8,7 +8,6 @@ import (
"io" "io"
"path/filepath" "path/filepath"
"slices" "slices"
"sort"
"strings" "strings"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -26,17 +25,19 @@ import (
) )
type goModCataloger struct { type goModCataloger struct {
usePackagesLib bool
licenseResolver goLicenseResolver licenseResolver goLicenseResolver
} }
func newGoModCataloger(opts CatalogerConfig) *goModCataloger { func newGoModCataloger(opts CatalogerConfig) *goModCataloger {
return &goModCataloger{ return &goModCataloger{
usePackagesLib: opts.UsePackagesLib,
licenseResolver: newGoLicenseResolver(modFileCatalogerName, opts), licenseResolver: newGoLicenseResolver(modFileCatalogerName, opts),
} }
} }
// parseGoModFile takes a go.mod and tries to resolve and lists all packages discovered. // parseGoModFile takes a go.mod and tries to resolve and lists all packages discovered.
func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) (pkgs []pkg.Package, relationships []artifact.Relationship, err error) {
modDir := filepath.Dir(string(reader.Location.Reference().RealPath)) modDir := filepath.Dir(string(reader.Location.Reference().RealPath))
digests, err := parseGoSumFile(resolver, reader) digests, err := parseGoSumFile(resolver, reader)
if err != nil { if err != nil {
@ -48,24 +49,34 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
scanRoot = dir.Chroot.Base() scanRoot = dir.Chroot.Base()
} }
// source analysis using go toolchain if available
syftSourcePackages, sourceModules, sourceDependencies, unknownErr := c.loadPackages(modDir, reader.Location)
catalogedModules, sourceModuleToPkg := c.catalogModules(ctx, scanRoot, syftSourcePackages, sourceModules, reader, digests)
relationships := buildModuleRelationships(catalogedModules, sourceDependencies, sourceModuleToPkg)
// base case go.mod file parsing // base case go.mod file parsing
modFile, err := c.parseModFileContents(reader) modFile, err := c.parseModFileContents(reader)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// only use mod packages NOT found in source analysis // source analysis using go toolchain if available
var sourceModules map[string]*packages.Module
var catalogedModules []pkg.Package
if c.usePackagesLib {
var sourcePackages map[string][]pkgInfo
var sourceDependencies map[string][]string
var sourceModuleToPkg map[string]artifact.Identifiable
sourcePackages, sourceModules, sourceDependencies, err = c.loadPackages(modDir, reader.Location)
catalogedModules, sourceModuleToPkg = c.catalogModules(ctx, scanRoot, sourcePackages, sourceModules, reader, digests)
relationships = buildModuleRelationships(catalogedModules, sourceDependencies, sourceModuleToPkg)
}
// 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, goModPackages, reader, digests)
c.applyExcludeDirectives(modFile, goModPackages) c.applyExcludeDirectives(modFile, goModPackages)
finalPkgs := c.assembleResults(catalogedModules, goModPackages) pkgs = c.assembleResults(catalogedModules, goModPackages)
return finalPkgs, relationships, unknownErr
return pkgs, relationships, err
} }
// loadPackages uses golang.org/x/tools/go/packages to get dependency information. // loadPackages uses golang.org/x/tools/go/packages to get dependency information.
@ -327,7 +338,7 @@ func (c *goModCataloger) createGoModPackages(ctx context.Context, resolver file.
goModPackages := make(map[string]pkg.Package) goModPackages := make(map[string]pkg.Package)
for _, m := range modFile.Require { for _, m := range modFile.Require {
if _, exists := sourceModules[m.Mod.Path]; !exists { if sourceModules == nil || sourceModules[m.Mod.Path] == nil {
lics := c.licenseResolver.getLicenses(ctx, resolver, m.Mod.Path, m.Mod.Version) lics := c.licenseResolver.getLicenses(ctx, resolver, m.Mod.Path, m.Mod.Version)
goModPkg := pkg.Package{ goModPkg := pkg.Package{
Name: m.Mod.Path, Name: m.Mod.Path,
@ -392,9 +403,7 @@ func (c *goModCataloger) assembleResults(catalogedPkgs []pkg.Package, goModPacka
pkgsSlice = append(pkgsSlice, p) pkgsSlice = append(pkgsSlice, p)
} }
sort.SliceStable(pkgsSlice, func(i, j int) bool { pkg.Sort(pkgsSlice)
return pkgsSlice[i].Name < pkgsSlice[j].Name
})
return pkgsSlice return pkgsSlice
} }

View File

@ -54,7 +54,7 @@ func TestParseGoMod(t *testing.T) {
{ {
Name: "github.com/anchore/archiver/v3", Name: "github.com/anchore/archiver/v3",
Version: "v3.5.2", Version: "v3.5.2",
PURL: "pkg:golang/github.com/anchore/archiver@v3.5.2#v3", PURL: "pkg:golang/github.com/anchore/archiver/v3@v3.5.2",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/go-mod-fixtures/many-packages/go.mod")), Locations: file.NewLocationSet(file.NewLocation("test-fixtures/go-mod-fixtures/many-packages/go.mod")),
Language: pkg.Go, Language: pkg.Go,
Type: pkg.GoModulePkg, Type: pkg.GoModulePkg,
@ -111,7 +111,7 @@ func TestParseGoMod(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) { t.Run(test.fixture, func(t *testing.T) {
c := newGoModCataloger(DefaultCatalogerConfig()) c := newGoModCataloger(DefaultCatalogerConfig().WithUsePackagesLib(false))
pkgtest.NewCatalogTester(). pkgtest.NewCatalogTester().
FromFile(t, test.fixture). FromFile(t, test.fixture).
Expects(test.expected, nil). Expects(test.expected, nil).
@ -172,7 +172,9 @@ func Test_GoSumHashes(t *testing.T) {
pkgtest.NewCatalogTester(). pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture). FromDirectory(t, test.fixture).
Expects(test.expected, nil). Expects(test.expected, nil).
TestCataloger(t, NewGoModuleFileCataloger(CatalogerConfig{})) TestCataloger(t, NewGoModuleFileCataloger(CatalogerConfig{
UsePackagesLib: false,
}))
}) })
} }
} }
@ -189,7 +191,6 @@ func Test_parseGoSource_packageResolution(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
fixturePath string fixturePath string
config CatalogerConfig
expectedPkgs []string expectedPkgs []string
expectedRels []string expectedRels []string
expectedLicenses map[string][]string expectedLicenses map[string][]string
@ -333,7 +334,7 @@ func Test_parseGoSource_packageResolution(t *testing.T) {
t.Errorf("mismatch in licenses (-want +got):\n%s", diff) t.Errorf("mismatch in licenses (-want +got):\n%s", diff)
} }
}). }).
TestCataloger(t, NewGoModuleFileCataloger(CatalogerConfig{})) TestCataloger(t, NewGoModuleFileCataloger(DefaultCatalogerConfig().WithUsePackagesLib(true)))
}) })
} }
} }

View File

@ -196,6 +196,11 @@ var defaultCandidateAdditions = buildCandidateLookup(
candidateAddition{AdditionalVendors: []string{"handlebarsjs"}}, candidateAddition{AdditionalVendors: []string{"handlebarsjs"}},
}, },
// NPM packages // NPM packages
{
pkg.NpmPkg,
candidateKey{PkgName: "next"},
candidateAddition{AdditionalProducts: []string{"next.js"}, AdditionalVendors: []string{"vercel"}},
},
{ {
pkg.NpmPkg, pkg.NpmPkg,
candidateKey{PkgName: "hapi"}, candidateKey{PkgName: "hapi"},

View File

@ -0,0 +1,259 @@
package pe
import (
"bytes"
"debug/pe"
"encoding/binary"
"errors"
"fmt"
"io"
)
// dotNetBundleSignature is the SHA-256 hash of ".net core bundle" used to identify single-file bundles.
var dotNetBundleSignature = []byte{
0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae,
}
// dotNetBundleHeader represents the fixed portion of the bundle header (version 1+)
type dotNetBundleHeader struct {
MajorVersion uint32
MinorVersion uint32
NumEmbeddedFiles int32
}
// dotNetBundleHeaderV2 represents additional fields in V2+ bundles (.NET 5+)
type dotNetBundleHeaderV2 struct {
DepsJSONOffset int64
DepsJSONSize int64
RuntimeConfigJSONOffset int64
RuntimeConfigJSONSize int64
Flags uint64
}
// dotNetFileType represents the type of bundled file in the manifest
type dotNetFileType uint8
const (
dotNetFileTypeUnknown dotNetFileType = iota
dotNetFileTypeAssembly
dotNetFileTypeNativeBinary
dotNetFileTypeDepsJSON
dotNetFileTypeRuntimeConfigJSON
dotNetFileTypeSymbols
)
// extractDepsJSONFromBundle searches for an embedded deps.json file in a .NET single-file bundle.
// When built with PublishSingleFile=true, .NET embeds the application and all dependencies into
// the AppHost executable. The bundle marker (8-byte header offset + 32-byte signature) is placed
// in a placeholder location within the PE structure, pointing to the bundle header which contains
// file entry metadata. For V2+ bundles (.NET 5+), the header includes direct offsets to deps.json;
// for V1 bundles (.NET Core 3.x), we parse the manifest to locate it.
//
// ┌──────────────────────────────────┐
// │ PE AppHost Binary │ Standard PE structure
// │ ... │
// │ [8B offset][32B signature] │ Bundle marker (in placeholder within PE)
// │ ... │
// ├──────────────────────────────────┤
// │ Bundled Files │ Raw file contents (assemblies, deps.json, etc.)
// ├──────────────────────────────────┤
// │ Bundle Header │ Version info, file count, deps.json offset (V2+)
// │ File Manifest │ Per-file: offset, size, type, path
// └──────────────────────────────────┘
//
// Parsing strategy:
// 1. Search only the PE portion (using section headers) for the bundle signature
// 2. Read 8 bytes before signature to get header offset
// 3. Parse header to get deps.json location (V2+) or scan manifest entries (V1)
//
// See related documentation for more information:
// - https://github.com/dotnet/designs/blob/main/accepted/2020/single-file/design.md
// - https://github.com/dotnet/designs/blob/main/accepted/2020/single-file/bundler.md
// - https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs
// - https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs
// - https://github.com/dotnet/runtime/blob/main/src/native/corehost/bundle/header.h
// - https://github.com/dotnet/runtime/blob/main/src/native/corehost/bundle/file_entry.h
// - https://github.com/dotnet/runtime/blob/main/src/native/corehost/bundle/file_type.h
func extractDepsJSONFromBundle(r io.ReadSeeker, sections []pe.SectionHeader32) (string, error) {
headerOffset, err := findBundleHeaderOffset(r, sections)
if err != nil {
return "", err
}
if headerOffset == 0 {
return "", nil // not a .NET single-file bundle
}
return readDepsJSONFromBundleHeader(r, headerOffset)
}
// findBundleHeaderOffset locates the bundle marker within the PE structure and returns the header offset.
// Returns 0 if no bundle marker is found (not a single-file bundle).
func findBundleHeaderOffset(r io.ReadSeeker, sections []pe.SectionHeader32) (int64, error) {
peEndOffset := calculatePEEndOffset(sections)
if _, err := r.Seek(0, io.SeekStart); err != nil {
return 0, err
}
peData := make([]byte, peEndOffset)
n, err := io.ReadFull(r, peData)
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
return 0, err
}
peData = peData[:n]
idx := bytes.Index(peData, dotNetBundleSignature)
if idx == -1 || idx < 8 {
return 0, nil
}
// the header offset is stored in the 8 bytes immediately before the signature
headerOffset := int64(binary.LittleEndian.Uint64(peData[idx-8 : idx]))
return headerOffset, nil
}
// calculatePEEndOffset determines where the PE structure ends based on section headers,
// adding padding for alignment. This bounds our search for the bundle marker.
func calculatePEEndOffset(sections []pe.SectionHeader32) int64 {
var peEndOffset int64
for _, sec := range sections {
endOfSection := int64(sec.PointerToRawData) + int64(sec.SizeOfRawData)
if endOfSection > peEndOffset {
peEndOffset = endOfSection
}
}
// add buffer for alignment padding after sections
return peEndOffset + 4096
}
// readDepsJSONFromBundleHeader parses the bundle header at the given offset and extracts deps.json content.
func readDepsJSONFromBundleHeader(r io.ReadSeeker, headerOffset int64) (string, error) {
if _, err := r.Seek(headerOffset, io.SeekStart); err != nil {
return "", err
}
var header dotNetBundleHeader
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return "", err
}
// skip bundle ID (7-bit length-prefixed string)
if err := skipDotNetString(r); err != nil {
return "", err
}
// for V2+ bundles (.NET 5+), read deps.json location directly from header
if header.MajorVersion >= 2 {
var headerV2 dotNetBundleHeaderV2
if err := binary.Read(r, binary.LittleEndian, &headerV2); err != nil {
return "", err
}
if headerV2.DepsJSONSize > 0 && headerV2.DepsJSONOffset > 0 {
return readDepsJSONAtOffset(r, headerV2.DepsJSONOffset, headerV2.DepsJSONSize)
}
}
// for V1 bundles (.NET Core 3.x) or if V2 header doesn't have deps.json, parse manifest
return findDepsJSONInManifest(r, header.NumEmbeddedFiles, header.MajorVersion)
}
// skipDotNetString skips a 7-bit length-prefixed string (.NET BinaryWriter format)
func skipDotNetString(r io.ReadSeeker) error {
length, err := read7BitEncodedInt(r)
if err != nil {
return err
}
_, err = r.Seek(int64(length), io.SeekCurrent)
return err
}
// read7BitEncodedInt reads a .NET 7-bit encoded integer (variable-length encoding used by BinaryWriter)
func read7BitEncodedInt(r io.Reader) (int, error) {
result := 0
shift := 0
for {
var b [1]byte
if _, err := r.Read(b[:]); err != nil {
return 0, err
}
result |= int(b[0]&0x7F) << shift
if b[0]&0x80 == 0 {
break
}
shift += 7
if shift >= 35 { // prevent overflow
return 0, errors.New("invalid 7-bit encoded int")
}
}
return result, nil
}
// readDepsJSONAtOffset reads deps.json content at a specific offset using seeks (avoiding loading entire file)
func readDepsJSONAtOffset(r io.ReadSeeker, offset, size int64) (string, error) {
if _, err := r.Seek(offset, io.SeekStart); err != nil {
return "", fmt.Errorf("failed to seek to deps.json at offset %d: %w", offset, err)
}
data := make([]byte, size)
if _, err := io.ReadFull(r, data); err != nil {
return "", fmt.Errorf("failed to read deps.json (%d bytes): %w", size, err)
}
return string(data), nil
}
// findDepsJSONInManifest parses manifest entries to find deps.json (for V1 bundles or fallback)
func findDepsJSONInManifest(r io.ReadSeeker, numFiles int32, majorVersion uint32) (string, error) {
for i := int32(0); i < numFiles; i++ {
var offset, size int64
if err := binary.Read(r, binary.LittleEndian, &offset); err != nil {
return "", err
}
if err := binary.Read(r, binary.LittleEndian, &size); err != nil {
return "", err
}
// V6+ bundles (.NET 6+) have compressed size field
if majorVersion >= 6 {
var compressedSize int64
if err := binary.Read(r, binary.LittleEndian, &compressedSize); err != nil {
return "", err
}
}
var fileType dotNetFileType
if err := binary.Read(r, binary.LittleEndian, &fileType); err != nil {
return "", err
}
// skip relativePath string
if err := skipDotNetString(r); err != nil {
return "", err
}
if fileType == dotNetFileTypeDepsJSON && size > 0 {
// save current position to resume manifest parsing if needed
currentPos, err := r.Seek(0, io.SeekCurrent)
if err != nil {
return "", err
}
// read deps.json content
content, err := readDepsJSONAtOffset(r, offset, size)
if err != nil {
return "", err
}
// restore position (in case caller needs to continue)
if _, err := r.Seek(currentPos, io.SeekStart); err != nil {
return "", err
}
return content, nil
}
}
return "", nil
}

View File

@ -0,0 +1,58 @@
package pe
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_extractDepsJSONFromBundle_Versions(t *testing.T) {
tests := []struct {
name string
fixture string
path string
wantDepsJSON bool // true if deps.json should be found
wantJSONContain string // string that should be in the JSON (varies by .NET version)
}{
{
name: "V1 bundle (.NET Core 3.1)",
fixture: "image-dotnet31-single-file",
path: "/app/hello.exe",
wantDepsJSON: true,
wantJSONContain: "runtimeOptions", // .NET Core 3.1 uses runtimeOptions
},
{
name: "V2 bundle (.NET 5)",
fixture: "image-dotnet5-single-file",
path: "/app/hello.exe",
wantDepsJSON: true,
wantJSONContain: "runtimeTarget", // .NET 5+ uses runtimeTarget
},
{
name: "V6 bundle (.NET 6)",
fixture: "image-dotnet6-single-file",
path: "/app/hello.exe",
wantDepsJSON: true,
wantJSONContain: "runtimeTarget", // .NET 6+ uses runtimeTarget
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reader := fixtureFile(t, tt.fixture, tt.path)
defer reader.Close()
got, err := Read(reader)
require.NoError(t, err)
if tt.wantDepsJSON {
assert.NotEmpty(t, got.EmbeddedDepsJSON, "expected deps.json to be extracted from bundle")
// verify it looks like valid JSON for this .NET version
assert.Contains(t, got.EmbeddedDepsJSON, tt.wantJSONContain, "deps.json should contain expected field")
} else {
assert.Empty(t, got.EmbeddedDepsJSON, "expected no deps.json in non-bundle file")
}
})
}
}

View File

@ -34,6 +34,10 @@ type File struct {
// understand if this executable is even a .NET application. // understand if this executable is even a .NET application.
CLR *CLREvidence CLR *CLREvidence
// EmbeddedDepsJSON is the contents of an embedded deps.json file found within the PE file, if any.
// This is typical when using the PublishSingleFile build option.
EmbeddedDepsJSON string
// VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory. // VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory.
VersionResources map[string]string VersionResources map[string]string
} }
@ -153,7 +157,7 @@ func Read(f file.LocationReadCloser) (*File, error) {
return nil, err return nil, err
} }
sections, _, err := parsePEFile(r) sections, sectionHeaders, err := parsePEFile(r)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse PE sections: %w", err) return nil, fmt.Errorf("unable to parse PE sections: %w", err)
} }
@ -171,9 +175,15 @@ func Read(f file.LocationReadCloser) (*File, error) {
return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err) return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err)
} }
embeddedDepsJSON, err := extractDepsJSONFromBundle(r, sectionHeaders)
if err != nil {
return nil, fmt.Errorf("unable to extract embedded deps.json: %w", err)
}
return &File{ return &File{
Location: f.Location, Location: f.Location,
CLR: c, CLR: c,
EmbeddedDepsJSON: embeddedDepsJSON,
VersionResources: versionResources, VersionResources: versionResources,
}, nil }, nil
} }

View File

@ -1,6 +1,8 @@
package pe package pe
import ( import (
"fmt"
"os"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -14,13 +16,17 @@ import (
) )
func Test_Read_DotNetDetection(t *testing.T) { func Test_Read_DotNetDetection(t *testing.T) {
singleFileDepsJSON, err := os.ReadFile("test-fixtures/net8-app-single-file.deps.json")
require.NoError(t, err)
tests := []struct { tests := []struct {
name string name string
fixture string fixture string
path string path string
wantVR map[string]string wantVR map[string]string
wantCLR bool wantCLR bool
wantErr require.ErrorAssertionFunc wantDepsJSON string
wantErr require.ErrorAssertionFunc
}{ }{
{ {
name: "newtonsoft", name: "newtonsoft",
@ -114,7 +120,8 @@ func Test_Read_DotNetDetection(t *testing.T) {
"ProductVersion": "1.0.0", "ProductVersion": "1.0.0",
"Assembly Version": "1.0.0.0", "Assembly Version": "1.0.0.0",
}, },
wantErr: require.NoError, wantDepsJSON: string(singleFileDepsJSON),
wantErr: require.NoError,
}, },
} }
@ -137,6 +144,11 @@ func Test_Read_DotNetDetection(t *testing.T) {
} }
assert.Equal(t, tt.wantCLR, got.CLR.HasEvidenceOfCLR()) assert.Equal(t, tt.wantCLR, got.CLR.HasEvidenceOfCLR())
if d := cmp.Diff(tt.wantDepsJSON, got.EmbeddedDepsJSON); d != "" {
fmt.Printf("got embedded deps.json: %s\n", got.EmbeddedDepsJSON)
t.Errorf("unexpected deps.json location (-want +got): %s", d)
}
}) })
} }
} }

View File

@ -0,0 +1,19 @@
FINGERPRINT_FILE=cache.fingerprint
.DEFAULT_GOAL := fixtures
# requirement 1: 'fixtures' goal to generate any and all test fixtures
fixtures:
@echo "nothing to do"
# requirement 2: 'fingerprint' goal to determine if cache should be busted
fingerprint: $(FINGERPRINT_FILE)
# requirement 3: always recalculate fingerprint based on source
.PHONY: $(FINGERPRINT_FILE)
$(FINGERPRINT_FILE):
@find Makefile **/Dockerfile **/src/** -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
# requirement 4: 'clean' goal to remove generated test fixtures
clean:
rm -f $(FINGERPRINT_FILE)

View File

@ -0,0 +1,11 @@
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY src/ .
RUN dotnet publish -c Release -r win-x64 \
-p:PublishSingleFile=true \
-p:SelfContained=true \
-o /app
FROM busybox
WORKDIR /app
COPY --from=build /app/hello.exe .

View File

@ -0,0 +1,12 @@
using System;
namespace Hello
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello");
}
}
}

View File

@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,11 @@
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY src/ .
RUN dotnet publish -c Release -r win-x64 \
-p:PublishSingleFile=true \
-p:SelfContained=true \
-o /app
FROM busybox
WORKDIR /app
COPY --from=build /app/hello.exe .

View File

@ -0,0 +1 @@
System.Console.WriteLine("Hello");

View File

@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,11 @@
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY src/ .
RUN dotnet publish -c Release -r win-x64 \
-p:PublishSingleFile=true \
-p:SelfContained=true \
-o /app
FROM busybox
WORKDIR /app
COPY --from=build /app/hello.exe .

View File

@ -0,0 +1 @@
System.Console.WriteLine("Hello");

View File

@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -184,7 +184,7 @@ func (j *archiveParser) parse(ctx context.Context, parentPkg *pkg.Package) ([]pk
relationships = append(relationships, nestedRelationships...) relationships = append(relationships, nestedRelationships...)
} else { } else {
// .jar and .war files are present in archives, are others? or generally just consider them top-level? // .jar and .war files are present in archives, are others? or generally just consider them top-level?
nestedArchives := j.fileManifest.GlobMatch(true, "*.jar", "*.war") nestedArchives := j.fileManifest.GlobMatch(true, "**/*.jar", "**/*.war")
if len(nestedArchives) > 0 { if len(nestedArchives) > 0 {
slices.Sort(nestedArchives) slices.Sort(nestedArchives)
errs = unknown.Appendf(errs, j.location, "nested archives not cataloged: %v", strings.Join(nestedArchives, ", ")) errs = unknown.Appendf(errs, j.location, "nested archives not cataloged: %v", strings.Join(nestedArchives, ", "))
@ -252,10 +252,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
return nil, err return nil, err
} }
name, version, lics, parsedPom, err := j.discoverNameVersionLicense(ctx, manifest) name, version, lics, parsedPom := j.discoverNameVersionLicense(ctx, manifest)
if err != nil {
return nil, err
}
var pkgPomProject *pkg.JavaPomProject var pkgPomProject *pkg.JavaPomProject
if parsedPom != nil { if parsedPom != nil {
pkgPomProject = newPomProject(ctx, j.maven, parsedPom.path, parsedPom.project) pkgPomProject = newPomProject(ctx, j.maven, parsedPom.path, parsedPom.project)
@ -280,7 +277,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
}, nil }, nil
} }
func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest *pkg.JavaManifest) (string, string, []pkg.License, *parsedPomProject, error) { func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest *pkg.JavaManifest) (string, string, []pkg.License, *parsedPomProject) {
// we use j.location because we want to associate the license declaration with where we discovered the contents in the manifest // we use j.location because we want to associate the license declaration with where we discovered the contents in the manifest
// TODO: when we support locations of paths within archives we should start passing the specific manifest location object instead of the top jar // TODO: when we support locations of paths within archives we should start passing the specific manifest location object instead of the top jar
lics := pkg.NewLicensesFromLocationWithContext(ctx, j.location, selectLicenses(manifest)...) lics := pkg.NewLicensesFromLocationWithContext(ctx, j.location, selectLicenses(manifest)...)
@ -300,10 +297,7 @@ func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest
} }
if len(lics) == 0 { if len(lics) == 0 {
fileLicenses, err := j.getLicenseFromFileInArchive(ctx) fileLicenses := j.getLicenseFromFileInArchive(ctx)
if err != nil {
return "", "", nil, parsedPom, err
}
if fileLicenses != nil { if fileLicenses != nil {
lics = append(lics, fileLicenses...) lics = append(lics, fileLicenses...)
} }
@ -317,7 +311,7 @@ func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest
lics = j.findLicenseFromJavaMetadata(ctx, groupID, artifactID, version, parsedPom, manifest) lics = j.findLicenseFromJavaMetadata(ctx, groupID, artifactID, version, parsedPom, manifest)
} }
return artifactID, version, lics, parsedPom, nil return artifactID, version, lics, parsedPom
} }
// findLicenseFromJavaMetadata attempts to find license information from all available maven metadata properties and pom info // findLicenseFromJavaMetadata attempts to find license information from all available maven metadata properties and pom info
@ -562,7 +556,7 @@ func getDigestsFromArchive(ctx context.Context, archivePath string) ([]file.Dige
return digests, nil return digests, nil
} }
func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) ([]pkg.License, error) { func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) []pkg.License {
// prefer identified licenses, fall back to unknown // prefer identified licenses, fall back to unknown
var identified []pkg.License var identified []pkg.License
var unidentified []pkg.License var unidentified []pkg.License
@ -578,7 +572,8 @@ func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) ([]pkg.
if len(licenseMatches) > 0 { if len(licenseMatches) > 0 {
contents, err := intFile.ContentsFromZip(ctx, j.archivePath, licenseMatches...) contents, err := intFile.ContentsFromZip(ctx, j.archivePath, licenseMatches...)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to extract java license (%s): %w", j.location, err) log.Debugf("unable to extract java license (%s): %w", j.location, err)
continue
} }
for _, licenseMatch := range licenseMatches { for _, licenseMatch := range licenseMatches {
@ -602,10 +597,10 @@ func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) ([]pkg.
} }
if len(identified) == 0 { if len(identified) == 0 {
return unidentified, nil return unidentified
} }
return identified, nil return identified
} }
func (j *archiveParser) discoverPkgsFromNestedArchives(ctx context.Context, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) { func (j *archiveParser) discoverPkgsFromNestedArchives(ctx context.Context, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {

View File

@ -11,7 +11,7 @@ import (
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
const pomPropertiesGlob = "*pom.properties" const pomPropertiesGlob = "**/*pom.properties"
func parsePomProperties(path string, reader io.Reader) (*pkg.JavaPomProperties, error) { func parsePomProperties(path string, reader io.Reader) (*pkg.JavaPomProperties, error) {
var props pkg.JavaPomProperties var props pkg.JavaPomProperties

View File

@ -16,7 +16,7 @@ import (
) )
const ( const (
pomXMLGlob = "*pom.xml" pomXMLGlob = "**/*pom.xml"
pomCatalogerName = "java-pom-cataloger" pomCatalogerName = "java-pom-cataloger"
) )

View File

@ -27,8 +27,6 @@ func packageLockDependencySpecifier(p pkg.Package) dependency.Specification {
// if the package url is valid, include the name from the package url since this is likely an alias // if the package url is valid, include the name from the package url since this is likely an alias
var fullName = fmt.Sprintf("%s/%s", purl.Namespace, purl.Name) var fullName = fmt.Sprintf("%s/%s", purl.Namespace, purl.Name)
requires = append(requires, fullName) requires = append(requires, fullName)
} else {
fmt.Println("error", err)
} }
requires = append(requires, name) requires = append(requires, name)

View File

@ -6,9 +6,9 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/bmatcuk/doublestar/v4"
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -141,7 +141,7 @@ func getFieldType(key, in string) any {
// of egg metadata (as opposed to a directory that contains more metadata // of egg metadata (as opposed to a directory that contains more metadata
// files). // files).
func isEggRegularFile(path string) bool { func isEggRegularFile(path string) bool {
return intFile.GlobMatch(eggInfoGlob, path) return doublestar.MatchUnvalidated(eggInfoGlob, path)
} }
// determineSitePackagesRootPath returns the path of the site packages root, // determineSitePackagesRootPath returns the path of the site packages root,

View File

@ -11,7 +11,7 @@ import (
// NewGemFileLockCataloger returns a new Bundler cataloger object tailored for parsing index-oriented files (e.g. Gemfile.lock). // NewGemFileLockCataloger returns a new Bundler cataloger object tailored for parsing index-oriented files (e.g. Gemfile.lock).
func NewGemFileLockCataloger() pkg.Cataloger { func NewGemFileLockCataloger() pkg.Cataloger {
return generic.NewCataloger("ruby-gemfile-cataloger"). return generic.NewCataloger("ruby-gemfile-cataloger").
WithParserByGlobs(parseGemFileLockEntries, "**/Gemfile.lock") WithParserByGlobs(parseGemFileLockEntries, "**/Gemfile.lock", "**/Gemfile.next.lock")
} }
// NewInstalledGemSpecCataloger returns a new Bundler cataloger object tailored for detecting installations of gems (e.g. Gemspec). // NewInstalledGemSpecCataloger returns a new Bundler cataloger object tailored for detecting installations of gems (e.g. Gemspec).

View File

@ -17,6 +17,7 @@ func Test_GemFileLock_Globs(t *testing.T) {
fixture: "test-fixtures/glob-paths", fixture: "test-fixtures/glob-paths",
expected: []string{ expected: []string{
"src/Gemfile.lock", "src/Gemfile.lock",
"src/Gemfile.next.lock",
}, },
}, },
} }