mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 23:00:42 -08:00
feat(report): GitHub Dependency Snapshots support (#1522)
Co-authored-by: Shira Cohen <97398476+ShiraCohen33@users.noreply.github.com> Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
|||||||
testcontainers "github.com/testcontainers/testcontainers-go"
|
testcontainers "github.com/testcontainers/testcontainers-go"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/clock"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands"
|
"github.com/aquasecurity/trivy/pkg/commands"
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
)
|
)
|
||||||
@@ -256,7 +257,7 @@ func TestClientServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientServerWithTemplate(t *testing.T) {
|
func TestClientServerWithFormat(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args csArgs
|
args csArgs
|
||||||
@@ -306,17 +307,35 @@ func TestClientServerWithTemplate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
golden: "testdata/alpine-310.html.golden",
|
golden: "testdata/alpine-310.html.golden",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "alpine 3.10 with github dependency snapshots format",
|
||||||
|
args: csArgs{
|
||||||
|
Format: "github",
|
||||||
|
Input: "testdata/fixtures/images/alpine-310.tar.gz",
|
||||||
|
},
|
||||||
|
golden: "testdata/alpine-310.gsbom.golden",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fakeTime := time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC)
|
||||||
|
clock.SetFakeTime(t, fakeTime)
|
||||||
|
|
||||||
report.CustomTemplateFuncMap = map[string]interface{}{
|
report.CustomTemplateFuncMap = map[string]interface{}{
|
||||||
"now": func() time.Time {
|
"now": func() time.Time {
|
||||||
return time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC)
|
return fakeTime
|
||||||
},
|
},
|
||||||
"date": func(format string, t time.Time) string {
|
"date": func(format string, t time.Time) string {
|
||||||
return t.Format(format)
|
return t.Format(format)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For GitHub Dependency Snapshots
|
||||||
|
t.Setenv("GITHUB_REF", "/ref/feature-1")
|
||||||
|
t.Setenv("GITHUB_SHA", "39da54a1ff04120a31df8cbc94ce9ede251d21a3")
|
||||||
|
t.Setenv("GITHUB_JOB", "integration")
|
||||||
|
t.Setenv("GITHUB_RUN_ID", "1910764383")
|
||||||
|
t.Setenv("GITHUB_WORKFLOW", "workflow-name")
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
report.CustomTemplateFuncMap = map[string]interface{}{}
|
report.CustomTemplateFuncMap = map[string]interface{}{}
|
||||||
})
|
})
|
||||||
|
|||||||
91
integration/testdata/alpine-310.gsbom.golden
vendored
Normal file
91
integration/testdata/alpine-310.gsbom.golden
vendored
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"detector": {
|
||||||
|
"name": "trivy",
|
||||||
|
"version": "dev",
|
||||||
|
"url": "https://github.com/aquasecurity/trivy"
|
||||||
|
},
|
||||||
|
"ref": "/ref/feature-1",
|
||||||
|
"sha": "39da54a1ff04120a31df8cbc94ce9ede251d21a3",
|
||||||
|
"job": {
|
||||||
|
"correlator": "workflow-name_integration",
|
||||||
|
"id": "1910764383"
|
||||||
|
},
|
||||||
|
"scanned": "2020-08-10T07:28:17Z",
|
||||||
|
"manifests": {
|
||||||
|
"testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)": {
|
||||||
|
"name": "alpine",
|
||||||
|
"resolved": {
|
||||||
|
"alpine-baselayout": {
|
||||||
|
"package_url": "pkg:apk/alpine-baselayout@3.1.2-r0",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"alpine-keys": {
|
||||||
|
"package_url": "pkg:apk/alpine-keys@2.1-r2",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"apk-tools": {
|
||||||
|
"package_url": "pkg:apk/apk-tools@2.10.4-r2",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"busybox": {
|
||||||
|
"package_url": "pkg:apk/busybox@1.30.1-r2",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"ca-certificates-cacert": {
|
||||||
|
"package_url": "pkg:apk/ca-certificates-cacert@20190108-r0",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"libc-utils": {
|
||||||
|
"package_url": "pkg:apk/libc-utils@0.7.1-r0",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"libcrypto1.1": {
|
||||||
|
"package_url": "pkg:apk/libcrypto1.1@1.1.1c-r0",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"libssl1.1": {
|
||||||
|
"package_url": "pkg:apk/libssl1.1@1.1.1c-r0",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"libtls-standalone": {
|
||||||
|
"package_url": "pkg:apk/libtls-standalone@2.9.1-r0",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"musl": {
|
||||||
|
"package_url": "pkg:apk/musl@1.1.22-r3",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"musl-utils": {
|
||||||
|
"package_url": "pkg:apk/musl-utils@1.1.22-r3",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"scanelf": {
|
||||||
|
"package_url": "pkg:apk/scanelf@1.2.3-r0",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"ssl_client": {
|
||||||
|
"package_url": "pkg:apk/ssl_client@1.30.1-r2",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
},
|
||||||
|
"zlib": {
|
||||||
|
"package_url": "pkg:apk/zlib@1.2.11-r1",
|
||||||
|
"relationship": "direct",
|
||||||
|
"scope": "runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
pkg/clock/clock.go
Normal file
23
pkg/clock/clock.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package clock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/utils/clock"
|
||||||
|
clocktesting "k8s.io/utils/clock/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var c clock.Clock = clock.RealClock{}
|
||||||
|
|
||||||
|
// SetFakeTime sets a fake time for testing.
|
||||||
|
func SetFakeTime(t *testing.T, fakeTime time.Time) {
|
||||||
|
c = clocktesting.NewFakeClock(fakeTime)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
c = clock.RealClock{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Now() time.Time {
|
||||||
|
return c.Now()
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/aquasecurity/trivy/pkg/commands/server"
|
"github.com/aquasecurity/trivy/pkg/commands/server"
|
||||||
"github.com/aquasecurity/trivy/pkg/k8s"
|
"github.com/aquasecurity/trivy/pkg/k8s"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/result"
|
"github.com/aquasecurity/trivy/pkg/result"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/utils"
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
@@ -41,8 +42,8 @@ var (
|
|||||||
formatFlag = cli.StringFlag{
|
formatFlag = cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
Aliases: []string{"f"},
|
Aliases: []string{"f"},
|
||||||
Value: "table",
|
Value: report.FormatTable,
|
||||||
Usage: "format (table, json, sarif, template)",
|
Usage: "format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github)",
|
||||||
EnvVars: []string{"TRIVY_FORMAT"},
|
EnvVars: []string{"TRIVY_FORMAT"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,8 +907,8 @@ func NewSbomCommand() *cli.Command {
|
|||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "sbom-format",
|
Name: "sbom-format",
|
||||||
Aliases: []string{"format"},
|
Aliases: []string{"format"},
|
||||||
Value: "cyclonedx",
|
Value: report.FormatCycloneDX,
|
||||||
Usage: "SBOM format (cyclonedx, spdx, spdx-json)",
|
Usage: "SBOM format (cyclonedx, spdx, spdx-json, github)",
|
||||||
EnvVars: []string{"TRIVY_SBOM_FORMAT"},
|
EnvVars: []string{"TRIVY_SBOM_FORMAT"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
"github.com/aquasecurity/trivy/pkg/commands/option"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -208,6 +209,23 @@ func TestOption_Init(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "json and list all packages",
|
||||||
|
args: []string{"--format", "json", "--list-all-pkgs", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
||||||
|
want: Option{
|
||||||
|
ReportOption: option.ReportOption{
|
||||||
|
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||||
|
Output: os.Stdout,
|
||||||
|
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||||
|
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||||
|
Format: "json",
|
||||||
|
ListAllPkgs: true,
|
||||||
|
},
|
||||||
|
ArtifactOption: option.ArtifactOption{
|
||||||
|
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "invalid option combination: --format template without --template",
|
name: "invalid option combination: --format template without --template",
|
||||||
args: []string{"--format", "template", "--severity", "MEDIUM", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
args: []string{"--format", "template", "--severity", "MEDIUM", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
||||||
@@ -227,6 +245,24 @@ func TestOption_Init(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "github enables list-all-pkgs",
|
||||||
|
args: []string{"--format", "github", "alpine:3.15"},
|
||||||
|
want: Option{
|
||||||
|
ReportOption: option.ReportOption{
|
||||||
|
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||||
|
Output: os.Stdout,
|
||||||
|
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||||
|
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||||
|
Format: report.FormatGitHub,
|
||||||
|
ListAllPkgs: true,
|
||||||
|
},
|
||||||
|
ArtifactOption: option.ArtifactOption{
|
||||||
|
Target: "alpine:3.15",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "sad: skip and download db",
|
name: "sad: skip and download db",
|
||||||
args: []string{"--skip-db-update", "--download-db-only", "alpine:3.10"},
|
args: []string{"--skip-db-update", "--download-db-only", "alpine:3.10"},
|
||||||
@@ -253,6 +289,7 @@ func TestOption_Init(t *testing.T) {
|
|||||||
set.Bool("reset", false, "")
|
set.Bool("reset", false, "")
|
||||||
set.Bool("skip-db-update", false, "")
|
set.Bool("skip-db-update", false, "")
|
||||||
set.Bool("download-db-only", false, "")
|
set.Bool("download-db-only", false, "")
|
||||||
|
set.Bool("list-all-pkgs", false, "")
|
||||||
set.String("severity", "CRITICAL", "")
|
set.String("severity", "CRITICAL", "")
|
||||||
set.String("vuln-type", "os,library", "")
|
set.String("vuln-type", "os,library", "")
|
||||||
set.String("security-checks", "vuln", "")
|
set.String("security-checks", "vuln", "")
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func (c *ReportOption) populateSecurityChecks() error {
|
|||||||
|
|
||||||
func (c *ReportOption) forceListAllPkgs(logger *zap.SugaredLogger) bool {
|
func (c *ReportOption) forceListAllPkgs(logger *zap.SugaredLogger) bool {
|
||||||
if slices.Contains(supportedSbomFormats, c.Format) && !c.ListAllPkgs {
|
if slices.Contains(supportedSbomFormats, c.Format) && !c.ListAllPkgs {
|
||||||
logger.Debugf("'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.")
|
logger.Debugf("'github', 'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"go.uber.org/zap/zaptest/observer"
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ func TestReportReportConfig_Init(t *testing.T) {
|
|||||||
severities: "CRITICAL",
|
severities: "CRITICAL",
|
||||||
vulnType: "os,library",
|
vulnType: "os,library",
|
||||||
securityChecks: "vuln",
|
securityChecks: "vuln",
|
||||||
Format: "cyclonedx",
|
Format: report.FormatCycloneDX,
|
||||||
listAllPksgs: true,
|
listAllPksgs: true,
|
||||||
},
|
},
|
||||||
args: []string{"centos:7"},
|
args: []string{"centos:7"},
|
||||||
@@ -86,7 +87,7 @@ func TestReportReportConfig_Init(t *testing.T) {
|
|||||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||||
Format: "cyclonedx",
|
Format: report.FormatCycloneDX,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
ListAllPkgs: true,
|
ListAllPkgs: true,
|
||||||
},
|
},
|
||||||
@@ -103,7 +104,7 @@ func TestReportReportConfig_Init(t *testing.T) {
|
|||||||
},
|
},
|
||||||
args: []string{"centos:7"},
|
args: []string{"centos:7"},
|
||||||
logs: []string{
|
logs: []string{
|
||||||
"'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.",
|
"'github', 'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.",
|
||||||
"Severities: CRITICAL",
|
"Severities: CRITICAL",
|
||||||
},
|
},
|
||||||
want: ReportOption{
|
want: ReportOption{
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var supportedSbomFormats = []string{"cyclonedx", "spdx", "spdx-json"}
|
var supportedSbomFormats = []string{report.FormatCycloneDX, report.FormatSPDX, report.FormatSPDXJSON, report.FormatGitHub}
|
||||||
|
|
||||||
// SbomOption holds the options for SBOM generation
|
// SbomOption holds the options for SBOM generation
|
||||||
type SbomOption struct {
|
type SbomOption struct {
|
||||||
|
|||||||
@@ -60,10 +60,14 @@ func NewPackageURL(t string, metadata types.Metadata, pkg ftypes.Package) (Packa
|
|||||||
qualifiers = append(qualifiers, qs...)
|
qualifiers = append(qualifiers, qs...)
|
||||||
case packageurl.TypeDebian:
|
case packageurl.TypeDebian:
|
||||||
qualifiers = append(qualifiers, parseDeb(metadata.OS)...)
|
qualifiers = append(qualifiers, parseDeb(metadata.OS)...)
|
||||||
namespace = metadata.OS.Family
|
if metadata.OS != nil {
|
||||||
|
namespace = metadata.OS.Family
|
||||||
|
}
|
||||||
case string(analyzer.TypeApk): // TODO: replace with packageurl.TypeApk once they add it.
|
case string(analyzer.TypeApk): // TODO: replace with packageurl.TypeApk once they add it.
|
||||||
qualifiers = append(qualifiers, parseApk(metadata.OS)...)
|
qualifiers = append(qualifiers, parseApk(metadata.OS)...)
|
||||||
namespace = metadata.OS.Family
|
if metadata.OS != nil {
|
||||||
|
namespace = metadata.OS.Family
|
||||||
|
}
|
||||||
case packageurl.TypeMaven:
|
case packageurl.TypeMaven:
|
||||||
namespace, name = parseMaven(name)
|
namespace, name = parseMaven(name)
|
||||||
case packageurl.TypePyPi:
|
case packageurl.TypePyPi:
|
||||||
@@ -119,6 +123,10 @@ func parseOCI(metadata types.Metadata) (packageurl.PackageURL, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseApk(fos *ftypes.OS) packageurl.Qualifiers {
|
func parseApk(fos *ftypes.OS) packageurl.Qualifiers {
|
||||||
|
if fos == nil {
|
||||||
|
return packageurl.Qualifiers{}
|
||||||
|
}
|
||||||
|
|
||||||
return packageurl.Qualifiers{
|
return packageurl.Qualifiers{
|
||||||
{
|
{
|
||||||
Key: "distro",
|
Key: "distro",
|
||||||
@@ -129,6 +137,11 @@ func parseApk(fos *ftypes.OS) packageurl.Qualifiers {
|
|||||||
|
|
||||||
// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#deb
|
// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#deb
|
||||||
func parseDeb(fos *ftypes.OS) packageurl.Qualifiers {
|
func parseDeb(fos *ftypes.OS) packageurl.Qualifiers {
|
||||||
|
|
||||||
|
if fos == nil {
|
||||||
|
return packageurl.Qualifiers{}
|
||||||
|
}
|
||||||
|
|
||||||
distro := fmt.Sprintf("%s-%s", fos.Family, fos.Name)
|
distro := fmt.Sprintf("%s-%s", fos.Family, fos.Name)
|
||||||
return packageurl.Qualifiers{
|
return packageurl.Qualifiers{
|
||||||
{
|
{
|
||||||
@@ -140,6 +153,10 @@ func parseDeb(fos *ftypes.OS) packageurl.Qualifiers {
|
|||||||
|
|
||||||
// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#rpm
|
// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#rpm
|
||||||
func parseRPM(fos *ftypes.OS, modularityLabel string) (string, packageurl.Qualifiers) {
|
func parseRPM(fos *ftypes.OS, modularityLabel string) (string, packageurl.Qualifiers) {
|
||||||
|
if fos == nil {
|
||||||
|
return "", packageurl.Qualifiers{}
|
||||||
|
}
|
||||||
|
|
||||||
// SLES string has whitespace
|
// SLES string has whitespace
|
||||||
family := fos.Family
|
family := fos.Family
|
||||||
if fos.Family == os.SLES {
|
if fos.Family == os.SLES {
|
||||||
|
|||||||
178
pkg/report/github/github.go
Normal file
178
pkg/report/github/github.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
ftypes "github.com/aquasecurity/fanal/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/clock"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/purl"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DirectRelationship string = "direct"
|
||||||
|
IndirectRelationship string = "indirect"
|
||||||
|
RuntimeScope string = "runtime"
|
||||||
|
DevelopmentScope string = "development"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Package struct {
|
||||||
|
PackageUrl string `json:"package_url,omitempty"`
|
||||||
|
Relationship string `json:"relationship,omitempty"`
|
||||||
|
Dependencies []string `json:"dependencies,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
Metadata Metadata `json:"metadata,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
SrcLocation string `json:"source_location,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Metadata map[string]interface{}
|
||||||
|
|
||||||
|
type Manifest struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
File *File `json:"file,omitempty"`
|
||||||
|
Metadata Metadata `json:"metadata,omitempty"`
|
||||||
|
Resolved map[string]Package `json:"resolved,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
Correlator string `json:"correlator,omitempty"`
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
|
}
|
||||||
|
type Detector struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DependencySnapshot struct {
|
||||||
|
Version int `json:"version,omitempty"`
|
||||||
|
Detector Detector `json:"detector"`
|
||||||
|
Metadata Metadata `json:"metadata,omitempty"`
|
||||||
|
Ref string `json:"ref,omitempty"`
|
||||||
|
Sha string `json:"sha,omitempty"`
|
||||||
|
Job *Job `json:"job,omitempty"`
|
||||||
|
Scanned string `json:"scanned,omitempty"`
|
||||||
|
Manifests map[string]Manifest `json:"manifests,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer generates JSON for GitHub Dependency Snapshots
|
||||||
|
type Writer struct {
|
||||||
|
Output io.Writer
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w Writer) Write(report types.Report) error {
|
||||||
|
snapshot := &DependencySnapshot{}
|
||||||
|
|
||||||
|
//use now() method that can be overwritten while integration tests run
|
||||||
|
snapshot.Scanned = clock.Now().Format(time.RFC3339)
|
||||||
|
snapshot.Detector = Detector{
|
||||||
|
Name: "trivy",
|
||||||
|
Version: w.Version,
|
||||||
|
Url: "https://github.com/aquasecurity/trivy",
|
||||||
|
}
|
||||||
|
snapshot.Version = 0 // The version of the repository snapshot submission.
|
||||||
|
|
||||||
|
snapshot.Ref = os.Getenv("GITHUB_REF")
|
||||||
|
snapshot.Sha = os.Getenv("GITHUB_SHA")
|
||||||
|
|
||||||
|
snapshot.Job = &Job{
|
||||||
|
Correlator: fmt.Sprintf("%s_%s", os.Getenv("GITHUB_WORKFLOW"), os.Getenv("GITHUB_JOB")),
|
||||||
|
Id: os.Getenv("GITHUB_RUN_ID"),
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.Metadata = getMetadata(report)
|
||||||
|
|
||||||
|
manifests := make(map[string]Manifest)
|
||||||
|
|
||||||
|
for _, result := range report.Results {
|
||||||
|
if result.Packages == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest := Manifest{}
|
||||||
|
manifest.Name = result.Type
|
||||||
|
//show path for languages only
|
||||||
|
if result.Class == types.ClassLangPkg {
|
||||||
|
manifest.File = &File{
|
||||||
|
SrcLocation: result.Target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved := make(map[string]Package)
|
||||||
|
|
||||||
|
for _, pkg := range result.Packages {
|
||||||
|
var err error
|
||||||
|
githubPkg := Package{}
|
||||||
|
githubPkg.Scope = RuntimeScope
|
||||||
|
githubPkg.Relationship = getPkgRelationshipType(pkg)
|
||||||
|
githubPkg.Dependencies = getDependencies(result, pkg)
|
||||||
|
githubPkg.PackageUrl, err = buildPurl(result.Type, pkg)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("unable to build purl for %s: %w", pkg.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved[pkg.Name] = githubPkg
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.Resolved = resolved
|
||||||
|
manifests[result.Target] = manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.Manifests = manifests
|
||||||
|
|
||||||
|
output, err := json.MarshalIndent(snapshot, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("failed to marshal github dependency snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = fmt.Fprint(w.Output, string(output)); err != nil {
|
||||||
|
return xerrors.Errorf("failed to write github dependency snapshots: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetadata(report types.Report) Metadata {
|
||||||
|
metadata := Metadata{}
|
||||||
|
if report.Metadata.RepoTags != nil {
|
||||||
|
metadata["aquasecurity:trivy:RepoTag"] = strings.Join(report.Metadata.RepoTags, ", ")
|
||||||
|
}
|
||||||
|
if report.Metadata.RepoDigests != nil {
|
||||||
|
metadata["aquasecurity:trivy:RepoDigest"] = strings.Join(report.Metadata.RepoDigests, ", ")
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDependencies(result types.Result, pkg ftypes.Package) []string {
|
||||||
|
for _, dep := range result.Dependencies {
|
||||||
|
if dep.ID == pkg.ID {
|
||||||
|
return dep.DependsOn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPkgRelationshipType(pkg ftypes.Package) string {
|
||||||
|
if pkg.Indirect {
|
||||||
|
return IndirectRelationship
|
||||||
|
}
|
||||||
|
return DirectRelationship
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPurl(t string, pkg ftypes.Package) (string, error) {
|
||||||
|
packageUrl, err := purl.NewPackageURL(t, types.Metadata{}, pkg)
|
||||||
|
if err != nil {
|
||||||
|
return "", xerrors.Errorf("purl error: %w", err)
|
||||||
|
}
|
||||||
|
return packageUrl.ToString(), nil
|
||||||
|
}
|
||||||
158
pkg/report/github/github_test.go
Normal file
158
pkg/report/github/github_test.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package github_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
ftypes "github.com/aquasecurity/fanal/types"
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report/github"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriter_Write(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
report types.Report
|
||||||
|
want map[string]github.Manifest
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "os packages",
|
||||||
|
report: types.Report{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
ArtifactName: "alpine:3.14",
|
||||||
|
Results: types.Results{
|
||||||
|
{
|
||||||
|
Target: "yarn.lock",
|
||||||
|
Class: "lang-pkgs",
|
||||||
|
Type: "yarn",
|
||||||
|
Packages: []ftypes.Package{
|
||||||
|
{
|
||||||
|
Name: "@xtuc/ieee754",
|
||||||
|
Version: "1.2.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "@xtuc/long",
|
||||||
|
Version: "4.2.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "@xtuc/binaryen",
|
||||||
|
Version: "1.37.33",
|
||||||
|
Indirect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Vulnerabilities: []types.DetectedVulnerability{
|
||||||
|
{
|
||||||
|
VulnerabilityID: "CVE-2020-0001",
|
||||||
|
PkgName: "foo",
|
||||||
|
InstalledVersion: "1.2.3",
|
||||||
|
FixedVersion: "3.4.5",
|
||||||
|
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001",
|
||||||
|
Vulnerability: dbTypes.Vulnerability{
|
||||||
|
Title: "foobar",
|
||||||
|
Description: "baz",
|
||||||
|
Severity: "HIGH",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: map[string]github.Manifest{
|
||||||
|
"yarn.lock": {
|
||||||
|
Name: "yarn",
|
||||||
|
File: &github.File{
|
||||||
|
SrcLocation: "yarn.lock",
|
||||||
|
},
|
||||||
|
Resolved: map[string]github.Package{
|
||||||
|
"@xtuc/ieee754": {
|
||||||
|
PackageUrl: "pkg:npm/%40xtuc/ieee754@1.2.0",
|
||||||
|
Relationship: "direct",
|
||||||
|
Scope: "runtime",
|
||||||
|
},
|
||||||
|
"@xtuc/long": {
|
||||||
|
PackageUrl: "pkg:npm/%40xtuc/long@4.2.2",
|
||||||
|
Relationship: "direct",
|
||||||
|
Scope: "runtime",
|
||||||
|
},
|
||||||
|
"@xtuc/binaryen": {
|
||||||
|
PackageUrl: "pkg:npm/%40xtuc/binaryen@1.37.33",
|
||||||
|
Relationship: "indirect",
|
||||||
|
Scope: "runtime",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "maven",
|
||||||
|
report: types.Report{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
ArtifactName: "my-java-app",
|
||||||
|
Results: types.Results{
|
||||||
|
{
|
||||||
|
Target: "pom.xml",
|
||||||
|
Class: "lang-pkgs",
|
||||||
|
Type: "pom",
|
||||||
|
Packages: []ftypes.Package{
|
||||||
|
{
|
||||||
|
Name: "com.google.code.gson:gson",
|
||||||
|
Version: "2.2.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "net.sf.opencsv:opencsv",
|
||||||
|
Version: "2.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: map[string]github.Manifest{
|
||||||
|
"pom.xml": {
|
||||||
|
Name: "pom",
|
||||||
|
File: &github.File{
|
||||||
|
SrcLocation: "pom.xml",
|
||||||
|
},
|
||||||
|
Resolved: map[string]github.Package{
|
||||||
|
"com.google.code.gson:gson": {
|
||||||
|
PackageUrl: "pkg:maven/com.google.code.gson/gson@2.2.2",
|
||||||
|
Relationship: "direct",
|
||||||
|
Scope: "runtime",
|
||||||
|
},
|
||||||
|
"net.sf.opencsv:opencsv": {
|
||||||
|
PackageUrl: "pkg:maven/net.sf.opencsv/opencsv@2.3",
|
||||||
|
Relationship: "direct",
|
||||||
|
Scope: "runtime",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
jw := github.Writer{}
|
||||||
|
written := bytes.Buffer{}
|
||||||
|
jw.Output = &written
|
||||||
|
|
||||||
|
inputResults := tt.report
|
||||||
|
|
||||||
|
err := report.Write(inputResults, report.Option{
|
||||||
|
Format: "github",
|
||||||
|
Output: &written,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var got github.DependencySnapshot
|
||||||
|
err = json.Unmarshal(written.Bytes(), &got)
|
||||||
|
assert.NoError(t, err, "invalid github written")
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got.Manifests, tt.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/clock"
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
@@ -162,9 +163,8 @@ func TestReportWriter_Template(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
report.Now = func() time.Time {
|
clock.SetFakeTime(t, time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC))
|
||||||
return time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC)
|
|
||||||
}
|
|
||||||
os.Setenv("AWS_ACCOUNT_ID", "123456789012")
|
os.Setenv("AWS_ACCOUNT_ID", "123456789012")
|
||||||
got := bytes.Buffer{}
|
got := bytes.Buffer{}
|
||||||
inputReport := types.Report{
|
inputReport := types.Report{
|
||||||
|
|||||||
@@ -4,23 +4,29 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/report/cyclonedx"
|
"github.com/aquasecurity/trivy/pkg/report/cyclonedx"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/report/github"
|
||||||
"github.com/aquasecurity/trivy/pkg/report/spdx"
|
"github.com/aquasecurity/trivy/pkg/report/spdx"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SchemaVersion = 2
|
SchemaVersion = 2
|
||||||
)
|
|
||||||
|
|
||||||
// Now returns the current time
|
FormatTable = "table"
|
||||||
var Now = time.Now
|
FormatJSON = "json"
|
||||||
|
FormatTemplate = "template"
|
||||||
|
FormatSarif = "sarif"
|
||||||
|
FormatCycloneDX = "cyclonedx"
|
||||||
|
FormatSPDX = "spdx"
|
||||||
|
FormatSPDXJSON = "spdx-json"
|
||||||
|
FormatGitHub = "github"
|
||||||
|
)
|
||||||
|
|
||||||
type Option struct {
|
type Option struct {
|
||||||
Format string
|
Format string
|
||||||
@@ -38,7 +44,7 @@ type Option struct {
|
|||||||
func Write(report types.Report, option Option) error {
|
func Write(report types.Report, option Option) error {
|
||||||
var writer Writer
|
var writer Writer
|
||||||
switch option.Format {
|
switch option.Format {
|
||||||
case "table":
|
case FormatTable:
|
||||||
writer = &TableWriter{
|
writer = &TableWriter{
|
||||||
Output: option.Output,
|
Output: option.Output,
|
||||||
Severities: option.Severities,
|
Severities: option.Severities,
|
||||||
@@ -46,14 +52,16 @@ func Write(report types.Report, option Option) error {
|
|||||||
IncludeNonFailures: option.IncludeNonFailures,
|
IncludeNonFailures: option.IncludeNonFailures,
|
||||||
Trace: option.Trace,
|
Trace: option.Trace,
|
||||||
}
|
}
|
||||||
case "json":
|
case FormatJSON:
|
||||||
writer = &JSONWriter{Output: option.Output}
|
writer = &JSONWriter{Output: option.Output}
|
||||||
case "cyclonedx":
|
case FormatGitHub:
|
||||||
|
writer = &github.Writer{Output: option.Output, Version: option.AppVersion}
|
||||||
|
case FormatCycloneDX:
|
||||||
// TODO: support xml format option with cyclonedx writer
|
// TODO: support xml format option with cyclonedx writer
|
||||||
writer = cyclonedx.NewWriter(option.Output, option.AppVersion)
|
writer = cyclonedx.NewWriter(option.Output, option.AppVersion)
|
||||||
case "spdx", "spdx-json":
|
case FormatSPDX, FormatSPDXJSON:
|
||||||
writer = spdx.NewWriter(option.Output, option.AppVersion, option.Format)
|
writer = spdx.NewWriter(option.Output, option.AppVersion, option.Format)
|
||||||
case "template":
|
case FormatTemplate:
|
||||||
// We keep `sarif.tpl` template working for backward compatibility for a while.
|
// We keep `sarif.tpl` template working for backward compatibility for a while.
|
||||||
if strings.HasPrefix(option.OutputTemplate, "@") && strings.HasSuffix(option.OutputTemplate, "sarif.tpl") {
|
if strings.HasPrefix(option.OutputTemplate, "@") && strings.HasSuffix(option.OutputTemplate, "sarif.tpl") {
|
||||||
log.Logger.Warn("Using `--template sarif.tpl` is deprecated. Please migrate to `--format sarif`. See https://github.com/aquasecurity/trivy/discussions/1571")
|
log.Logger.Warn("Using `--template sarif.tpl` is deprecated. Please migrate to `--format sarif`. See https://github.com/aquasecurity/trivy/discussions/1571")
|
||||||
@@ -64,7 +72,7 @@ func Write(report types.Report, option Option) error {
|
|||||||
if writer, err = NewTemplateWriter(option.Output, option.OutputTemplate); err != nil {
|
if writer, err = NewTemplateWriter(option.Output, option.OutputTemplate); err != nil {
|
||||||
return xerrors.Errorf("failed to initialize template writer: %w", err)
|
return xerrors.Errorf("failed to initialize template writer: %w", err)
|
||||||
}
|
}
|
||||||
case "sarif":
|
case FormatSarif:
|
||||||
writer = SarifWriter{Output: option.Output, Version: option.AppVersion}
|
writer = SarifWriter{Output: option.Output, Version: option.AppVersion}
|
||||||
default:
|
default:
|
||||||
return xerrors.Errorf("unknown format: %v", option.Format)
|
return xerrors.Errorf("unknown format: %v", option.Format)
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ func (s Scanner) scanLibrary(apps []ftypes.Application, options types.ScanOption
|
|||||||
}
|
}
|
||||||
if options.ListAllPackages {
|
if options.ListAllPackages {
|
||||||
libReport.Packages = app.Libraries
|
libReport.Packages = app.Libraries
|
||||||
|
libReport.Dependencies = app.Dependencies
|
||||||
}
|
}
|
||||||
results = append(results, libReport)
|
results = append(results, libReport)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
v1 "github.com/google/go-containerregistry/pkg/v1" // nolint: goimports
|
v1 "github.com/google/go-containerregistry/pkg/v1" // nolint: goimports
|
||||||
|
|
||||||
ftypes "github.com/aquasecurity/fanal/types"
|
ftypes "github.com/aquasecurity/fanal/types"
|
||||||
|
gdpTypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Report represents a scan result
|
// Report represents a scan result
|
||||||
@@ -48,6 +49,7 @@ type Result struct {
|
|||||||
Class ResultClass `json:"Class,omitempty"`
|
Class ResultClass `json:"Class,omitempty"`
|
||||||
Type string `json:"Type,omitempty"`
|
Type string `json:"Type,omitempty"`
|
||||||
Packages []ftypes.Package `json:"Packages,omitempty"`
|
Packages []ftypes.Package `json:"Packages,omitempty"`
|
||||||
|
Dependencies []gdpTypes.Dependency `json:"Dependencies,omitempty"`
|
||||||
Vulnerabilities []DetectedVulnerability `json:"Vulnerabilities,omitempty"`
|
Vulnerabilities []DetectedVulnerability `json:"Vulnerabilities,omitempty"`
|
||||||
MisconfSummary *MisconfSummary `json:"MisconfSummary,omitempty"`
|
MisconfSummary *MisconfSummary `json:"MisconfSummary,omitempty"`
|
||||||
Misconfigurations []DetectedMisconfiguration `json:"Misconfigurations,omitempty"`
|
Misconfigurations []DetectedMisconfiguration `json:"Misconfigurations,omitempty"`
|
||||||
|
|||||||
Reference in New Issue
Block a user