mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-08 13:50:49 -08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3caf0658c | ||
|
|
01dbb42ae9 | ||
|
|
f186d22bf2 | ||
|
|
093c0ae020 | ||
|
|
6bfda76022 | ||
|
|
53850c8b20 | ||
|
|
221196202c | ||
|
|
a614b693d7 |
@@ -1 +1 @@
|
||||
{".":"0.52.0"}
|
||||
{".":"0.52.1"}
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## [0.52.1](https://github.com/aquasecurity/trivy/compare/v0.52.0...v0.52.1) (2024-06-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **nodejs:** fix infinite loop when package link from `package-lock.json` file is broken [backport: release/v0.52] ([#6888](https://github.com/aquasecurity/trivy/issues/6888)) ([01dbb42](https://github.com/aquasecurity/trivy/commit/01dbb42ae9ecff21d1c71f095a27f47a6ac9adaa))
|
||||
* **nodejs:** fix infinity loops for `pnpm` with cyclic imports ([#6857](https://github.com/aquasecurity/trivy/issues/6857)) ([a614b69](https://github.com/aquasecurity/trivy/commit/a614b693d7b948df7d4ed3516e79573cb8424406))
|
||||
* **python:** compare pkg names from `poetry.lock` and `pyproject.toml` in lowercase [backport: release/v0.52] ([#6878](https://github.com/aquasecurity/trivy/issues/6878)) ([093c0ae](https://github.com/aquasecurity/trivy/commit/093c0ae020548bf6f3d1896d4d55210eb42c7b0e))
|
||||
* **sbom:** don't overwrite `srcEpoch` when decoding SBOM files [backport: release/v0.52] ([#6881](https://github.com/aquasecurity/trivy/issues/6881)) ([f186d22](https://github.com/aquasecurity/trivy/commit/f186d22bf275e872bd664f07131604f6a0216f20))
|
||||
|
||||
## [0.52.0](https://github.com/aquasecurity/trivy/compare/v0.51.1...v0.52.0) (2024-06-03)
|
||||
|
||||
|
||||
|
||||
@@ -263,6 +263,8 @@ $ trivy image ghcr.io/aquasecurity/trivy:0.50.0 --vex trivy.openvex.json
|
||||
VEX documents can indeed be reused across different container images, eliminating the need to issue separate VEX documents for each image.
|
||||
This is particularly useful when there is a common component or library that is used across multiple projects or container images.
|
||||
|
||||
You can see [the appendix](#applying-vex-to-dependency-trees) for more details on how VEX is applied in Trivy.
|
||||
|
||||
### Scan with VEX
|
||||
Provide the VEX when scanning your target.
|
||||
|
||||
@@ -412,6 +414,8 @@ At present, the specified relationship category is not taken into account and al
|
||||
- installed_on
|
||||
- installed_with
|
||||
|
||||
You can see [the appendix](#applying-vex-to-dependency-trees) for more details on how VEX is applied in Trivy.
|
||||
|
||||
### Scan with CSAF VEX
|
||||
Provide the CSAF document when scanning your target.
|
||||
|
||||
@@ -470,6 +474,103 @@ does not match:
|
||||
- `pkg:maven/com.google.guava/guava@24.1.1?classifier=sources`
|
||||
- `classifier` must have the same value.
|
||||
|
||||
### Applying VEX to Dependency Trees
|
||||
|
||||
Trivy internally generates a dependency tree and applies VEX statements to this graph.
|
||||
Let's consider a project with the following dependency tree, where `Module C v2.0.0` is assumed to have a vulnerability CVE-XXXX-YYYY:
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
modRootA(Module Root A v1.0.0)
|
||||
modB(Module B v1.0.0)
|
||||
modC(Module C v2.0.0)
|
||||
|
||||
modRootA-->modB
|
||||
modB-->modC
|
||||
```
|
||||
|
||||
Now, suppose a VEX statement is issued for `Module B` as follows:
|
||||
|
||||
```json
|
||||
"statements": [
|
||||
{
|
||||
"vulnerability": {"name": "CVE-XXXX-YYYY"},
|
||||
"products": [
|
||||
{
|
||||
"@id": "pkg:golang/module-b@1.0.0",
|
||||
"subcomponents": [
|
||||
{ "@id": "pkg:golang/module-c@2.0.0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": "not_affected",
|
||||
"justification": "vulnerable_code_not_in_execute_path"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
It declares that `Module B` is not affected by CVE-XXXX-YYYY on `Module C`.
|
||||
|
||||
!!! note
|
||||
The VEX in this example defines the relationship between `Module B` and `Module C`.
|
||||
However, as Trivy traverses all parents from vulnerable packages, it is also possible to define a VEX for the relationship between a vulnerable package and any parent, such as `Module A` and `Module C`, etc.
|
||||
|
||||
Mapping this VEX onto the dependency tree would look like this:
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
modRootA(Module Root A v1.0.0)
|
||||
|
||||
subgraph "VEX (Not Affected)"
|
||||
modB(Module B v1.0.0)
|
||||
modC(Module C v2.0.0)
|
||||
end
|
||||
|
||||
modRootA-->modB
|
||||
modB-->modC
|
||||
```
|
||||
|
||||
In this case, it's clear that `Module Root A` is also not affected by CVE-XXXX-YYYY, so this vulnerability is suppressed.
|
||||
|
||||
Now, let's consider another project:
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
modRootZ(Module Root Z v1.0.0)
|
||||
modB'(Module B v1.0.0)
|
||||
modC'(Module C v2.0.0)
|
||||
modD'(Module D v3.0.0)
|
||||
|
||||
modRootZ-->modB'
|
||||
modRootZ-->modD'
|
||||
modB'-->modC'
|
||||
modD'-->modC'
|
||||
```
|
||||
|
||||
Assuming the same VEX as before, applying it to this dependency tree would look like:
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
modRootZ(Module Root Z v1.0.0)
|
||||
|
||||
subgraph "VEX (Not Affected)"
|
||||
modB'(Module B v1.0.0)
|
||||
modC'(Module C v2.0.0)
|
||||
end
|
||||
|
||||
modD'(Module D v3.0.0)
|
||||
|
||||
modRootZ-->modB'
|
||||
modRootZ-->modD'
|
||||
modB'-->modC'
|
||||
modD'-->modC'
|
||||
```
|
||||
|
||||
`Module Root Z` depends on `Module C` via multiple paths.
|
||||
While the VEX tells us that `Module B` is not affected by the vulnerability, `Module D` might be.
|
||||
In the absence of a VEX, the default assumption is that it is affected.
|
||||
Taking all of this into account, Trivy determines that `Module Root Z` is affected by this vulnerability.
|
||||
|
||||
|
||||
[csaf]: https://oasis-open.github.io/csaf-documentation/specification.html
|
||||
[openvex]: https://github.com/openvex/spec
|
||||
|
||||
@@ -194,8 +194,16 @@ func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftype
|
||||
// node_modules/func1 -> link to target
|
||||
// see `package-lock_v3_with_workspace.json` to better understanding
|
||||
func (p *Parser) resolveLinks(packages map[string]Package) {
|
||||
links := lo.PickBy(packages, func(_ string, pkg Package) bool {
|
||||
return pkg.Link
|
||||
links := lo.PickBy(packages, func(pkgPath string, pkg Package) bool {
|
||||
if !pkg.Link {
|
||||
return false
|
||||
}
|
||||
if pkg.Resolved == "" {
|
||||
p.logger.Warn("`package-lock.json` contains broken link with empty `resolved` field. This package will be skipped to avoid receiving an empty package", log.String("pkg", pkgPath))
|
||||
delete(packages, pkgPath)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
// Early return
|
||||
if len(links) == 0 {
|
||||
@@ -208,7 +216,9 @@ func (p *Parser) resolveLinks(packages map[string]Package) {
|
||||
}
|
||||
|
||||
workspaces := rootPkg.Workspaces
|
||||
for pkgPath, pkg := range packages {
|
||||
// Changing the map during the map iteration causes unexpected behavior,
|
||||
// so we need to iterate over the cloned `packages` map, but change the original `packages` map.
|
||||
for pkgPath, pkg := range maps.Clone(packages) {
|
||||
for linkPath, link := range links {
|
||||
if !strings.HasPrefix(pkgPath, link.Resolved) {
|
||||
continue
|
||||
|
||||
@@ -53,6 +53,12 @@ func TestParse(t *testing.T) {
|
||||
want: npmV3WithoutRootDepsField,
|
||||
wantDeps: npmV3WithoutRootDepsFieldDeps,
|
||||
},
|
||||
{
|
||||
name: "lock version v3 with broken link",
|
||||
file: "testdata/package-lock_v3_broken_link.json",
|
||||
want: nil,
|
||||
wantDeps: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
24
pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json
vendored
Normal file
24
pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "node_v3_without_direct_deps",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "node_v3_without_direct_deps",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC"
|
||||
},
|
||||
"functions/func1": {
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"debug": "^2.6.9"
|
||||
}
|
||||
},
|
||||
"node_modules/func1": {
|
||||
"resolved": "",
|
||||
"link": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen
|
||||
if dep, ok := lockFile.Importers.Root.DevDependencies[name]; ok && dep.Version == ver {
|
||||
relationship = ftypes.RelationshipDirect
|
||||
}
|
||||
if dep, ok := lockFile.Importers.Root.Dependencies[name]; ok && dep.Version == ver {
|
||||
if dep, ok := lockFile.Importers.Root.Dependencies[name]; ok && p.trimPeerDeps(dep.Version, lockVer) == ver {
|
||||
relationship = ftypes.RelationshipDirect
|
||||
dev = false // mark root direct deps to update `dev` field of their child deps.
|
||||
}
|
||||
@@ -208,10 +208,11 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen
|
||||
}
|
||||
}
|
||||
|
||||
visited := make(map[string]struct{})
|
||||
// Overwrite the `Dev` field for dev deps and their child dependencies.
|
||||
for _, pkg := range resolvedPkgs {
|
||||
if !pkg.Dev {
|
||||
p.markRootPkgs(pkg.ID, resolvedPkgs, resolvedDeps)
|
||||
p.markRootPkgs(pkg.ID, resolvedPkgs, resolvedDeps, visited)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +220,10 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen
|
||||
}
|
||||
|
||||
// markRootPkgs sets `Dev` to false for non dev dependency.
|
||||
func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps map[string]ftypes.Dependency) {
|
||||
func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps map[string]ftypes.Dependency, visited map[string]struct{}) {
|
||||
if _, ok := visited[id]; ok {
|
||||
return
|
||||
}
|
||||
pkg, ok := pkgs[id]
|
||||
if !ok {
|
||||
return
|
||||
@@ -227,10 +231,11 @@ func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps ma
|
||||
|
||||
pkg.Dev = false
|
||||
pkgs[id] = pkg
|
||||
visited[id] = struct{}{}
|
||||
|
||||
// Update child deps
|
||||
for _, depID := range deps[id].DependsOn {
|
||||
p.markRootPkgs(depID, pkgs, deps)
|
||||
p.markRootPkgs(depID, pkgs, deps, visited)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -59,6 +59,18 @@ func TestParse(t *testing.T) {
|
||||
want: pnpmV9,
|
||||
wantDeps: pnpmV9Deps,
|
||||
},
|
||||
{
|
||||
name: "v9",
|
||||
file: "testdata/pnpm-lock_v9.yaml",
|
||||
want: pnpmV9,
|
||||
wantDeps: pnpmV9Deps,
|
||||
},
|
||||
{
|
||||
name: "v9 with cyclic dependencies import",
|
||||
file: "testdata/pnpm-lock_v9_cyclic_import.yaml",
|
||||
want: pnpmV9CyclicImport,
|
||||
wantDeps: pnpmV9CyclicImportDeps,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -900,4 +900,68 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pnpmV9CyclicImport = []ftypes.Package{
|
||||
{
|
||||
ID: "update-browserslist-db@1.0.16",
|
||||
Name: "update-browserslist-db",
|
||||
Version: "1.0.16",
|
||||
Relationship: ftypes.RelationshipDirect,
|
||||
},
|
||||
{
|
||||
ID: "browserslist@4.23.0",
|
||||
Name: "browserslist",
|
||||
Version: "4.23.0",
|
||||
Relationship: ftypes.RelationshipIndirect,
|
||||
},
|
||||
{
|
||||
ID: "caniuse-lite@1.0.30001627",
|
||||
Name: "caniuse-lite",
|
||||
Version: "1.0.30001627",
|
||||
Relationship: ftypes.RelationshipIndirect,
|
||||
},
|
||||
{
|
||||
ID: "electron-to-chromium@1.4.789",
|
||||
Name: "electron-to-chromium",
|
||||
Version: "1.4.789",
|
||||
Relationship: ftypes.RelationshipIndirect,
|
||||
},
|
||||
{
|
||||
ID: "escalade@3.1.2",
|
||||
Name: "escalade",
|
||||
Version: "3.1.2",
|
||||
Relationship: ftypes.RelationshipIndirect,
|
||||
},
|
||||
{
|
||||
ID: "node-releases@2.0.14",
|
||||
Name: "node-releases",
|
||||
Version: "2.0.14",
|
||||
Relationship: ftypes.RelationshipIndirect,
|
||||
},
|
||||
{
|
||||
ID: "picocolors@1.0.1",
|
||||
Name: "picocolors",
|
||||
Version: "1.0.1",
|
||||
Relationship: ftypes.RelationshipIndirect,
|
||||
},
|
||||
}
|
||||
pnpmV9CyclicImportDeps = []ftypes.Dependency{
|
||||
{
|
||||
ID: "browserslist@4.23.0",
|
||||
DependsOn: []string{
|
||||
"caniuse-lite@1.0.30001627",
|
||||
"electron-to-chromium@1.4.789",
|
||||
"node-releases@2.0.14",
|
||||
"update-browserslist-db@1.0.16",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "update-browserslist-db@1.0.16",
|
||||
DependsOn: []string{
|
||||
"browserslist@4.23.0",
|
||||
"escalade@3.1.2",
|
||||
"picocolors@1.0.1",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
67
pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9_cyclic_import.yaml
vendored
Normal file
67
pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9_cyclic_import.yaml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
update-browserslist-db:
|
||||
specifier: 1.0.16
|
||||
version: 1.0.16(browserslist@4.23.0)
|
||||
|
||||
packages:
|
||||
|
||||
browserslist@4.23.0:
|
||||
resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
caniuse-lite@1.0.30001627:
|
||||
resolution: {integrity: sha512-4zgNiB8nTyV/tHhwZrFs88ryjls/lHiqFhrxCW4qSTeuRByBVnPYpDInchOIySWknznucaf31Z4KYqjfbrecVw==}
|
||||
|
||||
electron-to-chromium@1.4.789:
|
||||
resolution: {integrity: sha512-0VbyiaXoT++Fi2vHGo2ThOeS6X3vgRCWrjPeO2FeIAWL6ItiSJ9BqlH8LfCXe3X1IdcG+S0iLoNaxQWhfZoGzQ==}
|
||||
|
||||
escalade@3.1.2:
|
||||
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
node-releases@2.0.14:
|
||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||
|
||||
picocolors@1.0.1:
|
||||
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
|
||||
|
||||
update-browserslist-db@1.0.16:
|
||||
resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
snapshots:
|
||||
|
||||
browserslist@4.23.0:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001627
|
||||
electron-to-chromium: 1.4.789
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.16(browserslist@4.23.0)
|
||||
|
||||
caniuse-lite@1.0.30001627: {}
|
||||
|
||||
electron-to-chromium@1.4.789: {}
|
||||
|
||||
escalade@3.1.2: {}
|
||||
|
||||
node-releases@2.0.14: {}
|
||||
|
||||
picocolors@1.0.1: {}
|
||||
|
||||
update-browserslist-db@1.0.16(browserslist@4.23.0):
|
||||
dependencies:
|
||||
browserslist: 4.23.0
|
||||
escalade: 3.1.2
|
||||
picocolors: 1.0.1
|
||||
@@ -105,7 +105,7 @@ func (p *Parser) parseDependencies(deps map[string]any, pkgVersions map[string][
|
||||
}
|
||||
|
||||
func (p *Parser) parseDependency(name string, versRange any, pkgVersions map[string][]string) (string, error) {
|
||||
name = normalizePkgName(name)
|
||||
name = NormalizePkgName(name)
|
||||
vers, ok := pkgVersions[name]
|
||||
if !ok {
|
||||
return "", xerrors.Errorf("no version found for %q", name)
|
||||
@@ -149,9 +149,11 @@ func matchVersion(currentVersion, constraint string) (bool, error) {
|
||||
return c.Check(v), nil
|
||||
}
|
||||
|
||||
func normalizePkgName(name string) string {
|
||||
// NormalizePkgName normalizes the package name based on pep-0426
|
||||
func NormalizePkgName(name string) string {
|
||||
// The package names don't use `_`, `.` or upper case, but dependency names can contain them.
|
||||
// We need to normalize those names.
|
||||
// cf. https://peps.python.org/pep-0426/#name
|
||||
name = strings.ToLower(name) // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L819
|
||||
name = strings.ReplaceAll(name, "_", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L50
|
||||
name = strings.ReplaceAll(name, ".", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L816
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/dependency/parser/python/poetry"
|
||||
@@ -102,8 +103,8 @@ func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Applic
|
||||
return xerrors.Errorf("unable to parse %s: %w", path, err)
|
||||
}
|
||||
|
||||
// Identify the direct/transitive dependencies
|
||||
for i, pkg := range app.Packages {
|
||||
// Identify the direct/transitive dependencies
|
||||
if _, ok := p[pkg.Name]; ok {
|
||||
app.Packages[i].Relationship = types.RelationshipDirect
|
||||
} else {
|
||||
@@ -127,5 +128,11 @@ func (a poetryAnalyzer) parsePyProject(fsys fs.FS, path string) (map[string]any,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Packages from `pyproject.toml` can use uppercase characters, `.` and `_`.
|
||||
parsed = lo.MapKeys(parsed, func(_ any, pkgName string) string {
|
||||
return poetry.NormalizePkgName(pkgName)
|
||||
})
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ authors = ["Trivy"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
flask = "^1.0"
|
||||
Flask = "^1.0"
|
||||
requests = {version = "2.28.1", optional = true}
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
@@ -271,6 +271,11 @@ func (m *Decoder) fillSrcPkg(c *core.Component, pkg *ftypes.Package) {
|
||||
}
|
||||
m.parseSrcVersion(pkg, c.SrcVersion)
|
||||
|
||||
// Source info was added from component or properties
|
||||
if pkg.SrcName != "" && pkg.SrcVersion != "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Fill source package information for components in third-party SBOMs .
|
||||
if pkg.SrcName == "" {
|
||||
pkg.SrcName = pkg.Name
|
||||
|
||||
Reference in New Issue
Block a user