From ca64d7fc3128fd4a47bea31a980d00f94251a503 Mon Sep 17 00:00:00 2001 From: kl-sinclair Date: Sat, 14 Jan 2023 01:24:58 +0900 Subject: [PATCH] feat(report): Include dependencies into scan result and cyclondex for supply chain security on Integration with GitHub Security Alerts (#1584) * feat(report): Enhance scan result and cyclondex for supply chain security on Integration with GitHub Security Alerts * derive ecosystem/version from dependency graph * fix vars name && fetch manifest info on GSA && arrange ghpkgToPURL structure * fix miscs * typo in error message * fix ecosystem equally to trivy * miscs * refactoring * recursive dependency graph pagination * change var name && update comments * omit map type of ghpkgToPURL in signatures * fix vars name * goimports * make fmt * fix comment Co-authored-by: MaineK00n --- cache/bolt.go | 2 +- config/config.go | 2 +- detector/detector.go | 4 + detector/github.go | 153 ++++++++++++++++++++++++++++++++++++- detector/wordpress.go | 6 +- logging/logutil.go | 3 +- models/github.go | 86 +++++++++++++++++++++ models/scanresults.go | 19 ++--- models/vulninfos.go | 31 ++++++-- reporter/sbom/cyclonedx.go | 60 +++++++++++++-- reporter/slack.go | 2 +- reporter/util.go | 2 +- scanner/executil.go | 2 +- scanner/freebsd.go | 2 +- scanner/redhatbase.go | 2 +- scanner/redhatbase_test.go | 2 +- tui/tui.go | 2 +- 17 files changed, 340 insertions(+), 40 deletions(-) create mode 100644 models/github.go diff --git a/cache/bolt.go b/cache/bolt.go index a07b56a3..8bc8cffc 100644 --- a/cache/bolt.go +++ b/cache/bolt.go @@ -48,7 +48,7 @@ func (b Bolt) Close() error { return b.db.Close() } -// CreateBucketIfNotExists creates a bucket that is specified by arg. +// CreateBucketIfNotExists creates a bucket that is specified by arg. func (b *Bolt) createBucketIfNotExists(name string) error { return b.db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(name)) diff --git a/config/config.go b/config/config.go index 64850c67..13667f54 100644 --- a/config/config.go +++ b/config/config.go @@ -21,7 +21,7 @@ var Revision string // Conf has Configuration var Conf Config -//Config is struct of Configuration +// Config is struct of Configuration type Config struct { logging.LogOpts diff --git a/detector/detector.go b/detector/detector.go index 035562b1..bd491eb5 100644 --- a/detector/detector.go +++ b/detector/detector.go @@ -303,6 +303,10 @@ func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]config.GitHub } logging.Log.Infof("%s: %d CVEs detected with GHSA %s/%s", r.FormatServerName(), n, owner, repo) + + if err = DetectGitHubDependencyGraph(r, owner, repo, setting.Token); err != nil { + return xerrors.Errorf("Failed to access GitHub Dependency graph: %w", err) + } } return nil } diff --git a/detector/github.go b/detector/github.go index e455353a..d6b90528 100644 --- a/detector/github.go +++ b/detector/github.go @@ -29,7 +29,7 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string, // TODO Use `https://github.com/shurcooL/githubv4` if the tool supports vulnerabilityAlerts Endpoint // Memo : https://developer.github.com/v4/explorer/ const jsonfmt = `{"query": - "query { repository(owner:\"%s\", name:\"%s\") { url vulnerabilityAlerts(first: %d, states:[OPEN], %s) { pageInfo { endCursor hasNextPage startCursor } edges { node { id dismissReason dismissedAt securityVulnerability{ package { name ecosystem } severity vulnerableVersionRange firstPatchedVersion { identifier } } securityAdvisory { description ghsaId permalink publishedAt summary updatedAt withdrawnAt origin severity references { url } identifiers { type value } } } } } } } "}` + "query { repository(owner:\"%s\", name:\"%s\") { url vulnerabilityAlerts(first: %d, states:[OPEN], %s) { pageInfo { endCursor hasNextPage startCursor } edges { node { id dismissReason dismissedAt securityVulnerability{ package { name ecosystem } severity vulnerableVersionRange firstPatchedVersion { identifier } } vulnerableManifestFilename vulnerableManifestPath vulnerableRequirements securityAdvisory { description ghsaId permalink publishedAt summary updatedAt withdrawnAt origin severity references { url } identifiers { type value } } } } } } } "}` after := "" for { @@ -79,11 +79,19 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string, continue } - pkgName := fmt.Sprintf("%s %s", + repoURLPkgName := fmt.Sprintf("%s %s", alerts.Data.Repository.URL, v.Node.SecurityVulnerability.Package.Name) m := models.GitHubSecurityAlert{ - PackageName: pkgName, + PackageName: repoURLPkgName, + Repository: alerts.Data.Repository.URL, + Package: models.GSAVulnerablePackage{ + Name: v.Node.SecurityVulnerability.Package.Name, + Ecosystem: v.Node.SecurityVulnerability.Package.Ecosystem, + ManifestFilename: v.Node.VulnerableManifestFilename, + ManifestPath: v.Node.VulnerableManifestPath, + Requirements: v.Node.VulnerableRequirements, + }, FixedIn: v.Node.SecurityVulnerability.FirstPatchedVersion.Identifier, AffectedRange: v.Node.SecurityVulnerability.VulnerableVersionRange, Dismissed: len(v.Node.DismissReason) != 0, @@ -175,7 +183,10 @@ type SecurityAlerts struct { Identifier string `json:"identifier"` } `json:"firstPatchedVersion"` } `json:"securityVulnerability"` - SecurityAdvisory struct { + VulnerableManifestFilename string `json:"vulnerableManifestFilename"` + VulnerableManifestPath string `json:"vulnerableManifestPath"` + VulnerableRequirements string `json:"vulnerableRequirements"` + SecurityAdvisory struct { Description string `json:"description"` GhsaID string `json:"ghsaId"` Permalink string `json:"permalink"` @@ -199,3 +210,137 @@ type SecurityAlerts struct { } `json:"repository"` } `json:"data"` } + +// DetectGitHubDependencyGraph access to owner/repo on GitHub and fetch dependency graph of the repository via GitHub API v4 GraphQL and then set to the given ScanResult. +// https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-the-dependency-graph +func DetectGitHubDependencyGraph(r *models.ScanResult, owner, repo, token string) (err error) { + src := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + //TODO Proxy + httpClient := oauth2.NewClient(context.Background(), src) + r.GitHubManifests = models.DependencyGraphManifests{} + + return fetchDependencyGraph(r, httpClient, owner, repo, "", "") +} + +// recursive function +func fetchDependencyGraph(r *models.ScanResult, httpClient *http.Client, owner, repo, after, dependenciesAfter string) (err error) { + const queryFmt = `{"query": + "query { repository(owner:\"%s\", name:\"%s\") { url dependencyGraphManifests(first: %d, withDependencies: true%s) { pageInfo { endCursor hasNextPage } edges { node { blobPath filename repository { url } parseable exceedsMaxSize dependenciesCount dependencies%s { pageInfo { endCursor hasNextPage } edges { node { packageName packageManager repository { url } requirements hasDependencies } } } } } } } }"}` + + queryStr := fmt.Sprintf(queryFmt, owner, repo, 100, after, dependenciesAfter) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, + "https://api.github.com/graphql", + bytes.NewBuffer([]byte(queryStr)), + ) + defer cancel() + if err != nil { + return err + } + + // https://docs.github.com/en/graphql/overview/schema-previews#access-to-a-repository-s-dependency-graph-preview + // TODO remove this header if it is no longer preview status in the future. + req.Header.Set("Accept", "application/vnd.github.hawkgirl-preview+json") + req.Header.Set("Content-Type", "application/json") + + resp, err := httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + graph := DependencyGraph{} + if err := json.Unmarshal(body, &graph); err != nil { + return err + } + + if graph.Data.Repository.URL == "" { + return errof.New(errof.ErrFailedToAccessGithubAPI, + fmt.Sprintf("Failed to access to GitHub API. Response: %s", string(body))) + } + + dependenciesAfter = "" + for _, m := range graph.Data.Repository.DependencyGraphManifests.Edges { + manifest, ok := r.GitHubManifests[m.Node.Filename] + if !ok { + manifest = models.DependencyGraphManifest{ + Filename: m.Node.Filename, + Repository: m.Node.Repository.URL, + Dependencies: []models.Dependency{}, + } + } + for _, d := range m.Node.Dependencies.Edges { + manifest.Dependencies = append(manifest.Dependencies, models.Dependency{ + PackageName: d.Node.PackageName, + PackageManager: d.Node.PackageManager, + Repository: d.Node.Repository.URL, + Requirements: d.Node.Requirements, + }) + } + r.GitHubManifests[m.Node.Filename] = manifest + + if m.Node.Dependencies.PageInfo.HasNextPage { + dependenciesAfter = fmt.Sprintf(`(after: \"%s\")`, m.Node.Dependencies.PageInfo.EndCursor) + } + } + if dependenciesAfter != "" { + return fetchDependencyGraph(r, httpClient, owner, repo, after, dependenciesAfter) + } + + if graph.Data.Repository.DependencyGraphManifests.PageInfo.HasNextPage { + after = fmt.Sprintf(`, after: \"%s\"`, graph.Data.Repository.DependencyGraphManifests.PageInfo.EndCursor) + return fetchDependencyGraph(r, httpClient, owner, repo, after, dependenciesAfter) + } + + return nil +} + +type DependencyGraph struct { + Data struct { + Repository struct { + URL string `json:"url"` + DependencyGraphManifests struct { + PageInfo struct { + EndCursor string `json:"endCursor"` + HasNextPage bool `json:"hasNextPage"` + } `json:"pageInfo"` + Edges []struct { + Node struct { + BlobPath string `json:"blobPath"` + Filename string `json:"filename"` + Repository struct { + URL string `json:"url"` + } + Parseable bool `json:"parseable"` + ExceedsMaxSize bool `json:"exceedsMaxSize"` + DependenciesCount int `json:"dependenciesCount"` + Dependencies struct { + PageInfo struct { + EndCursor string `json:"endCursor"` + HasNextPage bool `json:"hasNextPage"` + } `json:"pageInfo"` + Edges []struct { + Node struct { + PackageName string `json:"packageName"` + PackageManager string `json:"packageManager"` + Repository struct { + URL string `json:"url"` + } + Requirements string `json:"requirements"` + HasDependencies bool `json:"hasDependencies"` + } `json:"node"` + } `json:"edges"` + } `json:"dependencies"` + } `json:"node"` + } `json:"edges"` + } `json:"dependencyGraphManifests"` + } `json:"repository"` + } `json:"data"` +} diff --git a/detector/wordpress.go b/detector/wordpress.go index 3f6236c9..f95ea52e 100644 --- a/detector/wordpress.go +++ b/detector/wordpress.go @@ -21,7 +21,7 @@ import ( "golang.org/x/xerrors" ) -//WpCveInfos is for wpscan json +// WpCveInfos is for wpscan json type WpCveInfos struct { ReleaseDate string `json:"release_date"` ChangelogURL string `json:"changelog_url"` @@ -33,7 +33,7 @@ type WpCveInfos struct { Error string `json:"error"` } -//WpCveInfo is for wpscan json +// WpCveInfo is for wpscan json type WpCveInfo struct { ID string `json:"id"` Title string `json:"title"` @@ -44,7 +44,7 @@ type WpCveInfo struct { FixedIn string `json:"fixed_in"` } -//References is for wpscan json +// References is for wpscan json type References struct { URL []string `json:"url"` Cve []string `json:"cve"` diff --git a/logging/logutil.go b/logging/logutil.go index 6198a38a..33a244ec 100644 --- a/logging/logutil.go +++ b/logging/logutil.go @@ -15,7 +15,7 @@ import ( formatter "github.com/kotakanbe/logrus-prefixed-formatter" ) -//LogOpts has options for logging +// LogOpts has options for logging type LogOpts struct { Debug bool `json:"debug,omitempty"` DebugSQL bool `json:"debugSQL,omitempty"` @@ -45,7 +45,6 @@ func NewNormalLogger() Logger { return Logger{Entry: logrus.Entry{Logger: logrus.New()}} } -// NewNormalLogger creates normal logger func NewIODiscardLogger() Logger { l := logrus.New() l.Out = io.Discard diff --git a/models/github.go b/models/github.go new file mode 100644 index 00000000..d67b5799 --- /dev/null +++ b/models/github.go @@ -0,0 +1,86 @@ +package models + +import ( + "strings" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// DependencyGraphManifests has a map of DependencyGraphManifest +// key: Filename +type DependencyGraphManifests map[string]DependencyGraphManifest + +type DependencyGraphManifest struct { + Filename string `json:"filename"` + Repository string `json:"repository"` + Dependencies []Dependency `json:"dependencies"` +} + +// Ecosystem returns a name of ecosystem(or package manager) of manifest(lock) file in trivy way +// https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-the-dependency-graph#supported-package-ecosystems +func (m DependencyGraphManifest) Ecosystem() string { + switch { + case strings.HasSuffix(m.Filename, "Cargo.lock"), + strings.HasSuffix(m.Filename, "Cargo.toml"): + return ftypes.Cargo // Rust + case strings.HasSuffix(m.Filename, "composer.lock"), + strings.HasSuffix(m.Filename, "composer.json"): + return ftypes.Composer // PHP + case strings.HasSuffix(m.Filename, ".csproj"), + strings.HasSuffix(m.Filename, ".vbproj"), + strings.HasSuffix(m.Filename, ".nuspec"), + strings.HasSuffix(m.Filename, ".vcxproj"), + strings.HasSuffix(m.Filename, ".fsproj"), + strings.HasSuffix(m.Filename, "packages.config"): + return ftypes.NuGet // .NET languages (C#, F#, VB), C++ + case strings.HasSuffix(m.Filename, "go.sum"), + strings.HasSuffix(m.Filename, "go.mod"): + return ftypes.GoModule // Go + case strings.HasSuffix(m.Filename, "pom.xml"): + return ftypes.Pom // Java, Scala + case strings.HasSuffix(m.Filename, "package-lock.json"), + strings.HasSuffix(m.Filename, "package.json"): + return ftypes.Npm // JavaScript + case strings.HasSuffix(m.Filename, "yarn.lock"): + return ftypes.Yarn // JavaScript + case strings.HasSuffix(m.Filename, "requirements.txt"), + strings.HasSuffix(m.Filename, "requirements-dev.txt"), + strings.HasSuffix(m.Filename, "setup.py"): + return ftypes.Pip // Python + case strings.HasSuffix(m.Filename, "Pipfile.lock"), + strings.HasSuffix(m.Filename, "Pipfile"): + return ftypes.Pipenv // Python + case strings.HasSuffix(m.Filename, "poetry.lock"), + strings.HasSuffix(m.Filename, "pyproject.toml"): + return ftypes.Poetry // Python + case strings.HasSuffix(m.Filename, "Gemfile.lock"), + strings.HasSuffix(m.Filename, "Gemfile"): + return ftypes.Bundler // Ruby + case strings.HasSuffix(m.Filename, ".gemspec"): + return ftypes.GemSpec // Ruby + case strings.HasSuffix(m.Filename, "pubspec.lock"), + strings.HasSuffix(m.Filename, "pubspec.yaml"): + return "pub" // Dart + case strings.HasSuffix(m.Filename, ".yml"), + strings.HasSuffix(m.Filename, ".yaml"): + return "actions" // GitHub Actions workflows + default: + return "unknown" + } +} + +type Dependency struct { + PackageName string `json:"packageName"` + PackageManager string `json:"packageManager"` + Repository string `json:"repository"` + Requirements string `json:"requirements"` +} + +func (d Dependency) Version() string { + s := strings.Split(d.Requirements, " ") + if len(s) == 2 && s[0] == "=" { + return s[1] + } + // in case of ranged version + return "" +} diff --git a/models/scanresults.go b/models/scanresults.go index e0e7a818..0d6b124e 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -45,15 +45,16 @@ type ScanResult struct { Errors []string `json:"errors"` Warnings []string `json:"warnings"` - ScannedCves VulnInfos `json:"scannedCves"` - RunningKernel Kernel `json:"runningKernel"` - Packages Packages `json:"packages"` - SrcPackages SrcPackages `json:",omitempty"` - EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules - WordPressPackages WordPressPackages `json:",omitempty"` - LibraryScanners LibraryScanners `json:"libraries,omitempty"` - CweDict CweDict `json:"cweDict,omitempty"` - Optional map[string]interface{} `json:",omitempty"` + ScannedCves VulnInfos `json:"scannedCves"` + RunningKernel Kernel `json:"runningKernel"` + Packages Packages `json:"packages"` + SrcPackages SrcPackages `json:",omitempty"` + EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules + WordPressPackages WordPressPackages `json:",omitempty"` + GitHubManifests DependencyGraphManifests `json:"gitHubManifests,omitempty"` + LibraryScanners LibraryScanners `json:"libraries,omitempty"` + CweDict CweDict `json:"cweDict,omitempty"` + Optional map[string]interface{} `json:",omitempty"` Config struct { Scan config.Config `json:"scan"` Report config.Config `json:"report"` diff --git a/models/vulninfos.go b/models/vulninfos.go index 8ab8798a..f0b207ad 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -284,7 +284,7 @@ type GitHubSecurityAlerts []GitHubSecurityAlert // Add adds given arg to the slice and return the slice (immutable) func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlerts { for _, a := range g { - if a.PackageName == alert.PackageName { + if a.RepoURLPackageName() == alert.RepoURLPackageName() { return g } } @@ -294,19 +294,34 @@ func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlert // Names return a slice of lib names func (g GitHubSecurityAlerts) Names() (names []string) { for _, a := range g { - names = append(names, a.PackageName) + names = append(names, a.RepoURLPackageName()) } return names } // GitHubSecurityAlert has detected CVE-ID, PackageName, Status fetched via GitHub API type GitHubSecurityAlert struct { - PackageName string `json:"packageName"` - FixedIn string `json:"fixedIn"` - AffectedRange string `json:"affectedRange"` - Dismissed bool `json:"dismissed"` - DismissedAt time.Time `json:"dismissedAt"` - DismissReason string `json:"dismissReason"` + // TODO: PackageName deprecated. it will be removed next time. + PackageName string `json:"packageName"` + Repository string `json:"repository"` + Package GSAVulnerablePackage `json:"package,omitempty"` + FixedIn string `json:"fixedIn"` + AffectedRange string `json:"affectedRange"` + Dismissed bool `json:"dismissed"` + DismissedAt time.Time `json:"dismissedAt"` + DismissReason string `json:"dismissReason"` +} + +func (a GitHubSecurityAlert) RepoURLPackageName() string { + return fmt.Sprintf("%s %s", a.Repository, a.Package.Name) +} + +type GSAVulnerablePackage struct { + Name string `json:"name"` + Ecosystem string `json:"ecosystem"` + ManifestFilename string `json:"manifestFilename"` + ManifestPath string `json:"manifestPath"` + Requirements string `json:"requirements"` } // LibraryFixedIns is a list of Library's FixedIn diff --git a/reporter/sbom/cyclonedx.go b/reporter/sbom/cyclonedx.go index 94ff94fe..17945f31 100644 --- a/reporter/sbom/cyclonedx.go +++ b/reporter/sbom/cyclonedx.go @@ -84,6 +84,18 @@ func cdxComponents(result models.ScanResult, metaBomRef string) (*[]cdx.Componen components = append(components, libpkgComps...) } + ghpkgToPURL := map[string]map[string]string{} + for _, ghm := range result.GitHubManifests { + ghpkgToPURL[ghm.Filename] = map[string]string{} + + ghpkgComps := ghpkgToCdxComponents(ghm, ghpkgToPURL) + bomRefs[metaBomRef] = append(bomRefs[metaBomRef], ghpkgComps[0].BOMRef) + for _, comp := range ghpkgComps[1:] { + bomRefs[ghpkgComps[0].BOMRef] = append(bomRefs[ghpkgComps[0].BOMRef], comp.BOMRef) + } + components = append(components, ghpkgComps...) + } + wppkgToPURL := map[string]string{} if wppkgComps := wppkgToCdxComponents(result.WordPressPackages, wppkgToPURL); wppkgComps != nil { bomRefs[metaBomRef] = append(bomRefs[metaBomRef], wppkgComps[0].BOMRef) @@ -93,7 +105,7 @@ func cdxComponents(result models.ScanResult, metaBomRef string) (*[]cdx.Componen components = append(components, wppkgComps...) } - return &components, cdxDependencies(bomRefs), cdxVulnerabilities(result, ospkgToPURL, libpkgToPURL, wppkgToPURL) + return &components, cdxDependencies(bomRefs), cdxVulnerabilities(result, ospkgToPURL, libpkgToPURL, ghpkgToPURL, wppkgToPURL) } func osToCdxComponent(family, release, runningKernelRelease, runningKernelVersion string) cdx.Component { @@ -258,6 +270,37 @@ func libpkgToCdxComponents(libscanner models.LibraryScanner, libpkgToPURL map[st return components } +func ghpkgToCdxComponents(m models.DependencyGraphManifest, ghpkgToPURL map[string]map[string]string) []cdx.Component { + components := []cdx.Component{ + { + BOMRef: uuid.NewString(), + Type: cdx.ComponentTypeApplication, + Name: m.Filename, + Properties: &[]cdx.Property{ + { + Name: "future-architect:vuls:Type", + Value: m.Ecosystem(), + }, + }, + }, + } + + for _, dep := range m.Dependencies { + purl := packageurl.NewPackageURL(m.Ecosystem(), "", dep.PackageName, dep.Version(), packageurl.Qualifiers{{Key: "file_path", Value: m.Filename}}, "").ToString() + components = append(components, cdx.Component{ + BOMRef: purl, + Type: cdx.ComponentTypeLibrary, + Name: dep.PackageName, + Version: dep.Version(), + PackageURL: purl, + }) + + ghpkgToPURL[m.Filename][dep.PackageName] = purl + } + + return components +} + func wppkgToCdxComponents(wppkgs models.WordPressPackages, wppkgToPURL map[string]string) []cdx.Component { if len(wppkgs) == 0 { return nil @@ -352,7 +395,7 @@ func toPkgPURL(osFamily, osVersion, packName, packVersion, packRelease, packArch return packageurl.NewPackageURL(purlType, osFamily, packName, version, qualifiers, "").ToString() } -func cdxVulnerabilities(result models.ScanResult, ospkgToPURL map[string]string, libpkgToPURL map[string]map[string]string, wppkgToPURL map[string]string) *[]cdx.Vulnerability { +func cdxVulnerabilities(result models.ScanResult, ospkgToPURL map[string]string, libpkgToPURL, ghpkgToPURL map[string]map[string]string, wppkgToPURL map[string]string) *[]cdx.Vulnerability { vulnerabilities := make([]cdx.Vulnerability, 0, len(result.ScannedCves)) for _, cve := range result.ScannedCves { vulnerabilities = append(vulnerabilities, cdx.Vulnerability{ @@ -361,7 +404,7 @@ func cdxVulnerabilities(result models.ScanResult, ospkgToPURL map[string]string, CWEs: cdxCWEs(cve.CveContents), Description: cdxDescription(cve.CveContents), Advisories: cdxAdvisories(cve.CveContents), - Affects: cdxAffects(cve, ospkgToPURL, libpkgToPURL, wppkgToPURL), + Affects: cdxAffects(cve, ospkgToPURL, libpkgToPURL, ghpkgToPURL, wppkgToPURL), }) } return &vulnerabilities @@ -433,7 +476,7 @@ func cdxCVSS3Rating(source, vector string, score float64, severity string) cdx.V return r } -func cdxAffects(cve models.VulnInfo, ospkgToPURL map[string]string, libpkgToPURL map[string]map[string]string, wppkgToPURL map[string]string) *[]cdx.Affects { +func cdxAffects(cve models.VulnInfo, ospkgToPURL map[string]string, libpkgToPURL, ghpkgToPURL map[string]map[string]string, wppkgToPURL map[string]string) *[]cdx.Affects { affects := make([]cdx.Affects, 0, len(cve.AffectedPackages)+len(cve.CpeURIs)+len(cve.LibraryFixedIns)+len(cve.WpPackageFixStats)) for _, p := range cve.AffectedPackages { @@ -450,7 +493,14 @@ func cdxAffects(cve models.VulnInfo, ospkgToPURL map[string]string, libpkgToPURL affects = append(affects, cdx.Affects{ Ref: libpkgToPURL[lib.Path][lib.Name], }) - + } + for _, alert := range cve.GitHubSecurityAlerts { + // TODO: not in dependency graph + if purl, ok := ghpkgToPURL[alert.Package.ManifestPath][alert.Package.Name]; ok { + affects = append(affects, cdx.Affects{ + Ref: purl, + }) + } } for _, wppack := range cve.WpPackageFixStats { affects = append(affects, cdx.Affects{ diff --git a/reporter/slack.go b/reporter/slack.go index 80d4394b..f50bb803 100644 --- a/reporter/slack.go +++ b/reporter/slack.go @@ -195,7 +195,7 @@ func (w SlackWriter) toSlackAttachments(r models.ScanResult) (attaches []slack.A candidate = append(candidate, "?") } for _, n := range vinfo.GitHubSecurityAlerts { - installed = append(installed, n.PackageName) + installed = append(installed, n.RepoURLPackageName()) candidate = append(candidate, "?") } diff --git a/reporter/util.go b/reporter/util.go index f7487f45..41f94425 100644 --- a/reporter/util.go +++ b/reporter/util.go @@ -404,7 +404,7 @@ No CVE-IDs are found in updatable packages. } for _, alert := range vuln.GitHubSecurityAlerts { - data = append(data, []string{"GitHub", alert.PackageName}) + data = append(data, []string{"GitHub", alert.RepoURLPackageName()}) } for _, wp := range vuln.WpPackageFixStats { diff --git a/scanner/executil.go b/scanner/executil.go index 3d18fcf7..228c636a 100644 --- a/scanner/executil.go +++ b/scanner/executil.go @@ -62,7 +62,7 @@ const sudo = true // noSudo is Const value for normal user mode const noSudo = false -// Issue commands to the target servers in parallel via SSH or local execution. If execution fails, the server will be excluded from the target server list(servers) and added to the error server list(errServers). +// Issue commands to the target servers in parallel via SSH or local execution. If execution fails, the server will be excluded from the target server list(servers) and added to the error server list(errServers). func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) { resChan := make(chan osTypeInterface, len(servers)) defer close(resChan) diff --git a/scanner/freebsd.go b/scanner/freebsd.go index ec87a213..9ee70e0f 100644 --- a/scanner/freebsd.go +++ b/scanner/freebsd.go @@ -34,7 +34,7 @@ func newBsd(c config.ServerInfo) *bsd { return d } -//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb +// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb func detectFreebsd(c config.ServerInfo) (bool, osTypeInterface) { // Prevent from adding `set -o pipefail` option c.Distro = config.Distro{Family: constant.FreeBSD} diff --git a/scanner/redhatbase.go b/scanner/redhatbase.go index b5c4be05..e4bce263 100644 --- a/scanner/redhatbase.go +++ b/scanner/redhatbase.go @@ -801,7 +801,7 @@ func (o *redhatBase) parseNeedsRestarting(stdout string) (procs []models.NeedRes return } -//TODO refactor +// TODO refactor // procPathToFQPN returns Fully-Qualified-Package-Name from the command func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) { execCommand = strings.Replace(execCommand, "\x00", " ", -1) // for CentOS6.9 diff --git a/scanner/redhatbase_test.go b/scanner/redhatbase_test.go index 9e1aa35e..14216c15 100644 --- a/scanner/redhatbase_test.go +++ b/scanner/redhatbase_test.go @@ -603,7 +603,7 @@ func Test_redhatBase_parseRpmQfLine(t *testing.T) { { name: "valid line", fields: fields{base: base{}}, - args: args{line: "Percona-Server-shared-56 1 5.6.19 rel67.0.el6 x86_64"}, + args: args{line: "Percona-Server-shared-56 1 5.6.19 rel67.0.el6 x86_64"}, wantPkg: &models.Package{ Name: "Percona-Server-shared-56", Version: "1:5.6.19", diff --git a/tui/tui.go b/tui/tui.go index 8dbe075a..73760754 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -745,7 +745,7 @@ func setChangelogLayout(g *gocui.Gui) error { } for _, alert := range vinfo.GitHubSecurityAlerts { - lines = append(lines, "* "+alert.PackageName) + lines = append(lines, "* "+alert.RepoURLPackageName()) } r := currentScanResult