diff --git a/internal/engine/eval/trusty/actions.go b/internal/engine/eval/trusty/actions.go index decb409e30..b64bc1b2bf 100644 --- a/internal/engine/eval/trusty/actions.go +++ b/internal/engine/eval/trusty/actions.go @@ -59,6 +59,24 @@ Based on [Trusty](https://www.trustypkg.dev/) dependency data, Minder detected t ` ) +// RuleViolationReason are int constants that captures the various +// reasons a package was considered unsafe when compared with trusty data +type RuleViolationReason int + +const ( + // TRUSTY_LOW_SCORE Overall score was lower than threshold + TRUSTY_LOW_SCORE RuleViolationReason = iota + 1 + + // TRUSTY_MALICIOUS_PKG Package is marked as malicious + TRUSTY_MALICIOUS_PKG + + // TRUSTY_LOW_ACTIVITY The package does not have enough activity + TRUSTY_LOW_ACTIVITY + + // TRUSTY_LOW_PROVENANCE Low trust in proof of origin + TRUSTY_LOW_PROVENANCE +) + type maliciousTemplateData struct { PackageName string TrustyURL string @@ -76,7 +94,10 @@ type lowScoreTemplateData struct { } type dependencyAlternatives struct { - Dependency *pb.Dependency + Dependency *pb.Dependency + + // Reason captures the reason why a package was flagged + Reasons []RuleViolationReason trustyReply *Reply } @@ -92,10 +113,12 @@ type summaryPrHandler struct { func (sph *summaryPrHandler) trackAlternatives( dep *pb.PrDependencies_ContextualDependency, + violationReasons []RuleViolationReason, trustyReply *Reply, ) { sph.trackedAlternatives = append(sph.trackedAlternatives, dependencyAlternatives{ Dependency: dep.Dep, + Reasons: violationReasons, trustyReply: trustyReply, }) } diff --git a/internal/engine/eval/trusty/config.go b/internal/engine/eval/trusty/config.go index b3145749bd..c6bd2b4cd1 100644 --- a/internal/engine/eval/trusty/config.go +++ b/internal/engine/eval/trusty/config.go @@ -47,6 +47,14 @@ type ecosystemConfig struct { // If `evaluate_score` is set to something else (e.g. `provenance`) // then that score is used, which comes from the details field. EvaluateScore string `json:"evaluate_score" mapstructure:"evaluate_score"` + + // The provenance field contains the minimal provenance score + // to consider the origin of the package as trusted. + Provenance float64 `json:"provenance" mapstructure:"provenance"` + + // Activity is the minimal activity score that minder needs to find to + // consider the package as trustworthy. + Activity float64 `json:"activity" mapstructure:"activity"` } // config is the configuration for the vulncheck evaluator diff --git a/internal/engine/eval/trusty/trusty.go b/internal/engine/eval/trusty/trusty.go index 51db377ebe..050cdf0709 100644 --- a/internal/engine/eval/trusty/trusty.go +++ b/internal/engine/eval/trusty/trusty.go @@ -195,6 +195,9 @@ func classifyDependency( ctx context.Context, logger *zerolog.Logger, trusty *trustyClient, ruleConfig *config, prSummary *summaryPrHandler, dep *pb.PrDependencies_ContextualDependency, ) error { + // Check all the policy violations + reasons := []RuleViolationReason{} + ecoConfig := ruleConfig.getEcosystemConfig(dep.Dep.Ecosystem) if ecoConfig == nil { logger.Info(). @@ -204,6 +207,7 @@ func classifyDependency( return nil } + // Call the Trusty API resp, err := trusty.SendRecvRequest(ctx, dep.Dep) if err != nil { return fmt.Errorf("failed to send request: %w", err) @@ -216,47 +220,60 @@ func classifyDependency( return nil } + // If the package is malicious, ensure that the score is 0 to avoid it + // getting ignored from the report if resp.PackageData.Malicious != nil && resp.PackageData.Malicious.Published.String() != "" { logger.Debug(). Str("dependency", fmt.Sprintf("%s@%s", dep.Dep.Name, dep.Dep.Version)). Str("malicious", "true"). Msgf("malicious dependency") - if resp.Summary.Score == nil { - s := float64(0) - resp.Summary.Score = &s + reasons = append(reasons, TRUSTY_MALICIOUS_PKG) + } + + packageScore := float64(0) + if resp.Summary.Score != nil { + packageScore = *resp.Summary.Score + } + + descr := map[string]any{} + if resp.Summary.Description != nil { + descr = resp.Summary.Description + } + + // Ensure don't panic checking all fields are there + for _, fld := range []string{"activity", "provenance"} { + if _, ok := descr[fld]; !ok { + descr[fld] = 0 } } - if resp.Summary.Score == nil { - logger.Info(). - Str("dependency", dep.Dep.Name). - Msgf("the dependency has no score, skipping") - return nil + if ecoConfig.Score <= packageScore { + reasons = append(reasons, TRUSTY_LOW_SCORE) + } + if ecoConfig.Provenance <= descr["provenance"].(float64) { + reasons = append(reasons, TRUSTY_LOW_PROVENANCE) } - s, err := ecoConfig.getScore(resp.Summary) - if err != nil { - return fmt.Errorf("failed to get score: %w", err) + if ecoConfig.Activity <= descr["activity"].(float64) { + reasons = append(reasons, TRUSTY_LOW_ACTIVITY) } + if len(reasons) > 0 { + logger.Debug(). + Str("dependency", dep.Dep.Name). + Str("score-source", ecoConfig.getScoreSource()). + Float64("score", packageScore). + Float64("threshold", ecoConfig.Score). + Msgf("the dependency has lower score than threshold or is malicious, tracking") - if s >= ecoConfig.Score { + prSummary.trackAlternatives(dep, reasons, resp) + } else { logger.Debug(). Str("dependency", dep.Dep.Name). Str("score-source", ecoConfig.getScoreSource()). - Float64("score", s). + Float64("score", *resp.Summary.Score). Float64("threshold", ecoConfig.Score). - Msgf("the dependency has higher score than threshold, skipping") - return nil + Msgf("the dependency has lower score than threshold or is malicious, tracking") } - - logger.Debug(). - Str("dependency", dep.Dep.Name). - Str("score-source", ecoConfig.getScoreSource()). - Float64("score", s). - Float64("threshold", ecoConfig.Score). - Msgf("the dependency has lower score than threshold or is malicious, tracking") - - prSummary.trackAlternatives(dep, resp) return nil }