diff --git a/.binny.yaml b/.binny.yaml index a7c9abb60..d93e043d5 100644 --- a/.binny.yaml +++ b/.binny.yaml @@ -2,7 +2,7 @@ tools: # we want to use a pinned version of binny to manage the toolchain (so binny manages itself!) - name: binny version: - want: v0.10.0 + want: v0.11.0 method: github-release with: repo: anchore/binny @@ -26,7 +26,7 @@ tools: # used for linting - name: golangci-lint version: - want: v2.6.2 + want: v2.7.2 method: github-release with: repo: golangci/golangci-lint @@ -42,7 +42,7 @@ tools: # used for signing the checksums file at release - name: cosign version: - want: v3.0.2 + want: v3.0.3 method: github-release with: repo: sigstore/cosign @@ -58,7 +58,7 @@ tools: # used to release all artifacts - name: goreleaser version: - want: v2.13.0 + want: v2.13.1 method: github-release with: repo: goreleaser/goreleaser @@ -98,7 +98,7 @@ tools: # used for triggering a release - name: gh version: - want: v2.83.1 + want: v2.83.2 method: github-release with: repo: cli/cli @@ -114,7 +114,7 @@ tools: # used to upload test fixture cache - name: yq version: - want: v4.49.2 + want: v4.50.1 method: github-release with: repo: mikefarah/yq diff --git a/.github/actions/bootstrap/action.yaml b/.github/actions/bootstrap/action.yaml index e0cf9c17c..910d97f78 100644 --- a/.github/actions/bootstrap/action.yaml +++ b/.github/actions/bootstrap/action.yaml @@ -37,7 +37,7 @@ runs: - name: Restore tool cache if: inputs.tools == 'true' id: tool-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ${{ github.workspace }}/.tool key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }} @@ -63,7 +63,7 @@ runs: - name: Restore ORAS cache from github actions if: inputs.download-test-fixture-cache == 'true' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ${{ github.workspace }}/.tmp/oras-cache key: ${{ inputs.cache-key-prefix }}-oras-cache diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index faf52b8be..5f50a170c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,10 @@ -# Description +## Description -Please include a summary of the changes along with any relevant motivation and context, -or link to an issue where this is explained. + - + -- Fixes + ## 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) - [ ] 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 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 + +## Issue references + + diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1bbdd75e5..9d128c8b3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 #v3.29.5 + uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e #v3.29.5 with: languages: ${{ matrix.language }} # 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). # If this step fails, then you should remove it and run the build manually (see below) - 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. # 📚 https://git.io/JvXDl @@ -72,4 +72,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 #v3.29.5 + uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e #v3.29.5 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 07d3b5b17..b9889e0cf 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -181,7 +181,7 @@ jobs: # for updating brew formula in anchore/homebrew-syft 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 with: file: go.mod diff --git a/.github/workflows/update-anchore-dependencies.yml b/.github/workflows/update-anchore-dependencies.yml index 29c987352..d407e07da 100644 --- a/.github/workflows/update-anchore-dependencies.yml +++ b/.github/workflows/update-anchore-dependencies.yml @@ -31,13 +31,13 @@ jobs: with: 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 with: app-id: ${{ secrets.TOKEN_APP_ID }} 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: signoff: true delete-branch: true diff --git a/.github/workflows/update-bootstrap-tools.yml b/.github/workflows/update-bootstrap-tools.yml index 78670e26b..6b3a40346 100644 --- a/.github/workflows/update-bootstrap-tools.yml +++ b/.github/workflows/update-bootstrap-tools.yml @@ -45,13 +45,13 @@ jobs: echo "\`\`\`" } >> $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 with: app-id: ${{ secrets.TOKEN_APP_ID }} 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: signoff: true delete-branch: true diff --git a/.github/workflows/update-cpe-dictionary-index.yml b/.github/workflows/update-cpe-dictionary-index.yml index 58c540745..9dcd60fc6 100644 --- a/.github/workflows/update-cpe-dictionary-index.yml +++ b/.github/workflows/update-cpe-dictionary-index.yml @@ -46,13 +46,13 @@ jobs: - name: Push updated CPE cache to registry 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 with: app-id: ${{ secrets.TOKEN_APP_ID }} 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: signoff: true delete-branch: true diff --git a/.github/workflows/update-spdx-license-list.yaml b/.github/workflows/update-spdx-license-list.yaml index e04acf377..bdade44d9 100644 --- a/.github/workflows/update-spdx-license-list.yaml +++ b/.github/workflows/update-spdx-license-list.yaml @@ -32,7 +32,7 @@ jobs: app_id: ${{ secrets.TOKEN_APP_ID }} 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: signoff: true delete-branch: true diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index f9eb6c3d4..2518a4780 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -1,5 +1,9 @@ name: "Validations" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + on: workflow_dispatch: 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). # see https://github.com/actions/upload-artifact/issues/199 for more info - name: Upload snapshot artifacts - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 + uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1 with: # 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 @@ -118,7 +122,7 @@ jobs: - name: Download snapshot build id: snapshot-cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1 with: path: | snapshot @@ -175,7 +179,7 @@ jobs: - name: Download snapshot build id: snapshot-cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1 with: path: | snapshot @@ -225,7 +229,7 @@ jobs: - name: Download snapshot build id: snapshot-cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1 with: path: | snapshot @@ -262,7 +266,7 @@ jobs: - name: Download snapshot build id: snapshot-cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb #v5.0.1 with: path: | snapshot diff --git a/.golangci.yaml b/.golangci.yaml index 0978c1753..eacba69a6 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -49,6 +49,12 @@ linters: - common-false-positives - legacy - 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: - third_party$ - builtin$ diff --git a/cmd/syft/internal/options/catalog.go b/cmd/syft/internal/options/catalog.go index 592252b79..9ca6bf803 100644 --- a/cmd/syft/internal/options/catalog.go +++ b/cmd/syft/internal/options/catalog.go @@ -189,7 +189,8 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config { WithFromContents(cfg.Golang.MainModuleVersion.FromContents). WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings). WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags), - ), + ). + WithUsePackagesLib(*multiLevelOption(true, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.UsePackagesLib)), JavaScript: javascript.DefaultCatalogerConfig(). WithIncludeDevDependencies(*multiLevelOption(false, cfg.JavaScript.IncludeDevDependencies)). WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)). diff --git a/cmd/syft/internal/options/golang.go b/cmd/syft/internal/options/golang.go index 8eb8e4ba0..10539d79c 100644 --- a/cmd/syft/internal/options/golang.go +++ b/cmd/syft/internal/options/golang.go @@ -16,6 +16,7 @@ type golangConfig struct { Proxy string `json:"proxy" yaml:"proxy" mapstructure:"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"` + UsePackagesLib *bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"` } 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 always show (devel) as the version. Use these options to control heuristics to guess 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.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`) @@ -64,5 +66,6 @@ func defaultGolangConfig() golangConfig { FromContents: def.MainModuleVersion.FromContents, FromBuildSettings: def.MainModuleVersion.FromBuildSettings, }, + UsePackagesLib: nil, // this defaults to true, which is the API default } } diff --git a/go.mod b/go.mod index 13749dbeb..9ff604092 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 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/stereoscope v0.1.13 + github.com/anchore/stereoscope v0.1.16 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be github.com/aquasecurity/go-pep440-version v0.0.1 github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef @@ -39,9 +39,9 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/elliotchance/phpserialize v1.4.0 github.com/facebookincubator/nvdtools v0.1.5 - github.com/github/go-spdx/v2 v2.3.4 + github.com/github/go-spdx/v2 v2.3.5 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-test/deep v1.1.1 github.com/go-viper/mapstructure/v2 v2.4.0 @@ -57,7 +57,7 @@ require ( github.com/hashicorp/hcl/v2 v2.24.0 github.com/iancoleman/strcase v0.3.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/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 github.com/magiconair/properties v1.8.10 @@ -78,7 +78,7 @@ require ( github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb github.com/spdx/tools-golang v0.5.5 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/vbatts/go-mtree v0.6.0 github.com/vifraa/gopom v1.0.0 @@ -89,8 +89,8 @@ require ( go.uber.org/goleak v1.3.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b - golang.org/x/mod v0.30.0 - golang.org/x/net v0.47.0 + golang.org/x/mod v0.31.0 + golang.org/x/net v0.48.0 modernc.org/sqlite v1.40.1 ) @@ -143,7 +143,7 @@ require ( github.com/containerd/typeurl/v2 v2.2.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/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/docker v28.5.2+incompatible // 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/multierr v1.9.0 // 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/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect 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 google.golang.org/api v0.203.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect diff --git a/go.sum b/go.sum index bb4b2a767..6607f720c 100644 --- a/go.sum +++ b/go.sum @@ -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/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/stereoscope v0.1.13 h1:32GKF4+t8j0w+l6aOuEaofkPBLjlVCbsBCiVv3/+8u0= -github.com/anchore/stereoscope v0.1.13/go.mod h1:S4FMIyKp6dh2Ez8U44m/+krKuZKb+bVY9SNK345sAKs= +github.com/anchore/stereoscope v0.1.16 h1:kNcEH/B16DTPMaUIzJAFFaDX3N4mj06xnLFZcUC1gaE= +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/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= 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/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/docker/cli v29.0.3+incompatible h1:8J+PZIcF2xLd6h5sHPsp5pvvJA+Sr2wGQxHkRl53a1E= -github.com/docker/cli v29.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.1.2+incompatible h1:s4QI7drXpIo78OM+CwuthPsO5kCf8cpNsck5PsLVTH8= +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/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 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/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= 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.4/go.mod h1:7LYNCshU2Gj17qZ0heJ5CQUKWWmpd98K7o93K8fJSMk= +github.com/github/go-spdx/v2 v2.3.5 h1:rtRQmzDSq2sU/F2oTIvNQQ+6oInq7yxex5npgY//bJQ= +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/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= 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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -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 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +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/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.7.5 h1:9dJSWTJnsXJVVAbvxIFxeHf/JxoJd7GUl5o3UzhtuiM= -github.com/jedib0t/go-pretty/v6 v6.7.5/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= +github.com/jedib0t/go-pretty/v6 v6.7.7 h1:Y1Id3lJ3k4UB8uwWWy3l8EVFnUlx5chR5+VbsofPNX0= +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/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= 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/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 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.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +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/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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-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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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.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.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +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-20180826012351-8a410e7b638d/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-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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +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-20190226205417-e64efc72b421/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-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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +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-20180830151530-49385e6e1522/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.5.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.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +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-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.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +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.3.0/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.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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +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-20190308202827-9d24e82272b4/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.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/constants.go b/internal/constants.go index f2f05db21..f9be7bcd0 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -3,10 +3,11 @@ package internal const ( // 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. - JSONSchemaVersion = "16.1.1" + JSONSchemaVersion = "16.1.2" // Changelog // 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 ) diff --git a/internal/file/glob_match.go b/internal/file/glob_match.go deleted file mode 100644 index 6befe32e4..000000000 --- a/internal/file/glob_match.go +++ /dev/null @@ -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 -} diff --git a/internal/file/glob_match_test.go b/internal/file/glob_match_test.go deleted file mode 100644 index aab2803dc..000000000 --- a/internal/file/glob_match_test.go +++ /dev/null @@ -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) - } - } -} diff --git a/internal/file/test-fixtures/zip-source/b-file/in-subdir.txt b/internal/file/test-fixtures/zip-source/b-file/in-subdir.txt new file mode 100644 index 000000000..63e98b6f6 --- /dev/null +++ b/internal/file/test-fixtures/zip-source/b-file/in-subdir.txt @@ -0,0 +1 @@ +this file is in a subdirectory \ No newline at end of file diff --git a/internal/file/zip_file_helpers_test.go b/internal/file/zip_file_helpers_test.go index a94304cca..22f92e97b 100644 --- a/internal/file/zip_file_helpers_test.go +++ b/internal/file/zip_file_helpers_test.go @@ -7,14 +7,14 @@ import ( "path/filepath" "syscall" "testing" - - "github.com/stretchr/testify/assert" ) var expectedZipArchiveEntries = []string{ - "some-dir" + string(os.PathSeparator), - filepath.Join("some-dir", "a-file.txt"), + "some-dir/", + "some-dir/a-file.txt", "b-file.txt", + "b-file/", + "b-file/in-subdir.txt", "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, // 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 diff --git a/internal/file/zip_file_manifest.go b/internal/file/zip_file_manifest.go index 8dcb0d2f2..34a2cb66d 100644 --- a/internal/file/zip_file_manifest.go +++ b/internal/file/zip_file_manifest.go @@ -6,6 +6,7 @@ import ( "sort" "strings" + "github.com/bmatcuk/doublestar/v4" "github.com/mholt/archives" "github.com/scylladb/go-set/strset" @@ -44,12 +45,17 @@ func (z ZipFileManifest) Add(entry string, info os.FileInfo) { 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 { uniqueMatches := strset.New() for _, pattern := range patterns { 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) // so that glob logic is consistent inside and outside of ZIP archives normalizedEntry := normalizeZipEntryName(caseInsensitive, entry) @@ -57,7 +63,13 @@ func (z ZipFileManifest) GlobMatch(caseInsensitive bool, patterns ...string) []s if caseInsensitive { 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) } } diff --git a/internal/file/zip_file_manifest_test.go b/internal/file/zip_file_manifest_test.go index 9ebe42224..339048a1d 100644 --- a/internal/file/zip_file_manifest_test.go +++ b/internal/file/zip_file_manifest_test.go @@ -9,6 +9,8 @@ import ( "os" "path" "testing" + + "github.com/stretchr/testify/require" ) func TestNewZipFileManifest(t *testing.T) { @@ -107,23 +109,27 @@ func TestZipFileManifest_GlobMatch(t *testing.T) { cases := []struct { glob string - expected string + expected []string }{ { "/b*", - "b-file.txt", + []string{"b-file.txt"}, }, { - "*/a-file.txt", - "some-dir/a-file.txt", + "/b*/**", + []string{"b-file.txt", "b-file/in-subdir.txt"}, }, { - "*/A-file.txt", - "some-dir/a-file.txt", + "**/a-file.txt", + []string{"some-dir/a-file.txt"}, + }, + { + "**/A-file.txt", + []string{"some-dir/a-file.txt"}, }, { "**/*.zip", - "nested.zip", + []string{"nested.zip"}, }, } @@ -133,11 +139,7 @@ func TestZipFileManifest_GlobMatch(t *testing.T) { results := z.GlobMatch(true, glob) - if len(results) == 1 && results[0] == tc.expected { - return - } - - t.Errorf("unexpected results for glob '%s': %+v", glob, results) + require.ElementsMatch(t, tc.expected, results) }) } } diff --git a/internal/relationship/index.go b/internal/relationship/index.go index ab78c62b4..39aee4ec1 100644 --- a/internal/relationship/index.go +++ b/internal/relationship/index.go @@ -79,6 +79,10 @@ func (i *Index) Remove(id artifact.ID) { func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) { 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{ From: replacement, 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) { + // same as the above + if len(fromMappedByID(i.fromID, mapped.relationship.To.ID())) == 0 { + continue + } i.Add(artifact.Relationship{ From: mapped.relationship.From, To: replacement, diff --git a/schema/json/schema-16.1.1.json b/schema/json/schema-16.1.1.json index a4146954e..284ce6a0a 100644 --- a/schema/json/schema-16.1.1.json +++ b/schema/json/schema-16.1.1.json @@ -1169,7 +1169,11 @@ }, "osCPE": { "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": { "type": "string", @@ -1279,20 +1283,6 @@ "elfSecurityFeatures": { "$ref": "#/$defs/ELFSecurityFeatures", "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", @@ -4235,27 +4225,6 @@ ], "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": { "properties": { "pluginInstallDirectory": { diff --git a/schema/json/schema-16.1.2.json b/schema/json/schema-16.1.2.json new file mode 100644 index 000000000..9e83026ee --- /dev/null +++ b/schema/json/schema-16.1.2.json @@ -0,0 +1,4297 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/16.1.2/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmDbEntry": { + "properties": { + "basepackage": { + "type": "string", + "description": "BasePackage is the base package name this package was built from (source package in Arch build system)" + }, + "package": { + "type": "string", + "description": "Package is the package name as found in the desc file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the desc file" + }, + "description": { + "type": "string", + "description": "Description is a human-readable package description" + }, + "architecture": { + "type": "string", + "description": "Architecture is the target CPU architecture as defined in Arch architecture spec (e.g. x86_64, aarch64, or \"any\" for arch-independent packages)" + }, + "size": { + "type": "integer", + "description": "Size is the installed size in bytes" + }, + "packager": { + "type": "string", + "description": "Packager is the name and email of the person who packaged this (RFC822 format)" + }, + "url": { + "type": "string", + "description": "URL is the upstream project URL" + }, + "validation": { + "type": "string", + "description": "Validation is the validation method used for package integrity (e.g. pgp signature, sha256 checksum)" + }, + "reason": { + "type": "integer", + "description": "Reason is the installation reason tracked by pacman (0=explicitly installed by user, 1=installed as dependency)" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array", + "description": "Files are the files installed by this package" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array", + "description": "Backup is the list of configuration files that pacman backs up before upgrades" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Provides are virtual packages provided by this package (allows other packages to depend on capabilities rather than specific packages)" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Depends are the runtime dependencies required by this package" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ], + "description": "AlpmDBEntry is a struct that represents the package data stored in the pacman flat-file stores for arch linux." + }, + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string", + "description": "Path is the file path relative to the filesystem root" + }, + "type": { + "type": "string", + "description": "Type is the file type (e.g. regular file, directory, symlink)" + }, + "uid": { + "type": "string", + "description": "UID is the file owner user ID as recorded by pacman" + }, + "gid": { + "type": "string", + "description": "GID is the file owner group ID as recorded by pacman" + }, + "time": { + "type": "string", + "format": "date-time", + "description": "Time is the file modification timestamp" + }, + "size": { + "type": "string", + "description": "Size is the file size in bytes" + }, + "link": { + "type": "string", + "description": "Link is the symlink target path if this is a symlink" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array", + "description": "Digests contains file content hashes for integrity verification" + } + }, + "type": "object", + "description": "AlpmFileRecord represents a single file entry within an Arch Linux package with its associated metadata tracked by pacman." + }, + "ApkDbEntry": { + "properties": { + "package": { + "type": "string", + "description": "Package is the package name as found in the installed file" + }, + "originPackage": { + "type": "string", + "description": "OriginPackage is the original source package name this binary was built from (used to track which aport/source built this)" + }, + "maintainer": { + "type": "string", + "description": "Maintainer is the package maintainer name and email" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the installed file" + }, + "architecture": { + "type": "string", + "description": "Architecture is the target CPU architecture" + }, + "url": { + "type": "string", + "description": "URL is the upstream project URL" + }, + "description": { + "type": "string", + "description": "Description is a human-readable package description" + }, + "size": { + "type": "integer", + "description": "Size is the package archive size in bytes (.apk file size)" + }, + "installedSize": { + "type": "integer", + "description": "InstalledSize is the total size of installed files in bytes" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Dependencies are the runtime dependencies required by this package" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Provides are virtual packages provided by this package (for capability-based dependencies)" + }, + "pullChecksum": { + "type": "string", + "description": "Checksum is the package content checksum for integrity verification" + }, + "gitCommitOfApkPort": { + "type": "string", + "description": "GitCommit is the git commit hash of the APK port definition in Alpine's aports repository" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array", + "description": "Files are the files installed by this package" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ], + "description": "ApkDBEntry represents all captured data for the alpine linux package manager flat-file store." + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string", + "description": "Path is the file path relative to the filesystem root" + }, + "ownerUid": { + "type": "string", + "description": "OwnerUID is the file owner user ID" + }, + "ownerGid": { + "type": "string", + "description": "OwnerGID is the file owner group ID" + }, + "permissions": { + "type": "string", + "description": "Permissions is the file permission mode string (e.g. \"0755\", \"0644\")" + }, + "digest": { + "$ref": "#/$defs/Digest", + "description": "Digest is the file content hash for integrity verification" + } + }, + "type": "object", + "required": [ + "path" + ], + "description": "ApkFileRecord represents a single file listing and metadata from a APK DB entry (which may have many of these file records)." + }, + "BinarySignature": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ], + "description": "BinarySignature represents a set of matched values within a binary file." + }, + "BitnamiSbomEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the Bitnami SPDX file" + }, + "arch": { + "type": "string", + "description": "Architecture is the target CPU architecture (amd64 or arm64 in Bitnami images)" + }, + "distro": { + "type": "string", + "description": "Distro is the distribution name this package is for (base OS like debian, ubuntu, etc.)" + }, + "revision": { + "type": "string", + "description": "Revision is the Bitnami-specific package revision number (incremented for Bitnami rebuilds of same upstream version)" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the Bitnami SPDX file" + }, + "path": { + "type": "string", + "description": "Path is the installation path in the filesystem where the package is located" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Files are the file paths owned by this package (tracked via SPDX relationships)" + } + }, + "type": "object", + "required": [ + "name", + "arch", + "distro", + "revision", + "version", + "path", + "files" + ], + "description": "BitnamiSBOMEntry represents all captured data from Bitnami packages described in Bitnami' SPDX files." + }, + "CConanFileEntry": { + "properties": { + "ref": { + "type": "string", + "description": "Ref is the package reference string in format name/version@user/channel" + } + }, + "type": "object", + "required": [ + "ref" + ], + "description": "ConanfileEntry represents a single \"Requires\" entry from a conanfile.txt." + }, + "CConanInfoEntry": { + "properties": { + "ref": { + "type": "string", + "description": "Ref is the package reference string in format name/version@user/channel" + }, + "package_id": { + "type": "string", + "description": "PackageID is a unique package variant identifier" + } + }, + "type": "object", + "required": [ + "ref" + ], + "description": "ConaninfoEntry represents a single \"full_requires\" entry from a conaninfo.txt." + }, + "CConanLockEntry": { + "properties": { + "ref": { + "type": "string", + "description": "Ref is the package reference string in format name/version@user/channel" + }, + "package_id": { + "type": "string", + "description": "PackageID is a unique package variant identifier computed from settings/options (static hash in Conan 1.x, can have collisions with complex dependency graphs)" + }, + "prev": { + "type": "string", + "description": "Prev is the previous lock entry reference for versioning" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Requires are the runtime package dependencies" + }, + "build_requires": { + "items": { + "type": "string" + }, + "type": "array", + "description": "BuildRequires are the build-time dependencies (e.g. cmake, compilers)" + }, + "py_requires": { + "items": { + "type": "string" + }, + "type": "array", + "description": "PythonRequires are the Python dependencies needed for Conan recipes" + }, + "options": { + "$ref": "#/$defs/KeyValues", + "description": "Options are package configuration options as key-value pairs (e.g. shared=True, fPIC=True)" + }, + "path": { + "type": "string", + "description": "Path is the filesystem path to the package in Conan cache" + }, + "context": { + "type": "string", + "description": "Context is the build context information" + } + }, + "type": "object", + "required": [ + "ref" + ], + "description": "ConanV1LockEntry represents a single \"node\" entry from a conan.lock V1 file." + }, + "CConanLockV2Entry": { + "properties": { + "ref": { + "type": "string", + "description": "Ref is the package reference string in format name/version@user/channel" + }, + "packageID": { + "type": "string", + "description": "PackageID is a unique package variant identifier (dynamic in Conan 2.0, more accurate than V1)" + }, + "username": { + "type": "string", + "description": "Username is the Conan user/organization name" + }, + "channel": { + "type": "string", + "description": "Channel is the Conan channel name indicating stability/purpose (e.g. stable, testing, experimental)" + }, + "recipeRevision": { + "type": "string", + "description": "RecipeRevision is a git-like revision hash (RREV) of the recipe" + }, + "packageRevision": { + "type": "string", + "description": "PackageRevision is a git-like revision hash of the built binary package" + }, + "timestamp": { + "type": "string", + "description": "TimeStamp is when this package was built/locked" + } + }, + "type": "object", + "required": [ + "ref" + ], + "description": "ConanV2LockEntry represents a single \"node\" entry from a conan.lock V2 file." + }, + "CPE": { + "properties": { + "cpe": { + "type": "string", + "description": "Value is the CPE string identifier." + }, + "source": { + "type": "string", + "description": "Source is the source where this CPE was obtained or generated from." + } + }, + "type": "object", + "required": [ + "cpe" + ], + "description": "CPE represents a Common Platform Enumeration identifier used for matching packages to known vulnerabilities in security databases." + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ], + "description": "ClassifierMatch represents a single matched value within a binary file and the \"class\" name the search pattern represents." + }, + "CocoaPodfileLockEntry": { + "properties": { + "checksum": { + "type": "string", + "description": "Checksum is the SHA-1 hash of the podspec file for integrity verification (generated via `pod ipc spec ... | openssl sha1`), ensuring all team members use the same pod specification version" + } + }, + "type": "object", + "required": [ + "checksum" + ], + "description": "CocoaPodfileLockEntry represents a single entry from the \"Pods\" section of a Podfile.lock file." + }, + "CondaLink": { + "properties": { + "source": { + "type": "string", + "description": "Source is the original path where the package was extracted from cache." + }, + "type": { + "type": "integer", + "description": "Type indicates the link type (1 for hard link, 2 for soft link, 3 for copy)." + } + }, + "type": "object", + "required": [ + "source", + "type" + ], + "description": "CondaLink represents link metadata from a Conda package's link.json file describing package installation source." + }, + "CondaMetadataEntry": { + "properties": { + "arch": { + "type": "string", + "description": "Arch is the target CPU architecture for the package (e.g., \"arm64\", \"x86_64\")." + }, + "name": { + "type": "string", + "description": "Name is the package name as found in the conda-meta JSON file." + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the conda-meta JSON file." + }, + "build": { + "type": "string", + "description": "Build is the build string identifier (e.g., \"h90dfc92_1014\")." + }, + "build_number": { + "type": "integer", + "description": "BuildNumber is the sequential build number for this version." + }, + "channel": { + "type": "string", + "description": "Channel is the Conda channel URL where the package was retrieved from." + }, + "subdir": { + "type": "string", + "description": "Subdir is the subdirectory within the channel (e.g., \"osx-arm64\", \"linux-64\")." + }, + "noarch": { + "type": "string", + "description": "Noarch indicates if the package is platform-independent (e.g., \"python\", \"generic\")." + }, + "license": { + "type": "string", + "description": "License is the package license identifier." + }, + "license_family": { + "type": "string", + "description": "LicenseFamily is the general license category (e.g., \"MIT\", \"Apache\", \"GPL\")." + }, + "md5": { + "type": "string", + "description": "MD5 is the MD5 hash of the package archive." + }, + "sha256": { + "type": "string", + "description": "SHA256 is the SHA-256 hash of the package archive." + }, + "size": { + "type": "integer", + "description": "Size is the package archive size in bytes." + }, + "timestamp": { + "type": "integer", + "description": "Timestamp is the Unix timestamp when the package was built." + }, + "fn": { + "type": "string", + "description": "Filename is the original package archive filename (e.g., \"zlib-1.2.11-h90dfc92_1014.tar.bz2\")." + }, + "url": { + "type": "string", + "description": "URL is the full download URL for the package archive." + }, + "extracted_package_dir": { + "type": "string", + "description": "ExtractedPackageDir is the local cache directory where the package was extracted." + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Depends is the list of runtime dependencies with version constraints." + }, + "files": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Files is the list of files installed by this package." + }, + "paths_data": { + "$ref": "#/$defs/CondaPathsData", + "description": "PathsData contains detailed file metadata from the paths.json file." + }, + "link": { + "$ref": "#/$defs/CondaLink", + "description": "Link contains installation source metadata from the link.json file." + } + }, + "type": "object", + "required": [ + "name", + "version", + "build", + "build_number" + ], + "description": "CondaMetaPackage represents metadata for a Conda package extracted from the conda-meta/*.json files." + }, + "CondaPathData": { + "properties": { + "_path": { + "type": "string", + "description": "Path is the file path relative to the Conda environment root." + }, + "path_type": { + "type": "string", + "description": "PathType indicates the link type for the file (e.g., \"hardlink\", \"softlink\", \"directory\")." + }, + "sha256": { + "type": "string", + "description": "SHA256 is the SHA-256 hash of the file contents." + }, + "sha256_in_prefix": { + "type": "string", + "description": "SHA256InPrefix is the SHA-256 hash of the file after prefix replacement during installation." + }, + "size_in_bytes": { + "type": "integer", + "description": "SizeInBytes is the file size in bytes." + } + }, + "type": "object", + "required": [ + "_path", + "path_type", + "sha256", + "sha256_in_prefix", + "size_in_bytes" + ], + "description": "CondaPathData represents metadata for a single file within a Conda package from the paths.json file." + }, + "CondaPathsData": { + "properties": { + "paths_version": { + "type": "integer", + "description": "PathsVersion is the schema version of the paths data format." + }, + "paths": { + "items": { + "$ref": "#/$defs/CondaPathData" + }, + "type": "array", + "description": "Paths is the list of file metadata entries for all files in the package." + } + }, + "type": "object", + "required": [ + "paths_version", + "paths" + ], + "description": "CondaPathsData represents the paths.json file structure from a Conda package containing file metadata." + }, + "Coordinates": { + "properties": { + "path": { + "type": "string", + "description": "RealPath is the canonical absolute form of the path accessed (all symbolic links have been followed and relative path components like '.' and '..' have been removed)." + }, + "layerID": { + "type": "string", + "description": "FileSystemID is an ID representing and entire filesystem. For container images, this is a layer digest. For directories or a root filesystem, this is blank." + } + }, + "type": "object", + "required": [ + "path" + ], + "description": "Coordinates contains the minimal information needed to describe how to find a file within any possible source object (e.g." + }, + "DartPubspec": { + "properties": { + "homepage": { + "type": "string", + "description": "Homepage is the package homepage URL" + }, + "repository": { + "type": "string", + "description": "Repository is the source code repository URL" + }, + "documentation": { + "type": "string", + "description": "Documentation is the documentation site URL" + }, + "publish_to": { + "type": "string", + "description": "PublishTo is the package repository to publish to, or \"none\" to prevent accidental publishing" + }, + "environment": { + "$ref": "#/$defs/DartPubspecEnvironment", + "description": "Environment is SDK version constraints for Dart and Flutter" + }, + "platforms": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Platforms are the supported platforms (Android, iOS, web, etc.)" + }, + "ignored_advisories": { + "items": { + "type": "string" + }, + "type": "array", + "description": "IgnoredAdvisories are the security advisories to explicitly ignore for this package" + } + }, + "type": "object", + "description": "DartPubspec is a struct that represents a package described in a pubspec.yaml file" + }, + "DartPubspecEnvironment": { + "properties": { + "sdk": { + "type": "string", + "description": "SDK is the Dart SDK version constraint (e.g. \"\u003e=2.12.0 \u003c3.0.0\")" + }, + "flutter": { + "type": "string", + "description": "Flutter is the Flutter SDK version constraint if this is a Flutter package" + } + }, + "type": "object", + "description": "DartPubspecEnvironment represents SDK version constraints from the environment section of pubspec.yaml." + }, + "DartPubspecLockEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the pubspec.lock file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the pubspec.lock file" + }, + "hosted_url": { + "type": "string", + "description": "HostedURL is the URL of the package repository for hosted packages (typically pub.dev, but can be custom repository identified by hosted-url). When PUB_HOSTED_URL environment variable changes, lockfile tracks the source." + }, + "vcs_url": { + "type": "string", + "description": "VcsURL is the URL of the VCS repository for git/path dependencies (for packages fetched from version control systems like Git)" + } + }, + "type": "object", + "required": [ + "name", + "version" + ], + "description": "DartPubspecLockEntry is a struct that represents a single entry found in the \"packages\" section in a Dart pubspec.lock file." + }, + "Descriptor": { + "properties": { + "name": { + "type": "string", + "description": "Name is the name of the tool that generated this SBOM (e.g., \"syft\")." + }, + "version": { + "type": "string", + "description": "Version is the version of the tool that generated this SBOM." + }, + "configuration": { + "description": "Configuration contains the tool configuration used during SBOM generation." + } + }, + "type": "object", + "required": [ + "name", + "version" + ], + "description": "Descriptor identifies the tool that generated this SBOM document, including its name, version, and configuration used during catalog generation." + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string", + "description": "Algorithm specifies the hash algorithm used (e.g., \"sha256\", \"md5\")." + }, + "value": { + "type": "string", + "description": "Value is the hexadecimal string representation of the hash." + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ], + "description": "Digest represents a cryptographic hash of file contents." + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ], + "description": "Document represents the syft cataloging findings as a JSON document" + }, + "DotnetDepsEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the deps.json file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the deps.json file" + }, + "path": { + "type": "string", + "description": "Path is the relative path to the package within the deps structure (e.g. \"app.metrics/3.0.0\")" + }, + "sha512": { + "type": "string", + "description": "Sha512 is the SHA-512 hash of the NuGet package content WITHOUT the signed content for verification (won't match hash from NuGet API or manual calculation of .nupkg file)" + }, + "hashPath": { + "type": "string", + "description": "HashPath is the relative path to the .nupkg.sha512 hash file (e.g. \"app.metrics.3.0.0.nupkg.sha512\")" + }, + "executables": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + } + }, + "type": "object", + "description": "Executables are the map of .NET Portable Executable files within this package with their version resources" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ], + "description": "DotnetDepsEntry is a struct that represents a single entry found in the \"libraries\" section in a .NET [*.]deps.json file." + }, + "DotnetPackagesLockEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the packages.lock.json file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the packages.lock.json file" + }, + "contentHash": { + "type": "string", + "description": "ContentHash is the hash of the package content for verification" + }, + "type": { + "type": "string", + "description": "Type is the dependency type indicating how this dependency was added (Direct=explicit in project file, Transitive=pulled in by another package, Project=project reference)" + } + }, + "type": "object", + "required": [ + "name", + "version", + "contentHash", + "type" + ], + "description": "DotnetPackagesLockEntry is a struct that represents a single entry found in the \"dependencies\" section in a .NET packages.lock.json file." + }, + "DotnetPortableExecutableEntry": { + "properties": { + "assemblyVersion": { + "type": "string", + "description": "AssemblyVersion is the .NET assembly version number (strong-named version)" + }, + "legalCopyright": { + "type": "string", + "description": "LegalCopyright is the copyright notice string" + }, + "comments": { + "type": "string", + "description": "Comments are additional comments or description embedded in PE resources" + }, + "internalName": { + "type": "string", + "description": "InternalName is the internal name of the file" + }, + "companyName": { + "type": "string", + "description": "CompanyName is the company that produced the file" + }, + "productName": { + "type": "string", + "description": "ProductName is the name of the product this file is part of" + }, + "productVersion": { + "type": "string", + "description": "ProductVersion is the version of the product (may differ from AssemblyVersion)" + } + }, + "type": "object", + "required": [ + "assemblyVersion", + "legalCopyright", + "companyName", + "productName", + "productVersion" + ], + "description": "DotnetPortableExecutableEntry is a struct that represents a single entry found within \"VersionResources\" section of a .NET Portable Executable binary file." + }, + "DpkgArchiveEntry": { + "properties": { + "package": { + "type": "string", + "description": "Package is the package name as found in the status file" + }, + "source": { + "type": "string", + "description": "Source is the source package name this binary was built from (one source can produce multiple binary packages)" + }, + "version": { + "type": "string", + "description": "Version is the binary package version as found in the status file" + }, + "sourceVersion": { + "type": "string", + "description": "SourceVersion is the source package version (may differ from binary version when binNMU rebuilds occur)" + }, + "architecture": { + "type": "string", + "description": "Architecture is the target architecture per Debian spec (specific arch like amd64/arm64, wildcard like any, architecture-independent \"all\", or \"source\" for source packages)" + }, + "maintainer": { + "type": "string", + "description": "Maintainer is the package maintainer's name and email in RFC822 format (name must come first, then email in angle brackets)" + }, + "installedSize": { + "type": "integer", + "description": "InstalledSize is the total size of installed files in kilobytes" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Provides are the virtual packages provided by this package (allows other packages to depend on capabilities. Can include versioned provides like \"libdigest-md5-perl (= 2.55.01)\")" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Depends are the packages required for this package to function (will not be installed unless these requirements are met, creates strict ordering constraint)" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array", + "description": "PreDepends are the packages that must be installed and configured BEFORE even starting installation of this package (stronger than Depends, discouraged unless absolutely necessary as it adds strict constraints for apt)" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array", + "description": "Files are the files installed by this package" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ], + "description": "DpkgArchiveEntry represents package metadata extracted from a .deb archive file." + }, + "DpkgDbEntry": { + "properties": { + "package": { + "type": "string", + "description": "Package is the package name as found in the status file" + }, + "source": { + "type": "string", + "description": "Source is the source package name this binary was built from (one source can produce multiple binary packages)" + }, + "version": { + "type": "string", + "description": "Version is the binary package version as found in the status file" + }, + "sourceVersion": { + "type": "string", + "description": "SourceVersion is the source package version (may differ from binary version when binNMU rebuilds occur)" + }, + "architecture": { + "type": "string", + "description": "Architecture is the target architecture per Debian spec (specific arch like amd64/arm64, wildcard like any, architecture-independent \"all\", or \"source\" for source packages)" + }, + "maintainer": { + "type": "string", + "description": "Maintainer is the package maintainer's name and email in RFC822 format (name must come first, then email in angle brackets)" + }, + "installedSize": { + "type": "integer", + "description": "InstalledSize is the total size of installed files in kilobytes" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Provides are the virtual packages provided by this package (allows other packages to depend on capabilities. Can include versioned provides like \"libdigest-md5-perl (= 2.55.01)\")" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Depends are the packages required for this package to function (will not be installed unless these requirements are met, creates strict ordering constraint)" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array", + "description": "PreDepends are the packages that must be installed and configured BEFORE even starting installation of this package (stronger than Depends, discouraged unless absolutely necessary as it adds strict constraints for apt)" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array", + "description": "Files are the files installed by this package" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ], + "description": "DpkgDBEntry represents all captured data for a Debian package DB entry; available fields are described at http://manpages.ubuntu.com/manpages/xenial/man1/dpkg-query.1.html in the --showformat section." + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string", + "description": "Path is the file path relative to the filesystem root" + }, + "digest": { + "$ref": "#/$defs/Digest", + "description": "Digest is the file content hash (typically MD5 for dpkg compatibility with legacy systems)" + }, + "isConfigFile": { + "type": "boolean", + "description": "IsConfigFile is whether this file is marked as a configuration file (dpkg will preserve user modifications during upgrades)" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ], + "description": "DpkgFileRecord represents a single file attributed to a debian package." + }, + "ELFSecurityFeatures": { + "properties": { + "symbolTableStripped": { + "type": "boolean", + "description": "SymbolTableStripped indicates whether debugging symbols have been removed." + }, + "stackCanary": { + "type": "boolean", + "description": "StackCanary indicates whether stack smashing protection is enabled." + }, + "nx": { + "type": "boolean", + "description": "NoExecutable indicates whether NX (no-execute) protection is enabled for the stack." + }, + "relRO": { + "type": "string", + "description": "RelocationReadOnly indicates the RELRO protection level." + }, + "pie": { + "type": "boolean", + "description": "PositionIndependentExecutable indicates whether the binary is compiled as PIE." + }, + "dso": { + "type": "boolean", + "description": "DynamicSharedObject indicates whether the binary is a shared library." + }, + "safeStack": { + "type": "boolean", + "description": "LlvmSafeStack represents a compiler-based security mechanism that separates the stack into a safe stack for storing return addresses and other critical data, and an unsafe stack for everything else, to mitigate stack-based memory corruption errors\nsee https://clang.llvm.org/docs/SafeStack.html" + }, + "cfi": { + "type": "boolean", + "description": "ControlFlowIntegrity represents runtime checks to ensure a program's control flow adheres to the legal paths determined at compile time, thus protecting against various types of control-flow hijacking attacks\nsee https://clang.llvm.org/docs/ControlFlowIntegrity.html" + }, + "fortify": { + "type": "boolean", + "description": "ClangFortifySource is a broad suite of extensions to libc aimed at catching misuses of common library functions\nsee https://android.googlesource.com/platform//bionic/+/d192dbecf0b2a371eb127c0871f77a9caf81c4d2/docs/clang_fortify_anatomy.md" + } + }, + "type": "object", + "required": [ + "symbolTableStripped", + "nx", + "relRO", + "pie", + "dso" + ], + "description": "ELFSecurityFeatures captures security hardening and protection mechanisms in ELF binaries." + }, + "ElfBinaryPackageNoteJsonPayload": { + "properties": { + "type": { + "type": "string", + "description": "Type is the type of the package (e.g. \"rpm\", \"deb\", \"apk\", etc.)" + }, + "architecture": { + "type": "string", + "description": "Architecture of the binary package (e.g. \"amd64\", \"arm\", etc.)" + }, + "osCPE": { + "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)\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": { + "type": "string", + "description": "OS is the OS name, typically corresponding to ID in os-release (e.g. \"fedora\")" + }, + "osVersion": { + "type": "string", + "description": "osVersion is the version of the OS, typically corresponding to VERSION_ID in os-release (e.g. \"33\")" + }, + "system": { + "type": "string", + "description": "System is a context-specific name for the system that the binary package is intended to run on or a part of" + }, + "vendor": { + "type": "string", + "description": "Vendor is the individual or organization that produced the source code for the binary" + }, + "sourceRepo": { + "type": "string", + "description": "SourceRepo is the URL to the source repository for which the binary was built from" + }, + "commit": { + "type": "string", + "description": "Commit is the commit hash of the source repository for which the binary was built from" + } + }, + "type": "object", + "description": "ELFBinaryPackageNoteJSONPayload Represents metadata captured from the .note.package section of an ELF-formatted binary" + }, + "ElixirMixLockEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the mix.lock file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the mix.lock file" + }, + "pkgHash": { + "type": "string", + "description": "PkgHash is the outer checksum (SHA-256) of the entire Hex package tarball for integrity verification (preferred method, replaces deprecated inner checksum)" + }, + "pkgHashExt": { + "type": "string", + "description": "PkgHashExt is the extended package hash format (inner checksum is deprecated - SHA-256 of concatenated file contents excluding CHECKSUM file, now replaced by outer checksum)" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ], + "description": "ElixirMixLockEntry is a struct that represents a single entry in a mix.lock file" + }, + "ErlangRebarLockEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the rebar.lock file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the rebar.lock file" + }, + "pkgHash": { + "type": "string", + "description": "PkgHash is the outer checksum (SHA-256) of the entire Hex package tarball for integrity verification (preferred method over deprecated inner checksum)" + }, + "pkgHashExt": { + "type": "string", + "description": "PkgHashExt is the extended package hash format (inner checksum deprecated - was SHA-256 of concatenated file contents)" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ], + "description": "ErlangRebarLockEntry represents a single package entry from the \"deps\" section within an Erlang rebar.lock file." + }, + "Executable": { + "properties": { + "format": { + "type": "string", + "description": "Format denotes either ELF, Mach-O, or PE" + }, + "hasExports": { + "type": "boolean", + "description": "HasExports indicates whether the binary exports symbols." + }, + "hasEntrypoint": { + "type": "boolean", + "description": "HasEntrypoint indicates whether the binary has an entry point function." + }, + "importedLibraries": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ImportedLibraries lists the shared libraries required by this executable." + }, + "elfSecurityFeatures": { + "$ref": "#/$defs/ELFSecurityFeatures", + "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", + "required": [ + "format", + "hasExports", + "hasEntrypoint", + "importedLibraries" + ], + "description": "Executable contains metadata about binary files and their security features." + }, + "File": { + "properties": { + "id": { + "type": "string", + "description": "ID is a unique identifier for this file within the SBOM." + }, + "location": { + "$ref": "#/$defs/Coordinates", + "description": "Location is the file path and layer information where this file was found." + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry", + "description": "Metadata contains filesystem metadata such as permissions, ownership, and file type." + }, + "contents": { + "type": "string", + "description": "Contents is the file contents for small files." + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array", + "description": "Digests contains cryptographic hashes of the file contents." + }, + "licenses": { + "items": { + "$ref": "#/$defs/FileLicense" + }, + "type": "array", + "description": "Licenses contains license information discovered within this file." + }, + "executable": { + "$ref": "#/$defs/Executable", + "description": "Executable contains executable metadata if this file is a binary." + }, + "unknowns": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Unknowns contains unknown fields for forward compatibility." + } + }, + "type": "object", + "required": [ + "id", + "location" + ], + "description": "File represents a file discovered during cataloging with its metadata, content digests, licenses, and relationships to packages." + }, + "FileLicense": { + "properties": { + "value": { + "type": "string", + "description": "Value is the raw license identifier or text as found in the file." + }, + "spdxExpression": { + "type": "string", + "description": "SPDXExpression is the parsed SPDX license expression." + }, + "type": { + "type": "string", + "description": "Type is the license type classification (e.g., declared, concluded, discovered)." + }, + "evidence": { + "$ref": "#/$defs/FileLicenseEvidence", + "description": "Evidence contains supporting evidence for this license detection." + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type" + ], + "description": "FileLicense represents license information discovered within a file's contents or metadata, including the matched license text and SPDX expression." + }, + "FileLicenseEvidence": { + "properties": { + "confidence": { + "type": "integer", + "description": "Confidence is the confidence score for this license detection (0-100)." + }, + "offset": { + "type": "integer", + "description": "Offset is the byte offset where the license text starts in the file." + }, + "extent": { + "type": "integer", + "description": "Extent is the length of the license text in bytes." + } + }, + "type": "object", + "required": [ + "confidence", + "offset", + "extent" + ], + "description": "FileLicenseEvidence contains supporting evidence for a license detection in a file, including the byte offset, extent, and confidence level." + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer", + "description": "Mode is the Unix file permission mode in octal format." + }, + "type": { + "type": "string", + "description": "Type is the file type (e.g., \"RegularFile\", \"Directory\", \"SymbolicLink\")." + }, + "linkDestination": { + "type": "string", + "description": "LinkDestination is the target path for symbolic links." + }, + "userID": { + "type": "integer", + "description": "UserID is the file owner user ID." + }, + "groupID": { + "type": "integer", + "description": "GroupID is the file owner group ID." + }, + "mimeType": { + "type": "string", + "description": "MIMEType is the MIME type of the file contents." + }, + "size": { + "type": "integer", + "description": "Size is the file size in bytes." + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ], + "description": "FileMetadataEntry contains filesystem-level metadata attributes such as permissions, ownership, type, and size for a cataloged file." + }, + "GgufFileHeader": { + "properties": { + "ggufVersion": { + "type": "integer", + "description": "GGUFVersion is the GGUF format version (e.g., 3)" + }, + "fileSize": { + "type": "integer", + "description": "FileSize is the size of the GGUF file in bytes (best-effort if available from resolver)" + }, + "architecture": { + "type": "string", + "description": "Architecture is the model architecture (from general.architecture, e.g., \"qwen3moe\", \"llama\")" + }, + "quantization": { + "type": "string", + "description": "Quantization is the quantization type (e.g., \"IQ4_NL\", \"Q4_K_M\")" + }, + "parameters": { + "type": "integer", + "description": "Parameters is the number of model parameters (if present in header)" + }, + "tensorCount": { + "type": "integer", + "description": "TensorCount is the number of tensors in the model" + }, + "header": { + "type": "object", + "description": "RemainingKeyValues contains the remaining key-value pairs from the GGUF header that are not already\nrepresented as typed fields above. This preserves additional metadata fields for reference\n(namespaced with general.*, llama.*, etc.) while avoiding duplication." + }, + "metadataHash": { + "type": "string", + "description": "MetadataKeyValuesHash is a xx64 hash of all key-value pairs from the GGUF header metadata.\nThis hash is computed over the complete header metadata (including the fields extracted\ninto typed fields above) and provides a stable identifier for the model configuration\nacross different file locations or remotes. It allows matching identical models even\nwhen stored in different repositories or with different filenames." + } + }, + "type": "object", + "required": [ + "ggufVersion", + "tensorCount" + ], + "description": "GGUFFileHeader represents metadata extracted from a GGUF (GPT-Generated Unified Format) model file." + }, + "GithubActionsUseStatement": { + "properties": { + "value": { + "type": "string", + "description": "Value is the action reference (e.g. \"actions/checkout@v3\")" + }, + "comment": { + "type": "string", + "description": "Comment is the inline comment associated with this uses statement" + } + }, + "type": "object", + "required": [ + "value" + ], + "description": "GitHubActionsUseStatement represents a single 'uses' statement in a GitHub Actions workflow file referencing an action or reusable workflow." + }, + "GoModuleBuildinfoEntry": { + "properties": { + "goBuildSettings": { + "$ref": "#/$defs/KeyValues", + "description": "BuildSettings contains the Go build settings and flags used to compile the binary (e.g., GOARCH, GOOS, CGO_ENABLED)." + }, + "goCompiledVersion": { + "type": "string", + "description": "GoCompiledVersion is the version of Go used to compile the binary." + }, + "architecture": { + "type": "string", + "description": "Architecture is the target CPU architecture for the binary (extracted from GOARCH build setting)." + }, + "h1Digest": { + "type": "string", + "description": "H1Digest is the Go module hash in h1: format for the main module from go.sum." + }, + "mainModule": { + "type": "string", + "description": "MainModule is the main module path for the binary (e.g., \"github.com/anchore/syft\")." + }, + "goCryptoSettings": { + "items": { + "type": "string" + }, + "type": "array", + "description": "GoCryptoSettings contains FIPS and cryptographic configuration settings if present." + }, + "goExperiments": { + "items": { + "type": "string" + }, + "type": "array", + "description": "GoExperiments lists experimental Go features enabled during compilation (e.g., \"arenas\", \"cgocheck2\")." + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ], + "description": "GolangBinaryBuildinfoEntry represents all captured data for a Golang binary" + }, + "GoModuleEntry": { + "properties": { + "h1Digest": { + "type": "string", + "description": "H1Digest is the Go module hash in h1: format from go.sum for verifying module contents." + } + }, + "type": "object", + "description": "GolangModuleEntry represents all captured data for a Golang source scan with go.mod/go.sum" + }, + "GoSourceEntry": { + "properties": { + "h1Digest": { + "type": "string", + "description": "H1Digest is the Go module hash in h1: format from go.sum for verifying module contents." + }, + "os": { + "type": "string", + "description": "OperatingSystem is the target OS for build constraints (e.g., \"linux\", \"darwin\", \"windows\")." + }, + "architecture": { + "type": "string", + "description": "Architecture is the target CPU architecture for build constraints (e.g., \"amd64\", \"arm64\")." + }, + "buildTags": { + "type": "string", + "description": "BuildTags are the build tags used to conditionally compile code (e.g., \"integration,debug\")." + }, + "cgoEnabled": { + "type": "boolean", + "description": "CgoEnabled indicates whether CGO was enabled for this package." + } + }, + "type": "object", + "required": [ + "cgoEnabled" + ], + "description": "GolangSourceEntry represents all captured data for a Golang package found through source analysis" + }, + "HaskellHackageStackEntry": { + "properties": { + "pkgHash": { + "type": "string", + "description": "PkgHash is the package content hash for verification" + } + }, + "type": "object", + "description": "HackageStackYamlEntry represents a single entry from the \"extra-deps\" section of a stack.yaml file." + }, + "HaskellHackageStackLockEntry": { + "properties": { + "pkgHash": { + "type": "string", + "description": "PkgHash is the package content hash for verification" + }, + "snapshotURL": { + "type": "string", + "description": "SnapshotURL is the URL to the Stack snapshot this package came from" + } + }, + "type": "object", + "description": "HackageStackYamlLockEntry represents a single entry from the \"packages\" section of a stack.yaml.lock file." + }, + "HomebrewFormula": { + "properties": { + "tap": { + "type": "string", + "description": "Tap is Homebrew tap this formula belongs to (e.g. \"homebrew/core\")" + }, + "homepage": { + "type": "string", + "description": "Homepage is the upstream project homepage URL" + }, + "description": { + "type": "string", + "description": "Description is a human-readable formula description" + } + }, + "type": "object", + "description": "HomebrewFormula represents metadata about a Homebrew formula package extracted from formula JSON files." + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array", + "description": "IDLikes represents a list of distribution IDs that this Linux distribution is similar to or derived from, as defined in os-release ID_LIKE field." + }, + "JavaArchive": { + "properties": { + "virtualPath": { + "type": "string", + "description": "VirtualPath is path within the archive hierarchy, where nested entries are delimited with ':' (for nested JARs)" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest", + "description": "Manifest is parsed META-INF/MANIFEST.MF contents" + }, + "pomProperties": { + "$ref": "#/$defs/JavaPomProperties", + "description": "PomProperties is parsed pom.properties file contents" + }, + "pomProject": { + "$ref": "#/$defs/JavaPomProject", + "description": "PomProject is parsed pom.xml file contents" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array", + "description": "ArchiveDigests is cryptographic hashes of the archive file" + } + }, + "type": "object", + "required": [ + "virtualPath" + ], + "description": "JavaArchive encapsulates all Java ecosystem metadata for a package as well as an (optional) parent relationship." + }, + "JavaJvmInstallation": { + "properties": { + "release": { + "$ref": "#/$defs/JavaVMRelease", + "description": "Release is JVM release information and version details" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Files are the list of files that are part of this JVM installation" + } + }, + "type": "object", + "required": [ + "release", + "files" + ], + "description": "JavaVMInstallation represents a Java Virtual Machine installation discovered on the system with its release information and file list." + }, + "JavaManifest": { + "properties": { + "main": { + "$ref": "#/$defs/KeyValues", + "description": "Main is main manifest attributes as key-value pairs" + }, + "sections": { + "items": { + "$ref": "#/$defs/KeyValues" + }, + "type": "array", + "description": "Sections are the named sections from the manifest (e.g. per-entry attributes)" + } + }, + "type": "object", + "description": "JavaManifest represents the fields of interest extracted from a Java archive's META-INF/MANIFEST.MF file." + }, + "JavaPomParent": { + "properties": { + "groupId": { + "type": "string", + "description": "GroupID is the parent Maven group identifier" + }, + "artifactId": { + "type": "string", + "description": "ArtifactID is the parent Maven artifact identifier" + }, + "version": { + "type": "string", + "description": "Version is the parent version (child inherits configuration from this specific version of parent POM)" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ], + "description": "JavaPomParent contains the fields within the \u003cparent\u003e tag in a pom.xml file" + }, + "JavaPomProject": { + "properties": { + "path": { + "type": "string", + "description": "Path is path to the pom.xml file within the archive" + }, + "parent": { + "$ref": "#/$defs/JavaPomParent", + "description": "Parent is the parent POM reference for inheritance (child POMs inherit configuration from parent)" + }, + "groupId": { + "type": "string", + "description": "GroupID is Maven group identifier (reversed domain name like org.apache.maven)" + }, + "artifactId": { + "type": "string", + "description": "ArtifactID is Maven artifact identifier (project name)" + }, + "version": { + "type": "string", + "description": "Version is project version (together with groupId and artifactId forms Maven coordinates groupId:artifactId:version)" + }, + "name": { + "type": "string", + "description": "Name is a human-readable project name (displayed in Maven-generated documentation)" + }, + "description": { + "type": "string", + "description": "Description is detailed project description" + }, + "url": { + "type": "string", + "description": "URL is the project URL (typically project website or repository)" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ], + "description": "JavaPomProject represents fields of interest extracted from a Java archive's pom.xml file." + }, + "JavaPomProperties": { + "properties": { + "path": { + "type": "string", + "description": "Path is path to the pom.properties file within the archive" + }, + "name": { + "type": "string", + "description": "Name is the project name" + }, + "groupId": { + "type": "string", + "description": "GroupID is Maven group identifier uniquely identifying the project across all projects (follows reversed domain name convention like com.company.project)" + }, + "artifactId": { + "type": "string", + "description": "ArtifactID is Maven artifact identifier, the name of the jar/artifact (unique within the groupId scope)" + }, + "version": { + "type": "string", + "description": "Version is artifact version" + }, + "scope": { + "type": "string", + "description": "Scope is dependency scope determining when dependency is available (compile=default all phases, test=test compilation/execution only, runtime=runtime and test not compile, provided=expected from JDK or container)" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Extra is additional custom properties not in standard Maven coordinates" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ], + "description": "JavaPomProperties represents the fields of interest extracted from a Java archive's pom.properties file." + }, + "JavaVMRelease": { + "properties": { + "implementor": { + "type": "string", + "description": "Implementor is extracted with the `java.vendor` JVM property" + }, + "implementorVersion": { + "type": "string", + "description": "ImplementorVersion is extracted with the `java.vendor.version` JVM property" + }, + "javaRuntimeVersion": { + "type": "string", + "description": "JavaRuntimeVersion is extracted from the 'java.runtime.version' JVM property" + }, + "javaVersion": { + "type": "string", + "description": "JavaVersion matches that from `java -version` command output" + }, + "javaVersionDate": { + "type": "string", + "description": "JavaVersionDate is extracted from the 'java.version.date' JVM property" + }, + "libc": { + "type": "string", + "description": "Libc can either be 'glibc' or 'musl'" + }, + "modules": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Modules is a list of JVM modules that are packaged" + }, + "osArch": { + "type": "string", + "description": "OsArch is the target CPU architecture" + }, + "osName": { + "type": "string", + "description": "OsName is the name of the target runtime operating system environment" + }, + "osVersion": { + "type": "string", + "description": "OsVersion is the version of the target runtime operating system environment" + }, + "source": { + "type": "string", + "description": "Source refers to the origin repository of OpenJDK source" + }, + "buildSource": { + "type": "string", + "description": "BuildSource Git SHA of the build repository" + }, + "buildSourceRepo": { + "type": "string", + "description": "BuildSourceRepo refers to rhe repository URL for the build source" + }, + "sourceRepo": { + "type": "string", + "description": "SourceRepo refers to the OpenJDK repository URL" + }, + "fullVersion": { + "type": "string", + "description": "FullVersion is extracted from the 'java.runtime.version' JVM property" + }, + "semanticVersion": { + "type": "string", + "description": "SemanticVersion is derived from the OpenJDK version" + }, + "buildInfo": { + "type": "string", + "description": "BuildInfo contains additional build information" + }, + "jvmVariant": { + "type": "string", + "description": "JvmVariant specifies the JVM variant (e.g., Hotspot or OpenJ9)" + }, + "jvmVersion": { + "type": "string", + "description": "JvmVersion is extracted from the 'java.vm.version' JVM property" + }, + "imageType": { + "type": "string", + "description": "ImageType can be 'JDK' or 'JRE'" + }, + "buildType": { + "type": "string", + "description": "BuildType can be 'commercial' (used in some older oracle JDK distributions)" + } + }, + "type": "object", + "description": "JavaVMRelease represents JVM version and build information extracted from the release file in a Java installation." + }, + "JavascriptNpmPackage": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in package.json" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in package.json" + }, + "author": { + "type": "string", + "description": "Author is package author name" + }, + "homepage": { + "type": "string", + "description": "Homepage is project homepage URL" + }, + "description": { + "type": "string", + "description": "Description is a human-readable package description" + }, + "url": { + "type": "string", + "description": "URL is repository or project URL" + }, + "private": { + "type": "boolean", + "description": "Private is whether this is a private package" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ], + "description": "NpmPackage represents the contents of a javascript package.json file." + }, + "JavascriptNpmPackageLockEntry": { + "properties": { + "resolved": { + "type": "string", + "description": "Resolved is URL where this package was downloaded from (registry source)" + }, + "integrity": { + "type": "string", + "description": "Integrity is Subresource Integrity hash for verification using standard SRI format (sha512-... or sha1-...). npm changed from SHA-1 to SHA-512 in newer versions. For registry sources this is the integrity from registry, for remote tarballs it's SHA-512 of the file. npm verifies tarball matches this hash before unpacking, throwing EINTEGRITY error if mismatch detected." + }, + "dependencies": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Dependencies is a map of dependencies and their version markers, i.e. \"lodash\": \"^1.0.0\"" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity", + "dependencies" + ], + "description": "NpmPackageLockEntry represents a single entry within the \"packages\" section of a package-lock.json file." + }, + "JavascriptPnpmLockEntry": { + "properties": { + "resolution": { + "$ref": "#/$defs/PnpmLockResolution", + "description": "Resolution is the resolution information for the package" + }, + "dependencies": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Dependencies is a map of dependencies and their versions" + } + }, + "type": "object", + "required": [ + "resolution", + "dependencies" + ], + "description": "PnpmLockEntry represents a single entry in the \"packages\" section of a pnpm-lock.yaml file." + }, + "JavascriptYarnLockEntry": { + "properties": { + "resolved": { + "type": "string", + "description": "Resolved is URL where this package was downloaded from" + }, + "integrity": { + "type": "string", + "description": "Integrity is Subresource Integrity hash for verification (SRI format)" + }, + "dependencies": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Dependencies is a map of dependencies and their versions" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity", + "dependencies" + ], + "description": "YarnLockEntry represents a single entry section of a yarn.lock file." + }, + "KeyValue": { + "properties": { + "key": { + "type": "string", + "description": "Key is the key name" + }, + "value": { + "type": "string", + "description": "Value is the value associated with the key" + } + }, + "type": "object", + "required": [ + "key", + "value" + ], + "description": "KeyValue represents a single key-value pair." + }, + "KeyValues": { + "items": { + "$ref": "#/$defs/KeyValue" + }, + "type": "array", + "description": "KeyValues represents an ordered collection of key-value pairs that preserves insertion order." + }, + "License": { + "properties": { + "value": { + "type": "string", + "description": "Value is the raw license identifier or expression as found." + }, + "spdxExpression": { + "type": "string", + "description": "SPDXExpression is the parsed SPDX license expression." + }, + "type": { + "type": "string", + "description": "Type is the license type classification (e.g., declared, concluded, discovered)." + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array", + "description": "URLs are URLs where license text or information can be found." + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array", + "description": "Locations are file locations where this license was discovered." + }, + "contents": { + "type": "string", + "description": "Contents is the full license text content." + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ], + "description": "License represents software license information discovered for a package, including SPDX expressions and supporting evidence locations." + }, + "LinuxKernelArchive": { + "properties": { + "name": { + "type": "string", + "description": "Name is kernel name (typically \"Linux\")" + }, + "architecture": { + "type": "string", + "description": "Architecture is the target CPU architecture" + }, + "version": { + "type": "string", + "description": "Version is kernel version string" + }, + "extendedVersion": { + "type": "string", + "description": "ExtendedVersion is additional version information" + }, + "buildTime": { + "type": "string", + "description": "BuildTime is when the kernel was built" + }, + "author": { + "type": "string", + "description": "Author is who built the kernel" + }, + "format": { + "type": "string", + "description": "Format is kernel image format (e.g. bzImage, zImage)" + }, + "rwRootFS": { + "type": "boolean", + "description": "RWRootFS is whether root filesystem is mounted read-write" + }, + "swapDevice": { + "type": "integer", + "description": "SwapDevice is swap device number" + }, + "rootDevice": { + "type": "integer", + "description": "RootDevice is root device number" + }, + "videoMode": { + "type": "string", + "description": "VideoMode is default video mode setting" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ], + "description": "LinuxKernel represents all captured data for a Linux kernel" + }, + "LinuxKernelModule": { + "properties": { + "name": { + "type": "string", + "description": "Name is module name" + }, + "version": { + "type": "string", + "description": "Version is module version string" + }, + "sourceVersion": { + "type": "string", + "description": "SourceVersion is the source code version identifier" + }, + "path": { + "type": "string", + "description": "Path is the filesystem path to the .ko kernel object file (absolute path)" + }, + "description": { + "type": "string", + "description": "Description is a human-readable module description" + }, + "author": { + "type": "string", + "description": "Author is module author name and email" + }, + "license": { + "type": "string", + "description": "License is module license (e.g. GPL, BSD) which must be compatible with kernel" + }, + "kernelVersion": { + "type": "string", + "description": "KernelVersion is kernel version this module was built for" + }, + "versionMagic": { + "type": "string", + "description": "VersionMagic is version magic string for compatibility checking (includes kernel version, SMP status, module loading capabilities like \"3.17.4-302.fc21.x86_64 SMP mod_unload modversions\"). Module will NOT load if vermagic doesn't match running kernel." + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object", + "description": "Parameters are the module parameters that can be configured at load time (user-settable values like module options)" + } + }, + "type": "object", + "description": "LinuxKernelModule represents a loadable kernel module (.ko file) with its metadata, parameters, and dependencies." + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string", + "description": "Type is parameter data type (e.g. int, string, bool, array types)" + }, + "description": { + "type": "string", + "description": "Description is a human-readable parameter description explaining what the parameter controls" + } + }, + "type": "object", + "description": "LinuxKernelModuleParameter represents a configurable parameter for a kernel module with its type and description." + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string", + "description": "PrettyName is a human-readable operating system name with version." + }, + "name": { + "type": "string", + "description": "Name is the operating system name without version information." + }, + "id": { + "type": "string", + "description": "ID is the lower-case operating system identifier (e.g., \"ubuntu\", \"rhel\")." + }, + "idLike": { + "$ref": "#/$defs/IDLikes", + "description": "IDLike is a list of operating system IDs this distribution is similar to or derived from." + }, + "version": { + "type": "string", + "description": "Version is the operating system version including codename if available." + }, + "versionID": { + "type": "string", + "description": "VersionID is the operating system version number or identifier." + }, + "versionCodename": { + "type": "string", + "description": "VersionCodename is the operating system release codename (e.g., \"jammy\", \"bullseye\")." + }, + "buildID": { + "type": "string", + "description": "BuildID is a build identifier for the operating system." + }, + "imageID": { + "type": "string", + "description": "ImageID is an identifier for container or cloud images." + }, + "imageVersion": { + "type": "string", + "description": "ImageVersion is the version for container or cloud images." + }, + "variant": { + "type": "string", + "description": "Variant is the operating system variant name (e.g., \"Server\", \"Workstation\")." + }, + "variantID": { + "type": "string", + "description": "VariantID is the lower-case operating system variant identifier." + }, + "homeURL": { + "type": "string", + "description": "HomeURL is the homepage URL for the operating system." + }, + "supportURL": { + "type": "string", + "description": "SupportURL is the support or help URL for the operating system." + }, + "bugReportURL": { + "type": "string", + "description": "BugReportURL is the bug reporting URL for the operating system." + }, + "privacyPolicyURL": { + "type": "string", + "description": "PrivacyPolicyURL is the privacy policy URL for the operating system." + }, + "cpeName": { + "type": "string", + "description": "CPEName is the Common Platform Enumeration name for the operating system." + }, + "supportEnd": { + "type": "string", + "description": "SupportEnd is the end of support date or version identifier." + }, + "extendedSupport": { + "type": "boolean", + "description": "ExtendedSupport indicates whether extended security or support is available." + } + }, + "type": "object", + "description": "LinuxRelease contains Linux distribution identification and version information extracted from /etc/os-release or similar system files." + }, + "Location": { + "properties": { + "path": { + "type": "string", + "description": "RealPath is the canonical absolute form of the path accessed (all symbolic links have been followed and relative path components like '.' and '..' have been removed)." + }, + "layerID": { + "type": "string", + "description": "FileSystemID is an ID representing and entire filesystem. For container images, this is a layer digest. For directories or a root filesystem, this is blank." + }, + "accessPath": { + "type": "string", + "description": "AccessPath is the path used to retrieve file contents (which may or may not have hardlinks / symlinks in the path)" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "accessPath" + ], + "description": "Location represents a path relative to a particular filesystem resolved to a specific file.Reference." + }, + "LuarocksPackage": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the .rockspec file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the .rockspec file" + }, + "license": { + "type": "string", + "description": "License is license identifier" + }, + "homepage": { + "type": "string", + "description": "Homepage is project homepage URL" + }, + "description": { + "type": "string", + "description": "Description is a human-readable package description" + }, + "url": { + "type": "string", + "description": "URL is the source download URL" + }, + "dependencies": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Dependencies are the map of dependency names to version constraints" + } + }, + "type": "object", + "required": [ + "name", + "version", + "license", + "homepage", + "description", + "url", + "dependencies" + ], + "description": "LuaRocksPackage represents a Lua package managed by the LuaRocks package manager with metadata from .rockspec files." + }, + "MicrosoftKbPatch": { + "properties": { + "product_id": { + "type": "string", + "description": "ProductID is MSRC Product ID (e.g. \"Windows 10 Version 1703 for 32-bit Systems\")" + }, + "kb": { + "type": "string", + "description": "Kb is Knowledge Base article number (e.g. \"5001028\")" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ], + "description": "MicrosoftKbPatch represents a Windows Knowledge Base patch identifier associated with a specific Microsoft product from the MSRC (Microsoft Security Response Center)." + }, + "NixDerivation": { + "properties": { + "path": { + "type": "string", + "description": "Path is path to the .drv file in Nix store" + }, + "system": { + "type": "string", + "description": "System is target system string indicating where derivation can be built (e.g. \"x86_64-linux\", \"aarch64-darwin\"). Must match current system for local builds." + }, + "inputDerivations": { + "items": { + "$ref": "#/$defs/NixDerivationReference" + }, + "type": "array", + "description": "InputDerivations are the list of other derivations that were inputs to this build (dependencies)" + }, + "inputSources": { + "items": { + "type": "string" + }, + "type": "array", + "description": "InputSources are the list of source file paths that were inputs to this build" + } + }, + "type": "object", + "description": "NixDerivation represents a Nix .drv file that describes how to build a package including inputs, outputs, and build instructions." + }, + "NixDerivationReference": { + "properties": { + "path": { + "type": "string", + "description": "Path is path to the referenced .drv file" + }, + "outputs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Outputs are which outputs of the referenced derivation were used (e.g. [\"out\"], [\"bin\", \"dev\"])" + } + }, + "type": "object", + "description": "NixDerivationReference represents a reference to another derivation used as a build input or runtime dependency." + }, + "NixStoreEntry": { + "properties": { + "path": { + "type": "string", + "description": "Path is full store path for this output (e.g. /nix/store/abc123...-package-1.0)" + }, + "output": { + "type": "string", + "description": "Output is the specific output name for multi-output packages (empty string for default \"out\" output, can be \"bin\", \"dev\", \"doc\", etc.)" + }, + "outputHash": { + "type": "string", + "description": "OutputHash is hash prefix of the store path basename (first part before the dash)" + }, + "derivation": { + "$ref": "#/$defs/NixDerivation", + "description": "Derivation is information about the .drv file that describes how this package was built" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Files are the list of files under the nix/store path for this package" + } + }, + "type": "object", + "required": [ + "outputHash" + ], + "description": "NixStoreEntry represents a package in the Nix store (/nix/store) with its derivation information and metadata." + }, + "OpamPackage": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the .opam file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the .opam file" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Licenses are the list of applicable licenses" + }, + "url": { + "type": "string", + "description": "URL is download URL for the package source" + }, + "checksum": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Checksums are the list of checksums for verification" + }, + "homepage": { + "type": "string", + "description": "Homepage is project homepage URL" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Dependencies are the list of required dependencies" + } + }, + "type": "object", + "required": [ + "name", + "version", + "licenses", + "url", + "checksum", + "homepage", + "dependencies" + ], + "description": "OpamPackage represents an OCaml package managed by the OPAM package manager with metadata from .opam files." + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "language": { + "type": "string" + }, + "cpes": { + "$ref": "#/$defs/cpes" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmDbEntry" + }, + { + "$ref": "#/$defs/ApkDbEntry" + }, + { + "$ref": "#/$defs/BinarySignature" + }, + { + "$ref": "#/$defs/BitnamiSbomEntry" + }, + { + "$ref": "#/$defs/CConanFileEntry" + }, + { + "$ref": "#/$defs/CConanInfoEntry" + }, + { + "$ref": "#/$defs/CConanLockEntry" + }, + { + "$ref": "#/$defs/CConanLockV2Entry" + }, + { + "$ref": "#/$defs/CocoaPodfileLockEntry" + }, + { + "$ref": "#/$defs/CondaMetadataEntry" + }, + { + "$ref": "#/$defs/DartPubspec" + }, + { + "$ref": "#/$defs/DartPubspecLockEntry" + }, + { + "$ref": "#/$defs/DotnetDepsEntry" + }, + { + "$ref": "#/$defs/DotnetPackagesLockEntry" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + }, + { + "$ref": "#/$defs/DpkgArchiveEntry" + }, + { + "$ref": "#/$defs/DpkgDbEntry" + }, + { + "$ref": "#/$defs/ElfBinaryPackageNoteJsonPayload" + }, + { + "$ref": "#/$defs/ElixirMixLockEntry" + }, + { + "$ref": "#/$defs/ErlangRebarLockEntry" + }, + { + "$ref": "#/$defs/GgufFileHeader" + }, + { + "$ref": "#/$defs/GithubActionsUseStatement" + }, + { + "$ref": "#/$defs/GoModuleBuildinfoEntry" + }, + { + "$ref": "#/$defs/GoModuleEntry" + }, + { + "$ref": "#/$defs/GoSourceEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$ref": "#/$defs/HomebrewFormula" + }, + { + "$ref": "#/$defs/JavaArchive" + }, + { + "$ref": "#/$defs/JavaJvmInstallation" + }, + { + "$ref": "#/$defs/JavascriptNpmPackage" + }, + { + "$ref": "#/$defs/JavascriptNpmPackageLockEntry" + }, + { + "$ref": "#/$defs/JavascriptPnpmLockEntry" + }, + { + "$ref": "#/$defs/JavascriptYarnLockEntry" + }, + { + "$ref": "#/$defs/LinuxKernelArchive" + }, + { + "$ref": "#/$defs/LinuxKernelModule" + }, + { + "$ref": "#/$defs/LuarocksPackage" + }, + { + "$ref": "#/$defs/MicrosoftKbPatch" + }, + { + "$ref": "#/$defs/NixStoreEntry" + }, + { + "$ref": "#/$defs/OpamPackage" + }, + { + "$ref": "#/$defs/PeBinary" + }, + { + "$ref": "#/$defs/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PhpPearEntry" + }, + { + "$ref": "#/$defs/PhpPeclEntry" + }, + { + "$ref": "#/$defs/PortageDbEntry" + }, + { + "$ref": "#/$defs/PythonPackage" + }, + { + "$ref": "#/$defs/PythonPdmLockEntry" + }, + { + "$ref": "#/$defs/PythonPipRequirementsEntry" + }, + { + "$ref": "#/$defs/PythonPipfileLockEntry" + }, + { + "$ref": "#/$defs/PythonPoetryLockEntry" + }, + { + "$ref": "#/$defs/PythonUvLockEntry" + }, + { + "$ref": "#/$defs/RDescription" + }, + { + "$ref": "#/$defs/RpmArchive" + }, + { + "$ref": "#/$defs/RpmDbEntry" + }, + { + "$ref": "#/$defs/RubyGemspec" + }, + { + "$ref": "#/$defs/RustCargoAuditEntry" + }, + { + "$ref": "#/$defs/RustCargoLockEntry" + }, + { + "$ref": "#/$defs/SnapEntry" + }, + { + "$ref": "#/$defs/SwiftPackageManagerLockEntry" + }, + { + "$ref": "#/$defs/SwiplpackPackage" + }, + { + "$ref": "#/$defs/TerraformLockProviderEntry" + }, + { + "$ref": "#/$defs/WordpressPluginEntry" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ], + "description": "Package represents a pkg.Package object specialized for JSON marshaling and unmarshalling." + }, + "PeBinary": { + "properties": { + "VersionResources": { + "$ref": "#/$defs/KeyValues", + "description": "VersionResources contains key-value pairs extracted from the PE file's version resource section (e.g., FileVersion, ProductName, CompanyName)." + } + }, + "type": "object", + "required": [ + "VersionResources" + ], + "description": "PEBinary represents metadata captured from a Portable Executable formatted binary (dll, exe, etc.)" + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string", + "description": "Name is author's full name" + }, + "email": { + "type": "string", + "description": "Email is author's email address" + }, + "homepage": { + "type": "string", + "description": "Homepage is author's personal or company website" + } + }, + "type": "object", + "required": [ + "name" + ], + "description": "PhpComposerAuthors represents author information for a PHP Composer package from the authors field in composer.json." + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string", + "description": "Type is reference type (git for source VCS, zip/tar for dist archives)" + }, + "url": { + "type": "string", + "description": "URL is the URL to the resource (git repository URL or archive download URL)" + }, + "reference": { + "type": "string", + "description": "Reference is git commit hash or version tag for source, or archive version for dist" + }, + "shasum": { + "type": "string", + "description": "Shasum is SHA hash of the archive file for integrity verification (dist only)" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ], + "description": "PhpComposerExternalReference represents source or distribution information for a PHP package, indicating where the package code is retrieved from." + }, + "PhpComposerInstalledEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is package name in vendor/package format (e.g. symfony/console)" + }, + "version": { + "type": "string", + "description": "Version is the package version" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference", + "description": "Source is the source repository information for development (typically git repo, used when passing --prefer-source). Originates from source code repository." + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference", + "description": "Dist is distribution archive information for production (typically zip/tar, default install method). Packaged version of released code." + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Require is runtime dependencies with version constraints (package will not install unless these requirements can be met)" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Provide is virtual packages/functionality provided by this package (allows other packages to depend on capabilities)" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "RequireDev is development-only dependencies (not installed in production, only when developing this package or running tests)" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Suggest is optional but recommended dependencies (suggestions for packages that would extend functionality)" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array", + "description": "License is the list of license identifiers (SPDX format)" + }, + "type": { + "type": "string", + "description": "Type is package type indicating purpose (library=reusable code, project=application, metapackage=aggregates dependencies, etc.)" + }, + "notification-url": { + "type": "string", + "description": "NotificationURL is the URL to notify when package is installed (for tracking/statistics)" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Bin is the list of binary/executable files that should be added to PATH" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array", + "description": "Authors are the list of package authors with name/email/homepage" + }, + "description": { + "type": "string", + "description": "Description is a human-readable package description" + }, + "homepage": { + "type": "string", + "description": "Homepage is project homepage URL" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Keywords are the list of keywords for package discovery/search" + }, + "time": { + "type": "string", + "description": "Time is timestamp when this package version was released" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ], + "description": "PhpComposerInstalledEntry represents a single package entry from a composer v1/v2 \"installed.json\" files (very similar to composer.lock files)." + }, + "PhpComposerLockEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is package name in vendor/package format (e.g. symfony/console)" + }, + "version": { + "type": "string", + "description": "Version is the package version" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference", + "description": "Source is the source repository information for development (typically git repo, used when passing --prefer-source). Originates from source code repository." + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference", + "description": "Dist is distribution archive information for production (typically zip/tar, default install method). Packaged version of released code." + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Require is runtime dependencies with version constraints (package will not install unless these requirements can be met)" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Provide is virtual packages/functionality provided by this package (allows other packages to depend on capabilities)" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "RequireDev is development-only dependencies (not installed in production, only when developing this package or running tests)" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object", + "description": "Suggest is optional but recommended dependencies (suggestions for packages that would extend functionality)" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array", + "description": "License is the list of license identifiers (SPDX format)" + }, + "type": { + "type": "string", + "description": "Type is package type indicating purpose (library=reusable code, project=application, metapackage=aggregates dependencies, etc.)" + }, + "notification-url": { + "type": "string", + "description": "NotificationURL is the URL to notify when package is installed (for tracking/statistics)" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Bin is the list of binary/executable files that should be added to PATH" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array", + "description": "Authors are the list of package authors with name/email/homepage" + }, + "description": { + "type": "string", + "description": "Description is a human-readable package description" + }, + "homepage": { + "type": "string", + "description": "Homepage is project homepage URL" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Keywords are the list of keywords for package discovery/search" + }, + "time": { + "type": "string", + "description": "Time is timestamp when this package version was released" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ], + "description": "PhpComposerLockEntry represents a single package entry found from a composer.lock file." + }, + "PhpPearEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name" + }, + "channel": { + "type": "string", + "description": "Channel is PEAR channel this package is from" + }, + "version": { + "type": "string", + "description": "Version is the package version" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array", + "description": "License is the list of applicable licenses" + } + }, + "type": "object", + "required": [ + "name", + "version" + ], + "description": "PhpPearEntry represents a single package entry found within php pear metadata files." + }, + "PhpPeclEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name" + }, + "channel": { + "type": "string", + "description": "Channel is PEAR channel this package is from" + }, + "version": { + "type": "string", + "description": "Version is the package version" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array", + "description": "License is the list of applicable licenses" + } + }, + "type": "object", + "required": [ + "name", + "version" + ], + "description": "PhpPeclEntry represents a single package entry found within php pecl metadata files." + }, + "PnpmLockResolution": { + "properties": { + "integrity": { + "type": "string", + "description": "Integrity is Subresource Integrity hash for verification (SRI format)" + } + }, + "type": "object", + "required": [ + "integrity" + ], + "description": "PnpmLockResolution contains package resolution metadata from pnpm lockfiles, including the integrity hash used for verification." + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer", + "description": "InstalledSize is total size of installed files in bytes" + }, + "licenses": { + "type": "string", + "description": "Licenses is license string which may be an expression (e.g. \"GPL-2 OR Apache-2.0\")" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array", + "description": "Files are the files installed by this package (tracked in CONTENTS file)" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ], + "description": "PortageEntry represents a single package entry in the portage DB flat-file store." + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string", + "description": "Path is the file path relative to the filesystem root" + }, + "digest": { + "$ref": "#/$defs/Digest", + "description": "Digest is file content hash (MD5 for regular files in CONTENTS format: \"obj filename md5hash mtime\")" + } + }, + "type": "object", + "required": [ + "path" + ], + "description": "PortageFileRecord represents a single file attributed to a portage package." + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string", + "description": "URL is the source URL from which the package was installed." + }, + "commitId": { + "type": "string", + "description": "CommitID is the VCS commit hash if installed from version control." + }, + "vcs": { + "type": "string", + "description": "VCS is the version control system type (e.g., \"git\", \"hg\")." + } + }, + "type": "object", + "required": [ + "url" + ], + "description": "PythonDirectURLOriginInfo represents installation source metadata from direct_url.json for packages installed from VCS or direct URLs." + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string", + "description": "Algorithm is the hash algorithm used (e.g., \"sha256\")." + }, + "value": { + "type": "string", + "description": "Value is the hex-encoded hash digest value." + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ], + "description": "PythonFileDigest represents the file metadata for a single file attributed to a python package." + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string", + "description": "Path is the installed file path from the RECORD file." + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest", + "description": "Digest contains the hash algorithm and value for file integrity verification." + }, + "size": { + "type": "string", + "description": "Size is the file size in bytes as a string." + } + }, + "type": "object", + "required": [ + "path" + ], + "description": "PythonFileRecord represents a single entry within a RECORD file for a python wheel or egg package" + }, + "PythonPackage": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name from the Name field in PKG-INFO or METADATA." + }, + "version": { + "type": "string", + "description": "Version is the package version from the Version field in PKG-INFO or METADATA." + }, + "author": { + "type": "string", + "description": "Author is the package author name from the Author field." + }, + "authorEmail": { + "type": "string", + "description": "AuthorEmail is the package author's email address from the Author-Email field." + }, + "platform": { + "type": "string", + "description": "Platform indicates the target platform for the package (e.g., \"any\", \"linux\", \"win32\")." + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array", + "description": "Files are the installed files listed in the RECORD file for wheels or installed-files.txt for eggs." + }, + "sitePackagesRootPath": { + "type": "string", + "description": "SitePackagesRootPath is the root directory path containing the package (e.g., \"/usr/lib/python3.9/site-packages\")." + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array", + "description": "TopLevelPackages are the top-level Python module names from top_level.txt file." + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo", + "description": "DirectURLOrigin contains VCS or direct URL installation information from direct_url.json." + }, + "requiresPython": { + "type": "string", + "description": "RequiresPython specifies the Python version requirement (e.g., \"\u003e=3.6\")." + }, + "requiresDist": { + "items": { + "type": "string" + }, + "type": "array", + "description": "RequiresDist lists the package dependencies with version specifiers from Requires-Dist fields." + }, + "providesExtra": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ProvidesExtra lists optional feature names that can be installed via extras (e.g., \"dev\", \"test\")." + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ], + "description": "PythonPackage represents all captured data for a python egg or wheel package (specifically as outlined in the PyPA core metadata specification https://packaging.python.org/en/latest/specifications/core-metadata/)." + }, + "PythonPdmFileEntry": { + "properties": { + "url": { + "type": "string", + "description": "URL is the file download URL" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest", + "description": "Digest is the hash digest of the file hosted at the URL" + } + }, + "type": "object", + "required": [ + "url", + "digest" + ] + }, + "PythonPdmLockEntry": { + "properties": { + "summary": { + "type": "string", + "description": "Summary provides a description of the package" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonPdmFileEntry" + }, + "type": "array", + "description": "Files are the package files with their paths and hash digests (for the base package without extras)" + }, + "marker": { + "type": "string", + "description": "Marker is the \"environment\" --conditional expressions that determine whether a package should be installed based on the runtime environment" + }, + "requiresPython": { + "type": "string", + "description": "RequiresPython specifies the Python version requirement (e.g., \"\u003e=3.6\")." + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Dependencies are the dependency specifications for the base package (without extras)" + }, + "extras": { + "items": { + "$ref": "#/$defs/PythonPdmLockExtraVariant" + }, + "type": "array", + "description": "Extras contains variants for different extras combinations (PDM may have multiple entries per package)" + } + }, + "type": "object", + "required": [ + "summary", + "files" + ], + "description": "PythonPdmLockEntry represents a single package entry within a pdm.lock file." + }, + "PythonPdmLockExtraVariant": { + "properties": { + "extras": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Extras are the optional extras enabled for this variant (e.g., [\"toml\"], [\"dev\"], or [\"toml\", \"dev\"])" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Dependencies are the dependencies specific to this extras variant" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonPdmFileEntry" + }, + "type": "array", + "description": "Files are the package files specific to this variant (only populated if different from base)" + }, + "marker": { + "type": "string", + "description": "Marker is the environment conditional expression for this variant (e.g., \"python_version \u003c \\\"3.11\\\"\")" + } + }, + "type": "object", + "required": [ + "extras" + ], + "description": "PythonPdmLockExtraVariant represents a specific extras combination variant within a PDM lock file." + }, + "PythonPipRequirementsEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name from the requirements file." + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Extras are the optional features to install from the package (e.g., package[dev,test])." + }, + "versionConstraint": { + "type": "string", + "description": "VersionConstraint specifies version requirements (e.g., \"\u003e=1.0,\u003c2.0\")." + }, + "url": { + "type": "string", + "description": "URL is the direct download URL or VCS URL if specified instead of a PyPI package." + }, + "markers": { + "type": "string", + "description": "Markers are environment marker expressions for conditional installation (e.g., \"python_version \u003e= '3.8'\")." + } + }, + "type": "object", + "required": [ + "name", + "versionConstraint" + ], + "description": "PythonRequirementsEntry represents a single entry within a [*-]requirements.txt file." + }, + "PythonPipfileLockEntry": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Hashes are the package file hash values in the format \"algorithm:digest\" for integrity verification." + }, + "index": { + "type": "string", + "description": "Index is the PyPI index name where the package should be fetched from." + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ], + "description": "PythonPipfileLockEntry represents a single package entry within a Pipfile.lock file." + }, + "PythonPoetryLockDependencyEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the dependency package name." + }, + "version": { + "type": "string", + "description": "Version is the locked version or version constraint for the dependency." + }, + "optional": { + "type": "boolean", + "description": "Optional indicates whether this dependency is optional (only needed for certain extras)." + }, + "markers": { + "type": "string", + "description": "Markers are environment marker expressions that conditionally enable the dependency (e.g., \"python_version \u003e= '3.8'\")." + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Extras are the optional feature names from the dependency that should be installed." + } + }, + "type": "object", + "required": [ + "name", + "version", + "optional" + ], + "description": "PythonPoetryLockDependencyEntry represents a single dependency entry within a Poetry lock file." + }, + "PythonPoetryLockEntry": { + "properties": { + "index": { + "type": "string", + "description": "Index is the package repository name where the package should be fetched from." + }, + "dependencies": { + "items": { + "$ref": "#/$defs/PythonPoetryLockDependencyEntry" + }, + "type": "array", + "description": "Dependencies are the package's runtime dependencies with version constraints." + }, + "extras": { + "items": { + "$ref": "#/$defs/PythonPoetryLockExtraEntry" + }, + "type": "array", + "description": "Extras are optional feature groups that include additional dependencies." + } + }, + "type": "object", + "required": [ + "index", + "dependencies" + ], + "description": "PythonPoetryLockEntry represents a single package entry within a Pipfile.lock file." + }, + "PythonPoetryLockExtraEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the optional feature name (e.g., \"dev\", \"test\")." + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Dependencies are the package names required when this extra is installed." + } + }, + "type": "object", + "required": [ + "name", + "dependencies" + ], + "description": "PythonPoetryLockExtraEntry represents an optional feature group in a Poetry lock file." + }, + "PythonUvLockDependencyEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the dependency package name." + }, + "optional": { + "type": "boolean", + "description": "Optional indicates whether this dependency is optional (only needed for certain extras)." + }, + "markers": { + "type": "string", + "description": "Markers are environment marker expressions that conditionally enable the dependency (e.g., \"python_version \u003e= '3.8'\")." + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Extras are the optional feature names from the dependency that should be installed." + } + }, + "type": "object", + "required": [ + "name", + "optional" + ], + "description": "PythonUvLockDependencyEntry represents a single dependency entry within a uv lock file." + }, + "PythonUvLockEntry": { + "properties": { + "index": { + "type": "string", + "description": "Index is the package repository name where the package should be fetched from." + }, + "dependencies": { + "items": { + "$ref": "#/$defs/PythonUvLockDependencyEntry" + }, + "type": "array", + "description": "Dependencies are the package's runtime dependencies with version constraints." + }, + "extras": { + "items": { + "$ref": "#/$defs/PythonUvLockExtraEntry" + }, + "type": "array", + "description": "Extras are optional feature groups that include additional dependencies." + } + }, + "type": "object", + "required": [ + "index", + "dependencies" + ], + "description": "PythonUvLockEntry represents a single package entry within a uv.lock file." + }, + "PythonUvLockExtraEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the optional feature name (e.g., \"dev\", \"test\")." + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Dependencies are the package names required when this extra is installed." + } + }, + "type": "object", + "required": [ + "name", + "dependencies" + ], + "description": "PythonUvLockExtraEntry represents an optional feature group in a uv lock file." + }, + "RDescription": { + "properties": { + "title": { + "type": "string", + "description": "Title is short one-line package title" + }, + "description": { + "type": "string", + "description": "Description is detailed package description" + }, + "author": { + "type": "string", + "description": "Author is package author(s)" + }, + "maintainer": { + "type": "string", + "description": "Maintainer is current package maintainer" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array", + "description": "URL is the list of related URLs" + }, + "repository": { + "type": "string", + "description": "Repository is CRAN or other repository name" + }, + "built": { + "type": "string", + "description": "Built is R version and platform this was built with" + }, + "needsCompilation": { + "type": "boolean", + "description": "NeedsCompilation is whether this package requires compilation" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Imports are the packages imported in the NAMESPACE" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Depends are the packages this package depends on" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Suggests are the optional packages that extend functionality" + } + }, + "type": "object", + "description": "RDescription represents metadata from an R package DESCRIPTION file containing package information, dependencies, and author details." + }, + "Relationship": { + "properties": { + "parent": { + "type": "string", + "description": "Parent is the ID of the parent artifact in this relationship." + }, + "child": { + "type": "string", + "description": "Child is the ID of the child artifact in this relationship." + }, + "type": { + "type": "string", + "description": "Type is the relationship type (e.g., \"contains\", \"dependency-of\", \"ancestor-of\")." + }, + "metadata": { + "description": "Metadata contains additional relationship-specific metadata." + } + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ], + "description": "Relationship represents a directed relationship between two artifacts in the SBOM, such as package-contains-file or package-depends-on-package." + }, + "RpmArchive": { + "properties": { + "name": { + "type": "string", + "description": "Name is the RPM package name as found in the RPM database." + }, + "version": { + "type": "string", + "description": "Version is the upstream version of the package." + }, + "epoch": { + "oneOf": [ + { + "type": "integer", + "description": "Epoch is the version epoch used to force upgrade ordering (null if not set)." + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string", + "description": "Arch is the target CPU architecture (e.g., \"x86_64\", \"aarch64\", \"noarch\")." + }, + "release": { + "type": "string", + "description": "Release is the package release number or distribution-specific version suffix." + }, + "sourceRpm": { + "type": "string", + "description": "SourceRpm is the source RPM filename that was used to build this package." + }, + "signatures": { + "items": { + "$ref": "#/$defs/RpmSignature" + }, + "type": "array", + "description": "Signatures contains GPG signature metadata for package verification." + }, + "size": { + "type": "integer", + "description": "Size is the total installed size of the package in bytes." + }, + "vendor": { + "type": "string", + "description": "Vendor is the organization that packaged the software." + }, + "modularityLabel": { + "type": "string", + "description": "ModularityLabel identifies the module stream for modular RPM packages (e.g., \"nodejs:12:20200101\")." + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Provides lists the virtual packages and capabilities this package provides." + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Requires lists the dependencies required by this package." + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array", + "description": "Files are the file records for all files owned by this package." + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ], + "description": "RpmArchive represents package metadata extracted directly from a .rpm archive file, containing the same information as an RPM database entry." + }, + "RpmDbEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is the RPM package name as found in the RPM database." + }, + "version": { + "type": "string", + "description": "Version is the upstream version of the package." + }, + "epoch": { + "oneOf": [ + { + "type": "integer", + "description": "Epoch is the version epoch used to force upgrade ordering (null if not set)." + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string", + "description": "Arch is the target CPU architecture (e.g., \"x86_64\", \"aarch64\", \"noarch\")." + }, + "release": { + "type": "string", + "description": "Release is the package release number or distribution-specific version suffix." + }, + "sourceRpm": { + "type": "string", + "description": "SourceRpm is the source RPM filename that was used to build this package." + }, + "signatures": { + "items": { + "$ref": "#/$defs/RpmSignature" + }, + "type": "array", + "description": "Signatures contains GPG signature metadata for package verification." + }, + "size": { + "type": "integer", + "description": "Size is the total installed size of the package in bytes." + }, + "vendor": { + "type": "string", + "description": "Vendor is the organization that packaged the software." + }, + "modularityLabel": { + "type": "string", + "description": "ModularityLabel identifies the module stream for modular RPM packages (e.g., \"nodejs:12:20200101\")." + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Provides lists the virtual packages and capabilities this package provides." + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Requires lists the dependencies required by this package." + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array", + "description": "Files are the file records for all files owned by this package." + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ], + "description": "RpmDBEntry represents all captured data from a RPM DB package entry." + }, + "RpmFileRecord": { + "properties": { + "path": { + "type": "string", + "description": "Path is the absolute file path where the file is installed." + }, + "mode": { + "type": "integer", + "description": "Mode is the file permission mode bits following Unix stat.h conventions." + }, + "size": { + "type": "integer", + "description": "Size is the file size in bytes." + }, + "digest": { + "$ref": "#/$defs/Digest", + "description": "Digest contains the hash algorithm and value for file integrity verification." + }, + "userName": { + "type": "string", + "description": "UserName is the owner username for the file." + }, + "groupName": { + "type": "string", + "description": "GroupName is the group name for the file." + }, + "flags": { + "type": "string", + "description": "Flags indicates the file type (e.g., \"%config\", \"%doc\", \"%ghost\")." + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ], + "description": "RpmFileRecord represents the file metadata for a single file attributed to a RPM package." + }, + "RpmSignature": { + "properties": { + "algo": { + "type": "string", + "description": "PublicKeyAlgorithm is the public key algorithm used for signing (e.g., \"RSA\")." + }, + "hash": { + "type": "string", + "description": "HashAlgorithm is the hash algorithm used for the signature (e.g., \"SHA256\")." + }, + "created": { + "type": "string", + "description": "Created is the timestamp when the signature was created." + }, + "issuer": { + "type": "string", + "description": "IssuerKeyID is the GPG key ID that created the signature." + } + }, + "type": "object", + "required": [ + "algo", + "hash", + "created", + "issuer" + ], + "description": "RpmSignature represents a GPG signature for an RPM package used for authenticity verification." + }, + "RubyGemspec": { + "properties": { + "name": { + "type": "string", + "description": "Name is gem name as specified in the gemspec" + }, + "version": { + "type": "string", + "description": "Version is gem version as specified in the gemspec" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Files is logical list of files in the gem (NOT directly usable as filesystem paths. Example: bundler gem lists \"lib/bundler/vendor/uri/lib/uri/ldap.rb\" but actual path is \"/usr/local/lib/ruby/3.2.0/bundler/vendor/uri/lib/uri/ldap.rb\". Would need gem installation path, ruby version, and env vars like GEM_HOME to resolve actual paths.)" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Authors are the list of gem authors (stored as array regardless of using `author` or `authors` method in gemspec)" + }, + "homepage": { + "type": "string", + "description": "Homepage is project homepage URL" + } + }, + "type": "object", + "required": [ + "name", + "version" + ], + "description": "RubyGemspec represents all metadata parsed from the *.gemspec file" + }, + "RustCargoAuditEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is crate name as specified in audit section of the build binary" + }, + "version": { + "type": "string", + "description": "Version is crate version as specified in audit section of the build binary" + }, + "source": { + "type": "string", + "description": "Source is the source registry or repository where this crate came from" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source" + ], + "description": "RustBinaryAuditEntry represents Rust crate metadata extracted from a compiled binary using cargo-auditable format." + }, + "RustCargoLockEntry": { + "properties": { + "name": { + "type": "string", + "description": "Name is crate name as specified in Cargo.toml" + }, + "version": { + "type": "string", + "description": "Version is crate version as specified in Cargo.toml" + }, + "source": { + "type": "string", + "description": "Source is the source registry or repository URL in format \"registry+https://github.com/rust-lang/crates.io-index\" for registry packages" + }, + "checksum": { + "type": "string", + "description": "Checksum is content checksum for registry packages only (hexadecimal string). Cargo doesn't require or include checksums for git dependencies. Used to detect MITM attacks by verifying downloaded crate matches lockfile checksum." + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Dependencies are the list of dependencies with version constraints" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ], + "description": "RustCargoLockEntry represents a locked dependency from a Cargo.lock file with precise version and checksum information." + }, + "Schema": { + "properties": { + "version": { + "type": "string", + "description": "Version is the JSON schema version for this document format." + }, + "url": { + "type": "string", + "description": "URL is the URL to the JSON schema definition document." + } + }, + "type": "object", + "required": [ + "version", + "url" + ], + "description": "Schema specifies the JSON schema version and URL reference that defines the structure and validation rules for this document format." + }, + "SnapEntry": { + "properties": { + "snapType": { + "type": "string", + "description": "SnapType indicates the snap type (base, kernel, app, gadget, or snapd)." + }, + "base": { + "type": "string", + "description": "Base is the base snap name that this snap depends on (e.g., \"core20\", \"core22\")." + }, + "snapName": { + "type": "string", + "description": "SnapName is the snap package name." + }, + "snapVersion": { + "type": "string", + "description": "SnapVersion is the snap package version." + }, + "architecture": { + "type": "string", + "description": "Architecture is the target CPU architecture (e.g., \"amd64\", \"arm64\")." + } + }, + "type": "object", + "required": [ + "snapType", + "base", + "snapName", + "snapVersion", + "architecture" + ], + "description": "SnapEntry represents metadata for a Snap package extracted from snap.yaml or snapcraft.yaml files." + }, + "Source": { + "properties": { + "id": { + "type": "string", + "description": "ID is a unique identifier for the analyzed source artifact." + }, + "name": { + "type": "string", + "description": "Name is the name of the analyzed artifact (e.g., image name, directory path)." + }, + "version": { + "type": "string", + "description": "Version is the version of the analyzed artifact (e.g., image tag)." + }, + "supplier": { + "type": "string", + "description": "Supplier is supplier information, which can be user-provided for NTIA minimum elements compliance." + }, + "type": { + "type": "string", + "description": "Type is the source type (e.g., \"image\", \"directory\", \"file\")." + }, + "metadata": { + "description": "Metadata contains additional source-specific metadata." + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "metadata" + ], + "description": "Source represents the artifact that was analyzed to generate this SBOM, such as a container image, directory, or file archive." + }, + "SwiftPackageManagerLockEntry": { + "properties": { + "revision": { + "type": "string", + "description": "Revision is git commit hash of the resolved package" + } + }, + "type": "object", + "required": [ + "revision" + ], + "description": "SwiftPackageManagerResolvedEntry represents a resolved dependency from a Package.resolved file with its locked version and source location." + }, + "SwiplpackPackage": { + "properties": { + "name": { + "type": "string", + "description": "Name is the package name as found in the .toml file" + }, + "version": { + "type": "string", + "description": "Version is the package version as found in the .toml file" + }, + "author": { + "type": "string", + "description": "Author is author name" + }, + "authorEmail": { + "type": "string", + "description": "AuthorEmail is author email address" + }, + "packager": { + "type": "string", + "description": "Packager is packager name (if different from author)" + }, + "packagerEmail": { + "type": "string", + "description": "PackagerEmail is packager email address" + }, + "homepage": { + "type": "string", + "description": "Homepage is project homepage URL" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Dependencies are the list of required dependencies" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "packager", + "packagerEmail", + "homepage", + "dependencies" + ], + "description": "SwiplPackEntry represents a SWI-Prolog package from the pack system with metadata about the package and its dependencies." + }, + "TerraformLockProviderEntry": { + "properties": { + "url": { + "type": "string", + "description": "URL is the provider source address (e.g., \"registry.terraform.io/hashicorp/aws\")." + }, + "constraints": { + "type": "string", + "description": "Constraints specifies the version constraints for the provider (e.g., \"~\u003e 4.0\")." + }, + "version": { + "type": "string", + "description": "Version is the locked provider version selected during terraform init." + }, + "hashes": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Hashes are cryptographic checksums for the provider plugin archives across different platforms." + } + }, + "type": "object", + "required": [ + "url", + "constraints", + "version", + "hashes" + ], + "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": { + "properties": { + "pluginInstallDirectory": { + "type": "string", + "description": "PluginInstallDirectory is directory name where the plugin is installed" + }, + "author": { + "type": "string", + "description": "Author is plugin author name" + }, + "authorUri": { + "type": "string", + "description": "AuthorURI is author's website URL" + } + }, + "type": "object", + "required": [ + "pluginInstallDirectory" + ], + "description": "WordpressPluginEntry represents all metadata parsed from the wordpress plugin file" + }, + "cpes": { + "items": { + "$ref": "#/$defs/CPE" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/schema/json/schema-latest.json b/schema/json/schema-latest.json index a4146954e..9e83026ee 100644 --- a/schema/json/schema-latest.json +++ b/schema/json/schema-latest.json @@ -1,6 +1,6 @@ { "$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", "$defs": { "AlpmDbEntry": { @@ -1169,7 +1169,11 @@ }, "osCPE": { "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": { "type": "string", diff --git a/syft/file/location.go b/syft/file/location.go index 910ad5d1e..ad12c8f49 100644 --- a/syft/file/location.go +++ b/syft/file/location.go @@ -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. -func NewLocationFromDirectory(responsePath string, ref file.Reference) Location { +func NewLocationFromDirectory(responsePath string, fd string, ref file.Reference) Location { return Location{ LocationData: LocationData{ Coordinates: Coordinates{ - RealPath: responsePath, + RealPath: responsePath, + FileSystemID: fd, }, AccessPath: responsePath, ref: ref, diff --git a/syft/format/internal/spdxutil/helpers/download_location.go b/syft/format/internal/spdxutil/helpers/download_location.go index 93fd6d69d..e65a6e0b9 100644 --- a/syft/format/internal/spdxutil/helpers/download_location.go +++ b/syft/format/internal/spdxutil/helpers/download_location.go @@ -1,6 +1,7 @@ package helpers import ( + "net/url" "strings" urilib "github.com/spdx/gordf/uri" @@ -49,9 +50,21 @@ func isURIValid(uri string) bool { func URIValue(uri string) string { if strings.ToLower(uri) != "none" { if isURIValid(uri) { - return uri + return updateForGithub(url.Parse(uri)) } return NOASSERTION } 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 +} diff --git a/syft/format/internal/spdxutil/helpers/download_location_test.go b/syft/format/internal/spdxutil/helpers/download_location_test.go index cd69a63ec..9a2b9cd91 100644 --- a/syft/format/internal/spdxutil/helpers/download_location_test.go +++ b/syft/format/internal/spdxutil/helpers/download_location_test.go @@ -640,6 +640,16 @@ func Test_DownloadLocation(t *testing.T) { }, 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 { t.Run(test.name, func(t *testing.T) { diff --git a/syft/internal/fileresolver/chroot_context.go b/syft/internal/fileresolver/chroot_context.go index 3a718635d..88647be3a 100644 --- a/syft/internal/fileresolver/chroot_context.go +++ b/syft/internal/fileresolver/chroot_context.go @@ -14,10 +14,11 @@ import ( // 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). type ChrootContext struct { - root string - base string - cwd string - cwdRelativeToRoot string + root string + rootRelativeToBase string + base string + cwd string + cwdRelativeToRoot string } func NewChrootContextFromCWD(root, base string) (*ChrootContext, error) { @@ -40,10 +41,26 @@ func NewChrootContext(root, base, cwd string) (*ChrootContext, error) { 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{ - root: cleanRoot, - base: cleanBase, - cwd: cwd, + root: cleanRoot, + rootRelativeToBase: rootRelativeToBase, + base: cleanBase, + cwd: cwd, } return chroot, chroot.ChangeDirectory(cwd) @@ -125,8 +142,8 @@ func (r ChrootContext) ToNativePath(chrootPath string) (string, error) { responsePath := chrootPath if filepath.IsAbs(responsePath) { - // don't allow input to potentially hop above root path - responsePath = path.Join(r.root, responsePath) + // don't allow input to potentially hop above root path (and still un-apply any base paths) + responsePath = path.Join(r.root, r.rootRelativeToBase, responsePath) } else { // ensure we take into account any relative difference between the root path and the CWD for relative requests responsePath = path.Join(r.cwdRelativeToRoot, responsePath) diff --git a/syft/internal/fileresolver/chroot_context_test.go b/syft/internal/fileresolver/chroot_context_test.go index 71621efa5..96df93aea 100644 --- a/syft/internal/fileresolver/chroot_context_test.go +++ b/syft/internal/fileresolver/chroot_context_test.go @@ -452,6 +452,25 @@ func Test_ChrootContext_RequestResponse(t *testing.T) { expectedNativePath: absRelOutsidePath, 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 { 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) { tests := []struct { name string diff --git a/syft/internal/fileresolver/filetree_resolver.go b/syft/internal/fileresolver/filetree_resolver.go index 292349c7d..68b3b8395 100644 --- a/syft/internal/fileresolver/filetree_resolver.go +++ b/syft/internal/fileresolver/filetree_resolver.go @@ -190,7 +190,7 @@ func (r *FiletreeResolver) AllLocations(ctx context.Context) <-chan file.Locatio select { case <-ctx.Done(): return - case results <- file.NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), ref): + case results <- file.NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), "", ref): continue } } diff --git a/syft/internal/fileresolver/filetree_resolver_test.go b/syft/internal/fileresolver/filetree_resolver_test.go index d2ad67990..400a696f5 100644 --- a/syft/internal/fileresolver/filetree_resolver_test.go +++ b/syft/internal/fileresolver/filetree_resolver_test.go @@ -984,12 +984,12 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) { }{ { 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", }, { name: "error on empty file reference", - location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), + location: file.NewLocationFromDirectory("doesn't matter", "", stereoscopeFile.Reference{}), err: true, }, } @@ -1525,12 +1525,12 @@ func Test_fileResolver_FileContentsByLocation(t *testing.T) { }{ { 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", }, { name: "error on empty file reference", - location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), + location: file.NewLocationFromDirectory("doesn't matter", "", stereoscopeFile.Reference{}), err: true, }, } diff --git a/syft/internal/fileresolver/unindexed_directory_test.go b/syft/internal/fileresolver/unindexed_directory_test.go index 8945e53b2..254ba75c9 100644 --- a/syft/internal/fileresolver/unindexed_directory_test.go +++ b/syft/internal/fileresolver/unindexed_directory_test.go @@ -950,7 +950,7 @@ func Test_UnindexedDirectoryResolver_FileContentsByLocation(t *testing.T) { }, { name: "error on empty file reference", - location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), + location: file.NewLocationFromDirectory("doesn't matter", "", stereoscopeFile.Reference{}), err: true, }, } diff --git a/syft/pkg/binary.go b/syft/pkg/binary.go index b16c9a4b6..db5aae8a6 100644 --- a/syft/pkg/binary.go +++ b/syft/pkg/binary.go @@ -24,8 +24,13 @@ type ELFBinaryPackageNoteJSONPayload struct { 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) + // + // Deprecated: in Syft 2.0 the struct tag will be corrected to `osCpe` to match the systemd spec casing. 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 string `json:"os,omitempty"` diff --git a/syft/pkg/cataloger/binary/elf_package.go b/syft/pkg/cataloger/binary/elf_package.go index 65f19f500..8cc3a047f 100644 --- a/syft/pkg/cataloger/binary/elf_package.go +++ b/syft/pkg/cataloger/binary/elf_package.go @@ -68,11 +68,15 @@ func osNameAndVersionFromMetadata(metadata elfBinaryPackageNotes) (string, strin return os, osVersion } - if metadata.OSCPE == "" { + if metadata.OSCPE == "" { //nolint:staticcheck + // best-effort to get the os info + if os != "" { + return os, "" + } return "", "" } - attrs, err := cpe.NewAttributes(metadata.OSCPE) + attrs, err := cpe.NewAttributes(metadata.OSCPE) //nolint:staticcheck if err != nil { log.WithFields("error", err).Trace("unable to parse cpe attributes for elf binary package") return "", "" diff --git a/syft/pkg/cataloger/binary/elf_package_cataloger.go b/syft/pkg/cataloger/binary/elf_package_cataloger.go index dd370b527..07cbccbe2 100644 --- a/syft/pkg/cataloger/binary/elf_package_cataloger.go +++ b/syft/pkg/cataloger/binary/elf_package_cataloger.go @@ -34,7 +34,10 @@ type elfBinaryPackageNotes struct { CPE string `json:"cpe"` License string `json:"license"` 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 { @@ -164,9 +167,9 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) { } { - var metadata elfBinaryPackageNotes - if err := json.Unmarshal(notes, &metadata); err == nil { - return &metadata, nil + var metadata *elfBinaryPackageNotes + if metadata, err = unmarshalELFPackageNotesPayload(notes); err == nil { + return metadata, nil } } @@ -174,10 +177,10 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) { var header elf64SectionHeader headerSize := binary.Size(header) / 4 if len(notes) > headerSize { - var metadata elfBinaryPackageNotes + var metadata *elfBinaryPackageNotes newPayload := bytes.TrimRight(notes[headerSize:], "\x00") - if err = json.Unmarshal(newPayload, &metadata); err == nil { - return &metadata, nil + if metadata, err = unmarshalELFPackageNotesPayload(newPayload); err == nil { + return metadata, nil } 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 } +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 { ShName uint32 ShType uint32 diff --git a/syft/pkg/cataloger/binary/elf_package_cataloger_test.go b/syft/pkg/cataloger/binary/elf_package_cataloger_test.go index db19ef15b..7a29d3d44 100644 --- a/syft/pkg/cataloger/binary/elf_package_cataloger_test.go +++ b/syft/pkg/cataloger/binary/elf_package_cataloger_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + extFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "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 { @@ -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) + }) + } +} diff --git a/syft/pkg/cataloger/binary/test-fixtures/image-wolfi-64bit-without-version/Dockerfile b/syft/pkg/cataloger/binary/test-fixtures/image-wolfi-64bit-without-version/Dockerfile new file mode 100644 index 000000000..af4052ee6 --- /dev/null +++ b/syft/pkg/cataloger/binary/test-fixtures/image-wolfi-64bit-without-version/Dockerfile @@ -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"] + diff --git a/syft/pkg/cataloger/dotnet/cataloger_test.go b/syft/pkg/cataloger/dotnet/cataloger_test.go index 1e135318d..8b333bc8b 100644 --- a/syft/pkg/cataloger/dotnet/cataloger_test.go +++ b/syft/pkg/cataloger/dotnet/cataloger_test.go @@ -489,6 +489,40 @@ func TestCataloger(t *testing.T) { "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) { t.Helper() for _, p := range pkgs { @@ -895,12 +929,163 @@ func TestCataloger(t *testing.T) { name: "combined cataloger (single file)", fixture: "image-net8-app-single-file", cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), - - // important: no relationships should be found - expectedPkgs: []string{ - "dotnetapp @ 1.0.0.0 (/app/dotnetapp.exe)", + expectedPkgs: []string{"Humanizer @ 2.14.1 (/app/dotnetapp.exe)", + // extracted libraries from the embedded deps.json + "Humanizer.Core @ 2.14.1 (/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)", diff --git a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go index 475800b6e..b26be3cfd 100644 --- a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go +++ b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go @@ -3,6 +3,7 @@ package dotnet import ( "context" "fmt" + "io" "path" "regexp" "sort" @@ -147,6 +148,18 @@ func isRuntimePackageLocation(loc file.Location) (string, bool) { // partitionPEs pairs PE files with the deps.json based on directory containment. 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.Slice(depJsons, func(i, j int) bool { 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. } } - 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) } } @@ -489,3 +504,14 @@ func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) { 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 +} diff --git a/syft/pkg/cataloger/golang/config.go b/syft/pkg/cataloger/golang/config.go index d173f4a7c..c90767275 100644 --- a/syft/pkg/cataloger/golang/config.go +++ b/syft/pkg/cataloger/golang/config.go @@ -48,6 +48,9 @@ type CatalogerConfig struct { 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"` + + // 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 { @@ -70,6 +73,7 @@ type MainModuleVersionConfig struct { // - setting the default local module cache dir if none is provided func DefaultCatalogerConfig() CatalogerConfig { g := CatalogerConfig{ + UsePackagesLib: true, MainModuleVersion: DefaultMainModuleVersionConfig(), LocalModCacheDir: defaultGoModDir(), } @@ -180,6 +184,11 @@ func (g CatalogerConfig) WithMainModuleVersion(input MainModuleVersionConfig) Ca return g } +func (g CatalogerConfig) WithUsePackagesLib(useLib bool) CatalogerConfig { + g.UsePackagesLib = useLib + return g +} + func (g MainModuleVersionConfig) WithFromLDFlags(input bool) MainModuleVersionConfig { g.FromLDFlags = input return g diff --git a/syft/pkg/cataloger/golang/config_test.go b/syft/pkg/cataloger/golang/config_test.go index e9e0ef4b0..4b563e656 100644 --- a/syft/pkg/cataloger/golang/config_test.go +++ b/syft/pkg/cataloger/golang/config_test.go @@ -57,6 +57,7 @@ func Test_Config(t *testing.T) { Proxies: []string{"https://my.proxy"}, NoProxy: []string{"my.private", "no.proxy"}, MainModuleVersion: DefaultMainModuleVersionConfig(), + UsePackagesLib: true, }, }, { @@ -84,6 +85,7 @@ func Test_Config(t *testing.T) { Proxies: []string{"https://alt.proxy", "direct"}, NoProxy: []string{"alt.no.proxy"}, MainModuleVersion: DefaultMainModuleVersionConfig(), + UsePackagesLib: true, }, }, } diff --git a/syft/pkg/cataloger/golang/package.go b/syft/pkg/cataloger/golang/package.go index f07867e4c..f689c1a40 100644 --- a/syft/pkg/cataloger/golang/package.go +++ b/syft/pkg/cataloger/golang/package.go @@ -64,27 +64,23 @@ func newBinaryMetadata(dep *debug.Module, mainModule, goVersion, architecture st func packageURL(moduleName, moduleVersion string) string { // 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." - - fields := strings.Split(moduleName, "/") - if len(fields) == 0 { + if moduleName == "" { return "" } namespace := "" - name := "" - // The subpath is used to point to a subpath inside a package (e.g. pkg:golang/google.golang.org/genproto#googleapis/api/annotations) - subpath := "" + name := moduleName - switch len(fields) { - case 1: - name = fields[0] - case 2: - name = fields[1] - namespace = fields[0] - default: - name = fields[2] - namespace = strings.Join(fields[0:2], "/") - subpath = strings.Join(fields[3:], "/") + // golang PURLs from _modules_ are constructed as pkg:golang/@version, where + // the full module name often includes multiple segments including `/v#`-type versions, for example: + // pkg:golang/github.com/cli/cli/v2@2.63.0 + // see: https://github.com/package-url/purl-spec/issues/63 + // and: https://github.com/package-url/purl-spec/blob/main/types-doc/golang-definition.md#subpath-definition + // by setting the namespace this way, it does not escape the slashes: + lastSlash := strings.LastIndex(moduleName, "/") + if lastSlash > 0 && lastSlash < len(moduleName)-1 { + name = moduleName[lastSlash+1:] + namespace = moduleName[0:lastSlash] } return packageurl.NewPackageURL( @@ -93,6 +89,6 @@ func packageURL(moduleName, moduleVersion string) string { name, moduleVersion, nil, - subpath, + "", // subpath is used to reference a specific _package_ within the module ).ToString() } diff --git a/syft/pkg/cataloger/golang/package_test.go b/syft/pkg/cataloger/golang/package_test.go index 199a5d07e..114b64d40 100644 --- a/syft/pkg/cataloger/golang/package_test.go +++ b/syft/pkg/cataloger/golang/package_test.go @@ -38,14 +38,14 @@ func Test_packageURL(t *testing.T) { Name: "github.com/coreos/go-systemd/v22", 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", pkg: pkg.Package{ 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", }, } diff --git a/syft/pkg/cataloger/golang/parse_go_binary.go b/syft/pkg/cataloger/golang/parse_go_binary.go index 7a76df875..9cb25337f 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -98,6 +98,24 @@ func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact. 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 moduleFromPartialPackageBuild = debug.Module{Path: "command-line-arguments"} @@ -115,7 +133,9 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Re if dep == nil { continue } - + if moduleEqual(dep, &mod.Main) { + continue + } lics := c.licenseResolver.getLicenses(ctx, resolver, dep.Path, dep.Version) gover, experiments := getExperimentsFromVersion(mod.GoVersion) diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index 3ada19a8a..d1b201c72 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -278,7 +278,7 @@ func TestBuildGoPkgInfo(t *testing.T) { { Name: "github.com/a/b/c", 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, Type: pkg.GoModulePkg, Locations: file.NewLocationSet( @@ -847,6 +847,86 @@ func TestBuildGoPkgInfo(t *testing.T) { 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", cfg: func() *CatalogerConfig { diff --git a/syft/pkg/cataloger/golang/parse_go_mod.go b/syft/pkg/cataloger/golang/parse_go_mod.go index 23d3721fa..bb495cc40 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod.go +++ b/syft/pkg/cataloger/golang/parse_go_mod.go @@ -8,7 +8,6 @@ import ( "io" "path/filepath" "slices" - "sort" "strings" "github.com/spf13/afero" @@ -26,17 +25,19 @@ import ( ) type goModCataloger struct { + usePackagesLib bool licenseResolver goLicenseResolver } func newGoModCataloger(opts CatalogerConfig) *goModCataloger { return &goModCataloger{ + usePackagesLib: opts.UsePackagesLib, licenseResolver: newGoLicenseResolver(modFileCatalogerName, opts), } } // 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)) digests, err := parseGoSumFile(resolver, reader) if err != nil { @@ -48,24 +49,34 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol 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 modFile, err := c.parseModFileContents(reader) if err != nil { 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) c.applyReplaceDirectives(ctx, resolver, modFile, goModPackages, reader, digests) c.applyExcludeDirectives(modFile, goModPackages) - finalPkgs := c.assembleResults(catalogedModules, goModPackages) - return finalPkgs, relationships, unknownErr + pkgs = c.assembleResults(catalogedModules, goModPackages) + + return pkgs, relationships, err } // 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) 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) goModPkg := pkg.Package{ Name: m.Mod.Path, @@ -392,9 +403,7 @@ func (c *goModCataloger) assembleResults(catalogedPkgs []pkg.Package, goModPacka pkgsSlice = append(pkgsSlice, p) } - sort.SliceStable(pkgsSlice, func(i, j int) bool { - return pkgsSlice[i].Name < pkgsSlice[j].Name - }) + pkg.Sort(pkgsSlice) return pkgsSlice } diff --git a/syft/pkg/cataloger/golang/parse_go_mod_test.go b/syft/pkg/cataloger/golang/parse_go_mod_test.go index 77232133f..45789e441 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod_test.go +++ b/syft/pkg/cataloger/golang/parse_go_mod_test.go @@ -54,7 +54,7 @@ func TestParseGoMod(t *testing.T) { { Name: "github.com/anchore/archiver/v3", 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")), Language: pkg.Go, Type: pkg.GoModulePkg, @@ -111,7 +111,7 @@ func TestParseGoMod(t *testing.T) { for _, test := range tests { t.Run(test.fixture, func(t *testing.T) { - c := newGoModCataloger(DefaultCatalogerConfig()) + c := newGoModCataloger(DefaultCatalogerConfig().WithUsePackagesLib(false)) pkgtest.NewCatalogTester(). FromFile(t, test.fixture). Expects(test.expected, nil). @@ -172,7 +172,9 @@ func Test_GoSumHashes(t *testing.T) { pkgtest.NewCatalogTester(). FromDirectory(t, test.fixture). 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 { name string fixturePath string - config CatalogerConfig expectedPkgs []string expectedRels []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) } }). - TestCataloger(t, NewGoModuleFileCataloger(CatalogerConfig{})) + TestCataloger(t, NewGoModuleFileCataloger(DefaultCatalogerConfig().WithUsePackagesLib(true))) }) } } diff --git a/syft/pkg/cataloger/internal/cpegenerate/candidate_by_package_type.go b/syft/pkg/cataloger/internal/cpegenerate/candidate_by_package_type.go index 90f46398e..347bd99b3 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/candidate_by_package_type.go +++ b/syft/pkg/cataloger/internal/cpegenerate/candidate_by_package_type.go @@ -196,6 +196,11 @@ var defaultCandidateAdditions = buildCandidateLookup( candidateAddition{AdditionalVendors: []string{"handlebarsjs"}}, }, // NPM packages + { + pkg.NpmPkg, + candidateKey{PkgName: "next"}, + candidateAddition{AdditionalProducts: []string{"next.js"}, AdditionalVendors: []string{"vercel"}}, + }, { pkg.NpmPkg, candidateKey{PkgName: "hapi"}, diff --git a/syft/pkg/cataloger/internal/pe/bundle.go b/syft/pkg/cataloger/internal/pe/bundle.go new file mode 100644 index 000000000..2fefc0acb --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/bundle.go @@ -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 +} diff --git a/syft/pkg/cataloger/internal/pe/bundle_test.go b/syft/pkg/cataloger/internal/pe/bundle_test.go new file mode 100644 index 000000000..d25b33f7c --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/bundle_test.go @@ -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") + } + }) + } +} diff --git a/syft/pkg/cataloger/internal/pe/pe.go b/syft/pkg/cataloger/internal/pe/pe.go index 395d607b5..d81923ffc 100644 --- a/syft/pkg/cataloger/internal/pe/pe.go +++ b/syft/pkg/cataloger/internal/pe/pe.go @@ -34,6 +34,10 @@ type File struct { // understand if this executable is even a .NET application. 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 map[string]string } @@ -153,7 +157,7 @@ func Read(f file.LocationReadCloser) (*File, error) { return nil, err } - sections, _, err := parsePEFile(r) + sections, sectionHeaders, err := parsePEFile(r) if err != nil { 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) } + embeddedDepsJSON, err := extractDepsJSONFromBundle(r, sectionHeaders) + if err != nil { + return nil, fmt.Errorf("unable to extract embedded deps.json: %w", err) + } + return &File{ Location: f.Location, CLR: c, + EmbeddedDepsJSON: embeddedDepsJSON, VersionResources: versionResources, }, nil } diff --git a/syft/pkg/cataloger/internal/pe/pe_test.go b/syft/pkg/cataloger/internal/pe/pe_test.go index e95d33257..e04c1e00d 100644 --- a/syft/pkg/cataloger/internal/pe/pe_test.go +++ b/syft/pkg/cataloger/internal/pe/pe_test.go @@ -1,6 +1,8 @@ package pe import ( + "fmt" + "os" "testing" "github.com/google/go-cmp/cmp" @@ -14,13 +16,17 @@ import ( ) 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 { - name string - fixture string - path string - wantVR map[string]string - wantCLR bool - wantErr require.ErrorAssertionFunc + name string + fixture string + path string + wantVR map[string]string + wantCLR bool + wantDepsJSON string + wantErr require.ErrorAssertionFunc }{ { name: "newtonsoft", @@ -114,7 +120,8 @@ func Test_Read_DotNetDetection(t *testing.T) { "ProductVersion": "1.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()) + + 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) + } }) } } diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/Makefile b/syft/pkg/cataloger/internal/pe/test-fixtures/Makefile new file mode 100644 index 000000000..44674c2c9 --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/Makefile @@ -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) diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/Dockerfile b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/Dockerfile new file mode 100644 index 000000000..dc7f30763 --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/Dockerfile @@ -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 . diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/src/Program.cs b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/src/Program.cs new file mode 100644 index 000000000..d9a16e17d --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/src/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace Hello +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello"); + } + } +} diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/src/hello.csproj b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/src/hello.csproj new file mode 100644 index 000000000..afa7bad5e --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet31-single-file/src/hello.csproj @@ -0,0 +1,6 @@ + + + Exe + netcoreapp3.1 + + diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/Dockerfile b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/Dockerfile new file mode 100644 index 000000000..71ad584de --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/Dockerfile @@ -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 . diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/src/Program.cs b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/src/Program.cs new file mode 100644 index 000000000..e7b9629b1 --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/src/Program.cs @@ -0,0 +1 @@ +System.Console.WriteLine("Hello"); diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/src/hello.csproj b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/src/hello.csproj new file mode 100644 index 000000000..dbc7a12ef --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet5-single-file/src/hello.csproj @@ -0,0 +1,6 @@ + + + Exe + net5.0 + + diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/Dockerfile b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/Dockerfile new file mode 100644 index 000000000..2089ef9e6 --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/Dockerfile @@ -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 . diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/src/Program.cs b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/src/Program.cs new file mode 100644 index 000000000..e7b9629b1 --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/src/Program.cs @@ -0,0 +1 @@ +System.Console.WriteLine("Hello"); diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/src/hello.csproj b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/src/hello.csproj new file mode 100644 index 000000000..a69c6ed46 --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/image-dotnet6-single-file/src/hello.csproj @@ -0,0 +1,6 @@ + + + Exe + net6.0 + + diff --git a/syft/pkg/cataloger/internal/pe/test-fixtures/net8-app-single-file.deps.json b/syft/pkg/cataloger/internal/pe/test-fixtures/net8-app-single-file.deps.json new file mode 100644 index 000000000..191d24c6a --- /dev/null +++ b/syft/pkg/cataloger/internal/pe/test-fixtures/net8-app-single-file.deps.json @@ -0,0 +1,1622 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0/win-x64", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": {}, + ".NETCoreApp,Version=v8.0/win-x64": { + "dotnetapp/1.0.0": { + "dependencies": { + "Humanizer": "2.14.1", + "Newtonsoft.Json": "13.0.3", + "runtimepack.Microsoft.NETCore.App.Runtime.win-x64": "8.0.14" + }, + "runtime": { + "dotnetapp.dll": {} + } + }, + "runtimepack.Microsoft.NETCore.App.Runtime.win-x64/8.0.14": { + "runtime": { + "Microsoft.CSharp.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "Microsoft.VisualBasic.Core.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.1425.11118" + }, + "Microsoft.VisualBasic.dll": { + "assemblyVersion": "10.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "Microsoft.Win32.Primitives.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "Microsoft.Win32.Registry.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.AppContext.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Buffers.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Collections.Concurrent.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Collections.Immutable.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Collections.NonGeneric.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Collections.Specialized.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Collections.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ComponentModel.Annotations.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ComponentModel.DataAnnotations.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ComponentModel.EventBasedAsync.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ComponentModel.Primitives.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ComponentModel.TypeConverter.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ComponentModel.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Configuration.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Console.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Core.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Data.Common.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Data.DataSetExtensions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Data.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.Contracts.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.Debug.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.DiagnosticSource.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.FileVersionInfo.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.Process.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.StackTrace.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.TextWriterTraceListener.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.Tools.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.TraceSource.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Diagnostics.Tracing.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Drawing.Primitives.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Drawing.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Dynamic.Runtime.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Formats.Asn1.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Formats.Tar.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Globalization.Calendars.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Globalization.Extensions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Globalization.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.Compression.Brotli.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.Compression.FileSystem.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.Compression.ZipFile.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.Compression.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.FileSystem.AccessControl.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.FileSystem.DriveInfo.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.FileSystem.Primitives.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.FileSystem.Watcher.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.FileSystem.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.IsolatedStorage.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.MemoryMappedFiles.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.Pipes.AccessControl.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.Pipes.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.UnmanagedMemoryStream.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.IO.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Linq.Expressions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Linq.Parallel.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Linq.Queryable.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Linq.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Memory.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Http.Json.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Http.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.HttpListener.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Mail.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.NameResolution.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.NetworkInformation.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Ping.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Primitives.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Quic.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Requests.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Security.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.ServicePoint.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.Sockets.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.WebClient.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.WebHeaderCollection.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.WebProxy.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.WebSockets.Client.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.WebSockets.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Net.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Numerics.Vectors.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Numerics.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ObjectModel.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Private.CoreLib.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Private.DataContractSerialization.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Private.Uri.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Private.Xml.Linq.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Private.Xml.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.DispatchProxy.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.Emit.ILGeneration.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.Emit.Lightweight.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.Emit.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.Extensions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.Metadata.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.Primitives.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.TypeExtensions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Reflection.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Resources.Reader.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Resources.ResourceManager.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Resources.Writer.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.CompilerServices.Unsafe.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.CompilerServices.VisualC.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Extensions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Handles.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.InteropServices.JavaScript.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.InteropServices.RuntimeInformation.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.InteropServices.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Intrinsics.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Loader.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Numerics.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Serialization.Formatters.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Serialization.Json.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Serialization.Primitives.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Serialization.Xml.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.Serialization.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Runtime.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.AccessControl.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Claims.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Cryptography.Algorithms.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Cryptography.Cng.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Cryptography.Csp.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Cryptography.Encoding.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Cryptography.OpenSsl.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Cryptography.Primitives.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Cryptography.X509Certificates.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Cryptography.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Principal.Windows.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.Principal.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.SecureString.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Security.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ServiceModel.Web.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ServiceProcess.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Text.Encoding.CodePages.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Text.Encoding.Extensions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Text.Encoding.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Text.Encodings.Web.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Text.Json.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Text.RegularExpressions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.Channels.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.Overlapped.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.Tasks.Dataflow.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.Tasks.Extensions.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.Tasks.Parallel.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.Tasks.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.Thread.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.ThreadPool.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.Timer.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Threading.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Transactions.Local.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Transactions.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.ValueTuple.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Web.HttpUtility.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Web.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Windows.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.Linq.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.ReaderWriter.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.Serialization.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.XDocument.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.XPath.XDocument.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.XPath.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.XmlDocument.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.XmlSerializer.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.Xml.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "System.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "WindowsBase.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "mscorlib.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "8.0.1425.11118" + }, + "netstandard.dll": { + "assemblyVersion": "2.1.0.0", + "fileVersion": "8.0.1425.11118" + } + } + }, + "Humanizer/2.14.1": { + "dependencies": { + "Humanizer.Core.af": "2.14.1", + "Humanizer.Core.ar": "2.14.1", + "Humanizer.Core.az": "2.14.1", + "Humanizer.Core.bg": "2.14.1", + "Humanizer.Core.bn-BD": "2.14.1", + "Humanizer.Core.cs": "2.14.1", + "Humanizer.Core.da": "2.14.1", + "Humanizer.Core.de": "2.14.1", + "Humanizer.Core.el": "2.14.1", + "Humanizer.Core.es": "2.14.1", + "Humanizer.Core.fa": "2.14.1", + "Humanizer.Core.fi-FI": "2.14.1", + "Humanizer.Core.fr": "2.14.1", + "Humanizer.Core.fr-BE": "2.14.1", + "Humanizer.Core.he": "2.14.1", + "Humanizer.Core.hr": "2.14.1", + "Humanizer.Core.hu": "2.14.1", + "Humanizer.Core.hy": "2.14.1", + "Humanizer.Core.id": "2.14.1", + "Humanizer.Core.is": "2.14.1", + "Humanizer.Core.it": "2.14.1", + "Humanizer.Core.ja": "2.14.1", + "Humanizer.Core.ko-KR": "2.14.1", + "Humanizer.Core.ku": "2.14.1", + "Humanizer.Core.lv": "2.14.1", + "Humanizer.Core.ms-MY": "2.14.1", + "Humanizer.Core.mt": "2.14.1", + "Humanizer.Core.nb": "2.14.1", + "Humanizer.Core.nb-NO": "2.14.1", + "Humanizer.Core.nl": "2.14.1", + "Humanizer.Core.pl": "2.14.1", + "Humanizer.Core.pt": "2.14.1", + "Humanizer.Core.ro": "2.14.1", + "Humanizer.Core.ru": "2.14.1", + "Humanizer.Core.sk": "2.14.1", + "Humanizer.Core.sl": "2.14.1", + "Humanizer.Core.sr": "2.14.1", + "Humanizer.Core.sr-Latn": "2.14.1", + "Humanizer.Core.sv": "2.14.1", + "Humanizer.Core.th-TH": "2.14.1", + "Humanizer.Core.tr": "2.14.1", + "Humanizer.Core.uk": "2.14.1", + "Humanizer.Core.uz-Cyrl-UZ": "2.14.1", + "Humanizer.Core.uz-Latn-UZ": "2.14.1", + "Humanizer.Core.vi": "2.14.1", + "Humanizer.Core.zh-CN": "2.14.1", + "Humanizer.Core.zh-Hans": "2.14.1", + "Humanizer.Core.zh-Hant": "2.14.1" + } + }, + "Humanizer.Core/2.14.1": { + "runtime": { + "lib/net6.0/Humanizer.dll": { + "assemblyVersion": "2.14.0.0", + "fileVersion": "2.14.1.48190" + } + } + }, + "Humanizer.Core.af/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/af/Humanizer.resources.dll": { + "locale": "af" + } + } + }, + "Humanizer.Core.ar/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/ar/Humanizer.resources.dll": { + "locale": "ar" + } + } + }, + "Humanizer.Core.az/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/az/Humanizer.resources.dll": { + "locale": "az" + } + } + }, + "Humanizer.Core.bg/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/bg/Humanizer.resources.dll": { + "locale": "bg" + } + } + }, + "Humanizer.Core.bn-BD/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/bn-BD/Humanizer.resources.dll": { + "locale": "bn-BD" + } + } + }, + "Humanizer.Core.cs/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/cs/Humanizer.resources.dll": { + "locale": "cs" + } + } + }, + "Humanizer.Core.da/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/da/Humanizer.resources.dll": { + "locale": "da" + } + } + }, + "Humanizer.Core.de/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/de/Humanizer.resources.dll": { + "locale": "de" + } + } + }, + "Humanizer.Core.el/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/el/Humanizer.resources.dll": { + "locale": "el" + } + } + }, + "Humanizer.Core.es/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/es/Humanizer.resources.dll": { + "locale": "es" + } + } + }, + "Humanizer.Core.fa/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/fa/Humanizer.resources.dll": { + "locale": "fa" + } + } + }, + "Humanizer.Core.fi-FI/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/fi-FI/Humanizer.resources.dll": { + "locale": "fi-FI" + } + } + }, + "Humanizer.Core.fr/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/fr/Humanizer.resources.dll": { + "locale": "fr" + } + } + }, + "Humanizer.Core.fr-BE/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/fr-BE/Humanizer.resources.dll": { + "locale": "fr-BE" + } + } + }, + "Humanizer.Core.he/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/he/Humanizer.resources.dll": { + "locale": "he" + } + } + }, + "Humanizer.Core.hr/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/hr/Humanizer.resources.dll": { + "locale": "hr" + } + } + }, + "Humanizer.Core.hu/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/hu/Humanizer.resources.dll": { + "locale": "hu" + } + } + }, + "Humanizer.Core.hy/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/hy/Humanizer.resources.dll": { + "locale": "hy" + } + } + }, + "Humanizer.Core.id/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/id/Humanizer.resources.dll": { + "locale": "id" + } + } + }, + "Humanizer.Core.is/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/is/Humanizer.resources.dll": { + "locale": "is" + } + } + }, + "Humanizer.Core.it/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/it/Humanizer.resources.dll": { + "locale": "it" + } + } + }, + "Humanizer.Core.ja/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/ja/Humanizer.resources.dll": { + "locale": "ja" + } + } + }, + "Humanizer.Core.ko-KR/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/netstandard2.0/ko-KR/Humanizer.resources.dll": { + "locale": "ko-KR" + } + } + }, + "Humanizer.Core.ku/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/ku/Humanizer.resources.dll": { + "locale": "ku" + } + } + }, + "Humanizer.Core.lv/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/lv/Humanizer.resources.dll": { + "locale": "lv" + } + } + }, + "Humanizer.Core.ms-MY/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/netstandard2.0/ms-MY/Humanizer.resources.dll": { + "locale": "ms-MY" + } + } + }, + "Humanizer.Core.mt/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/netstandard2.0/mt/Humanizer.resources.dll": { + "locale": "mt" + } + } + }, + "Humanizer.Core.nb/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/nb/Humanizer.resources.dll": { + "locale": "nb" + } + } + }, + "Humanizer.Core.nb-NO/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/nb-NO/Humanizer.resources.dll": { + "locale": "nb-NO" + } + } + }, + "Humanizer.Core.nl/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/nl/Humanizer.resources.dll": { + "locale": "nl" + } + } + }, + "Humanizer.Core.pl/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/pl/Humanizer.resources.dll": { + "locale": "pl" + } + } + }, + "Humanizer.Core.pt/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/pt/Humanizer.resources.dll": { + "locale": "pt" + } + } + }, + "Humanizer.Core.ro/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/ro/Humanizer.resources.dll": { + "locale": "ro" + } + } + }, + "Humanizer.Core.ru/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/ru/Humanizer.resources.dll": { + "locale": "ru" + } + } + }, + "Humanizer.Core.sk/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/sk/Humanizer.resources.dll": { + "locale": "sk" + } + } + }, + "Humanizer.Core.sl/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/sl/Humanizer.resources.dll": { + "locale": "sl" + } + } + }, + "Humanizer.Core.sr/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/sr/Humanizer.resources.dll": { + "locale": "sr" + } + } + }, + "Humanizer.Core.sr-Latn/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/sr-Latn/Humanizer.resources.dll": { + "locale": "sr-Latn" + } + } + }, + "Humanizer.Core.sv/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/sv/Humanizer.resources.dll": { + "locale": "sv" + } + } + }, + "Humanizer.Core.th-TH/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/netstandard2.0/th-TH/Humanizer.resources.dll": { + "locale": "th-TH" + } + } + }, + "Humanizer.Core.tr/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/tr/Humanizer.resources.dll": { + "locale": "tr" + } + } + }, + "Humanizer.Core.uk/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/uk/Humanizer.resources.dll": { + "locale": "uk" + } + } + }, + "Humanizer.Core.uz-Cyrl-UZ/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/uz-Cyrl-UZ/Humanizer.resources.dll": { + "locale": "uz-Cyrl-UZ" + } + } + }, + "Humanizer.Core.uz-Latn-UZ/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/uz-Latn-UZ/Humanizer.resources.dll": { + "locale": "uz-Latn-UZ" + } + } + }, + "Humanizer.Core.vi/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/vi/Humanizer.resources.dll": { + "locale": "vi" + } + } + }, + "Humanizer.Core.zh-CN/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/zh-CN/Humanizer.resources.dll": { + "locale": "zh-CN" + } + } + }, + "Humanizer.Core.zh-Hans/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/zh-Hans/Humanizer.resources.dll": { + "locale": "zh-Hans" + } + } + }, + "Humanizer.Core.zh-Hant/2.14.1": { + "dependencies": { + "Humanizer.Core": "2.14.1" + }, + "resources": { + "lib/net6.0/zh-Hant/Humanizer.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Newtonsoft.Json/13.0.3": { + "runtime": { + "lib/net6.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.3.27908" + } + } + } + } + }, + "libraries": { + "dotnetapp/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "runtimepack.Microsoft.NETCore.App.Runtime.win-x64/8.0.14": { + "type": "runtimepack", + "serviceable": false, + "sha512": "" + }, + "Humanizer/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==", + "path": "humanizer/2.14.1", + "hashPath": "humanizer.2.14.1.nupkg.sha512" + }, + "Humanizer.Core/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "path": "humanizer.core/2.14.1", + "hashPath": "humanizer.core.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.af/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==", + "path": "humanizer.core.af/2.14.1", + "hashPath": "humanizer.core.af.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.ar/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==", + "path": "humanizer.core.ar/2.14.1", + "hashPath": "humanizer.core.ar.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.az/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==", + "path": "humanizer.core.az/2.14.1", + "hashPath": "humanizer.core.az.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.bg/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==", + "path": "humanizer.core.bg/2.14.1", + "hashPath": "humanizer.core.bg.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.bn-BD/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==", + "path": "humanizer.core.bn-bd/2.14.1", + "hashPath": "humanizer.core.bn-bd.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.cs/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==", + "path": "humanizer.core.cs/2.14.1", + "hashPath": "humanizer.core.cs.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.da/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==", + "path": "humanizer.core.da/2.14.1", + "hashPath": "humanizer.core.da.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.de/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==", + "path": "humanizer.core.de/2.14.1", + "hashPath": "humanizer.core.de.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.el/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==", + "path": "humanizer.core.el/2.14.1", + "hashPath": "humanizer.core.el.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.es/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==", + "path": "humanizer.core.es/2.14.1", + "hashPath": "humanizer.core.es.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.fa/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==", + "path": "humanizer.core.fa/2.14.1", + "hashPath": "humanizer.core.fa.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.fi-FI/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==", + "path": "humanizer.core.fi-fi/2.14.1", + "hashPath": "humanizer.core.fi-fi.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.fr/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==", + "path": "humanizer.core.fr/2.14.1", + "hashPath": "humanizer.core.fr.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.fr-BE/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==", + "path": "humanizer.core.fr-be/2.14.1", + "hashPath": "humanizer.core.fr-be.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.he/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==", + "path": "humanizer.core.he/2.14.1", + "hashPath": "humanizer.core.he.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.hr/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==", + "path": "humanizer.core.hr/2.14.1", + "hashPath": "humanizer.core.hr.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.hu/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==", + "path": "humanizer.core.hu/2.14.1", + "hashPath": "humanizer.core.hu.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.hy/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==", + "path": "humanizer.core.hy/2.14.1", + "hashPath": "humanizer.core.hy.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.id/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==", + "path": "humanizer.core.id/2.14.1", + "hashPath": "humanizer.core.id.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.is/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==", + "path": "humanizer.core.is/2.14.1", + "hashPath": "humanizer.core.is.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.it/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==", + "path": "humanizer.core.it/2.14.1", + "hashPath": "humanizer.core.it.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.ja/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==", + "path": "humanizer.core.ja/2.14.1", + "hashPath": "humanizer.core.ja.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.ko-KR/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==", + "path": "humanizer.core.ko-kr/2.14.1", + "hashPath": "humanizer.core.ko-kr.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.ku/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==", + "path": "humanizer.core.ku/2.14.1", + "hashPath": "humanizer.core.ku.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.lv/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==", + "path": "humanizer.core.lv/2.14.1", + "hashPath": "humanizer.core.lv.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.ms-MY/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==", + "path": "humanizer.core.ms-my/2.14.1", + "hashPath": "humanizer.core.ms-my.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.mt/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==", + "path": "humanizer.core.mt/2.14.1", + "hashPath": "humanizer.core.mt.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.nb/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==", + "path": "humanizer.core.nb/2.14.1", + "hashPath": "humanizer.core.nb.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.nb-NO/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==", + "path": "humanizer.core.nb-no/2.14.1", + "hashPath": "humanizer.core.nb-no.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.nl/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==", + "path": "humanizer.core.nl/2.14.1", + "hashPath": "humanizer.core.nl.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.pl/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==", + "path": "humanizer.core.pl/2.14.1", + "hashPath": "humanizer.core.pl.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.pt/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==", + "path": "humanizer.core.pt/2.14.1", + "hashPath": "humanizer.core.pt.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.ro/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==", + "path": "humanizer.core.ro/2.14.1", + "hashPath": "humanizer.core.ro.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.ru/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==", + "path": "humanizer.core.ru/2.14.1", + "hashPath": "humanizer.core.ru.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.sk/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==", + "path": "humanizer.core.sk/2.14.1", + "hashPath": "humanizer.core.sk.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.sl/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==", + "path": "humanizer.core.sl/2.14.1", + "hashPath": "humanizer.core.sl.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.sr/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==", + "path": "humanizer.core.sr/2.14.1", + "hashPath": "humanizer.core.sr.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.sr-Latn/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==", + "path": "humanizer.core.sr-latn/2.14.1", + "hashPath": "humanizer.core.sr-latn.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.sv/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==", + "path": "humanizer.core.sv/2.14.1", + "hashPath": "humanizer.core.sv.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.th-TH/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==", + "path": "humanizer.core.th-th/2.14.1", + "hashPath": "humanizer.core.th-th.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.tr/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==", + "path": "humanizer.core.tr/2.14.1", + "hashPath": "humanizer.core.tr.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.uk/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==", + "path": "humanizer.core.uk/2.14.1", + "hashPath": "humanizer.core.uk.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.uz-Cyrl-UZ/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==", + "path": "humanizer.core.uz-cyrl-uz/2.14.1", + "hashPath": "humanizer.core.uz-cyrl-uz.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.uz-Latn-UZ/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==", + "path": "humanizer.core.uz-latn-uz/2.14.1", + "hashPath": "humanizer.core.uz-latn-uz.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.vi/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==", + "path": "humanizer.core.vi/2.14.1", + "hashPath": "humanizer.core.vi.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.zh-CN/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==", + "path": "humanizer.core.zh-cn/2.14.1", + "hashPath": "humanizer.core.zh-cn.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.zh-Hans/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==", + "path": "humanizer.core.zh-hans/2.14.1", + "hashPath": "humanizer.core.zh-hans.2.14.1.nupkg.sha512" + }, + "Humanizer.Core.zh-Hant/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==", + "path": "humanizer.core.zh-hant/2.14.1", + "hashPath": "humanizer.core.zh-hant.2.14.1.nupkg.sha512" + }, + "Newtonsoft.Json/13.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "path": "newtonsoft.json/13.0.3", + "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512" + } + }, + "runtimes": { + "win-x64": [ + "win", + "any", + "base" + ] + } +} \ No newline at end of file diff --git a/syft/pkg/cataloger/java/archive_parser.go b/syft/pkg/cataloger/java/archive_parser.go index d44ec5d3a..7cefa9f47 100644 --- a/syft/pkg/cataloger/java/archive_parser.go +++ b/syft/pkg/cataloger/java/archive_parser.go @@ -184,7 +184,7 @@ func (j *archiveParser) parse(ctx context.Context, parentPkg *pkg.Package) ([]pk relationships = append(relationships, nestedRelationships...) } else { // .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 { slices.Sort(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 } - name, version, lics, parsedPom, err := j.discoverNameVersionLicense(ctx, manifest) - if err != nil { - return nil, err - } + name, version, lics, parsedPom := j.discoverNameVersionLicense(ctx, manifest) var pkgPomProject *pkg.JavaPomProject if parsedPom != nil { pkgPomProject = newPomProject(ctx, j.maven, parsedPom.path, parsedPom.project) @@ -280,7 +277,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package, }, 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 // 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)...) @@ -300,10 +297,7 @@ func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest } if len(lics) == 0 { - fileLicenses, err := j.getLicenseFromFileInArchive(ctx) - if err != nil { - return "", "", nil, parsedPom, err - } + fileLicenses := j.getLicenseFromFileInArchive(ctx) if fileLicenses != nil { 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) } - 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 @@ -562,7 +556,7 @@ func getDigestsFromArchive(ctx context.Context, archivePath string) ([]file.Dige 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 var identified []pkg.License var unidentified []pkg.License @@ -578,7 +572,8 @@ func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) ([]pkg. if len(licenseMatches) > 0 { contents, err := intFile.ContentsFromZip(ctx, j.archivePath, licenseMatches...) 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 { @@ -602,10 +597,10 @@ func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) ([]pkg. } 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) { diff --git a/syft/pkg/cataloger/java/parse_pom_properties.go b/syft/pkg/cataloger/java/parse_pom_properties.go index 084e10807..8ef3676be 100644 --- a/syft/pkg/cataloger/java/parse_pom_properties.go +++ b/syft/pkg/cataloger/java/parse_pom_properties.go @@ -11,7 +11,7 @@ import ( "github.com/anchore/syft/syft/pkg" ) -const pomPropertiesGlob = "*pom.properties" +const pomPropertiesGlob = "**/*pom.properties" func parsePomProperties(path string, reader io.Reader) (*pkg.JavaPomProperties, error) { var props pkg.JavaPomProperties diff --git a/syft/pkg/cataloger/java/parse_pom_xml.go b/syft/pkg/cataloger/java/parse_pom_xml.go index 24fdcde74..572dfed8e 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml.go +++ b/syft/pkg/cataloger/java/parse_pom_xml.go @@ -16,7 +16,7 @@ import ( ) const ( - pomXMLGlob = "*pom.xml" + pomXMLGlob = "**/*pom.xml" pomCatalogerName = "java-pom-cataloger" ) diff --git a/syft/pkg/cataloger/javascript/dependency.go b/syft/pkg/cataloger/javascript/dependency.go index 1946f7ef4..eb5bdbcd2 100644 --- a/syft/pkg/cataloger/javascript/dependency.go +++ b/syft/pkg/cataloger/javascript/dependency.go @@ -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 var fullName = fmt.Sprintf("%s/%s", purl.Namespace, purl.Name) requires = append(requires, fullName) - } else { - fmt.Println("error", err) } requires = append(requires, name) diff --git a/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go b/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go index 5d11d3e52..0cb060a23 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go @@ -6,9 +6,9 @@ import ( "path/filepath" "strings" + "github.com/bmatcuk/doublestar/v4" "github.com/go-viper/mapstructure/v2" - intFile "github.com/anchore/syft/internal/file" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/file" "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 // files). func isEggRegularFile(path string) bool { - return intFile.GlobMatch(eggInfoGlob, path) + return doublestar.MatchUnvalidated(eggInfoGlob, path) } // determineSitePackagesRootPath returns the path of the site packages root, diff --git a/syft/pkg/cataloger/ruby/cataloger.go b/syft/pkg/cataloger/ruby/cataloger.go index 8186278ea..215792050 100644 --- a/syft/pkg/cataloger/ruby/cataloger.go +++ b/syft/pkg/cataloger/ruby/cataloger.go @@ -11,7 +11,7 @@ import ( // NewGemFileLockCataloger returns a new Bundler cataloger object tailored for parsing index-oriented files (e.g. Gemfile.lock). func NewGemFileLockCataloger() pkg.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). diff --git a/syft/pkg/cataloger/ruby/cataloger_test.go b/syft/pkg/cataloger/ruby/cataloger_test.go index 3d79c7a93..92a364ae7 100644 --- a/syft/pkg/cataloger/ruby/cataloger_test.go +++ b/syft/pkg/cataloger/ruby/cataloger_test.go @@ -17,6 +17,7 @@ func Test_GemFileLock_Globs(t *testing.T) { fixture: "test-fixtures/glob-paths", expected: []string{ "src/Gemfile.lock", + "src/Gemfile.next.lock", }, }, } diff --git a/syft/pkg/cataloger/ruby/test-fixtures/glob-paths/src/Gemfile.next.lock b/syft/pkg/cataloger/ruby/test-fixtures/glob-paths/src/Gemfile.next.lock new file mode 100644 index 000000000..5ffba7b57 --- /dev/null +++ b/syft/pkg/cataloger/ruby/test-fixtures/glob-paths/src/Gemfile.next.lock @@ -0,0 +1 @@ +bogus