Skip to content

Commit

Permalink
Add support for activity and provenance scores
Browse files Browse the repository at this point in the history
Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]>
  • Loading branch information
puerco committed May 9, 2024
1 parent 55e15d7 commit 723ee7a
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 25 deletions.
25 changes: 24 additions & 1 deletion internal/engine/eval/trusty/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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,
})
}
Expand Down
8 changes: 8 additions & 0 deletions internal/engine/eval/trusty/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 41 additions & 24 deletions internal/engine/eval/trusty/trusty.go
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand All @@ -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)
Expand All @@ -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
}

0 comments on commit 723ee7a

Please sign in to comment.