mirror of
https://github.com/safedep/vet.git
synced 2025-12-10 13:43:01 -06:00
* feat: Add support for malysis min confidence config * fix: Test case to use factory function
184 lines
5.8 KiB
Go
184 lines
5.8 KiB
Go
package analyzer
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
malysisv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/malysis/v1"
|
|
"github.com/safedep/vet/gen/checks"
|
|
"github.com/safedep/vet/gen/filtersuite"
|
|
"github.com/safedep/vet/pkg/common/logger"
|
|
"github.com/safedep/vet/pkg/models"
|
|
"github.com/safedep/vet/pkg/readers"
|
|
)
|
|
|
|
type MalwareAnalyzerConfig struct {
|
|
// Flag to trust automated analysis results without needing
|
|
// a verification record
|
|
TrustAutomatedAnalysis bool
|
|
|
|
// Fail fast on malware detection
|
|
FailFast bool
|
|
|
|
// Minimum confidence level for malicious package analysis result to fail fast
|
|
// Should be HIGH, MEDIUM or LOW
|
|
MinimumConfidence string
|
|
|
|
// Internally mapped confidence level as per protobuf spec
|
|
minimumConfidenceLevel malysisv1.Report_Evidence_Confidence
|
|
}
|
|
|
|
func DefaultMalwareAnalyzerConfig() MalwareAnalyzerConfig {
|
|
return MalwareAnalyzerConfig{
|
|
TrustAutomatedAnalysis: false,
|
|
FailFast: true,
|
|
}
|
|
}
|
|
|
|
type malwareAnalyzer struct {
|
|
config MalwareAnalyzerConfig
|
|
m sync.Mutex
|
|
}
|
|
|
|
var _ Analyzer = (*malwareAnalyzer)(nil)
|
|
|
|
func NewMalwareAnalyzer(config MalwareAnalyzerConfig) (*malwareAnalyzer, error) {
|
|
config.minimumConfidenceLevel = malysisv1.Report_Evidence_CONFIDENCE_HIGH
|
|
if config.MinimumConfidence != "" {
|
|
confidenceName := fmt.Sprintf("CONFIDENCE_%s", strings.ToUpper(config.MinimumConfidence))
|
|
conf, ok := malysisv1.Report_Evidence_Confidence_value[confidenceName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid minimum confidence level: %s", config.MinimumConfidence)
|
|
}
|
|
|
|
config.minimumConfidenceLevel = malysisv1.Report_Evidence_Confidence(conf)
|
|
}
|
|
|
|
return &malwareAnalyzer{
|
|
config: config,
|
|
}, nil
|
|
}
|
|
|
|
func (a *malwareAnalyzer) Name() string {
|
|
return "Malware Analyzer"
|
|
}
|
|
|
|
func (a *malwareAnalyzer) Analyze(manifest *models.PackageManifest,
|
|
handler AnalyzerEventHandler,
|
|
) error {
|
|
return readers.NewManifestModelReader(manifest).EnumPackages(func(pkg *models.Package) error {
|
|
err := a.applyMalwareDecision(pkg)
|
|
if err != nil {
|
|
return fmt.Errorf("MalwareAnalyzer: Failed to apply malware decision for package %s/%s/%s: %w",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion(), err)
|
|
}
|
|
|
|
if !pkg.IsMalware() && !pkg.IsSuspicious() {
|
|
return nil
|
|
}
|
|
|
|
var filterMsg string
|
|
if pkg.IsMalware() {
|
|
filterMsg = fmt.Sprintf("MalwareAnalyzer: Package %s/%s/%s is classified as malicious",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion())
|
|
} else {
|
|
filterMsg = fmt.Sprintf("MalwareAnalyzer: Package %s/%s/%s is classified as suspicious",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion())
|
|
}
|
|
|
|
// Trigger a policy violation event so that it gets recorded
|
|
// across reports that are tracking policy violations
|
|
err = handler(&AnalyzerEvent{
|
|
Type: ET_FilterExpressionMatched,
|
|
Source: a.Name(),
|
|
Manifest: manifest,
|
|
Package: pkg,
|
|
Message: filterMsg,
|
|
Filter: &filtersuite.Filter{
|
|
Name: "malware",
|
|
CheckType: checks.CheckType_CheckTypeMalware,
|
|
Summary: filterMsg,
|
|
Description: fmt.Sprintf("%s analyzed the package for possible malicious behavior", a.Name()),
|
|
References: []string{"https://docs.safedep.io/cloud/malware-analysis"},
|
|
Tags: []string{"malware-analysis"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
logger.Errorf("MalwareAnalyzer: Failed to handle filter event for package %s/%s/%s: %v",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion(), err)
|
|
}
|
|
|
|
if a.config.FailFast && pkg.IsMalware() {
|
|
return handler(&AnalyzerEvent{
|
|
Type: ET_AnalyzerFailOnError,
|
|
Source: a.Name(),
|
|
Err: fmt.Errorf("Malware detected in package %s/%s/%s",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion()),
|
|
})
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (a *malwareAnalyzer) Finish() error {
|
|
return nil
|
|
}
|
|
|
|
// The malware analyzer makes a decision based on configuration and malware analysis results.
|
|
// The decision involves:
|
|
//
|
|
// - No action if the package is not classified as malware
|
|
// - Malware if a verfication record is available to confirm
|
|
// - Malware if `TrustAutomatedAnalysis` config is enabled and confidence is high
|
|
// - Suspicious for all other cases
|
|
func (a *malwareAnalyzer) applyMalwareDecision(pkg *models.Package) error {
|
|
ma := pkg.GetMalwareAnalysisResult()
|
|
if ma == nil || ma.Report == nil {
|
|
logger.Warnf("MalwareAnalyzer: No malware analysis result found for package %s/%s/%s",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion())
|
|
return nil
|
|
}
|
|
|
|
a.m.Lock()
|
|
defer a.m.Unlock()
|
|
|
|
report := ma.Report
|
|
if !report.GetInference().GetIsMalware() {
|
|
return nil
|
|
}
|
|
|
|
vr := ma.VerificationRecord
|
|
if vr != nil && vr.GetIsMalware() {
|
|
logger.Warnf("MalwareAnalyzer: Package %s/%s/%s is classified as malwars with verification record",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion())
|
|
ma.IsMalware = true
|
|
return nil
|
|
}
|
|
|
|
// By default we do not trust results without a verification record
|
|
// unless the config is set to trust automated analysis and a minimum confidence is set
|
|
if a.config.TrustAutomatedAnalysis && a.hasMinimumConfidence(report) {
|
|
logger.Warnf("MalwareAnalyzer: Package %s/%s/%s is classified as malware",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion())
|
|
ma.IsMalware = true
|
|
return nil
|
|
}
|
|
|
|
logger.Warnf("MalwareAnalyzer: Package %s/%s/%s is classified as suspicious due to low confidence",
|
|
pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion())
|
|
ma.IsSuspicious = true
|
|
return nil
|
|
}
|
|
|
|
func (a *malwareAnalyzer) hasMinimumConfidence(report *malysisv1.Report) bool {
|
|
confidence := report.GetInference().GetConfidence()
|
|
if confidence == malysisv1.Report_Evidence_CONFIDENCE_UNSPECIFIED {
|
|
return false
|
|
}
|
|
|
|
// Confidence is a protobuf enum, so we need to compare the integer values
|
|
return confidence <= a.config.minimumConfidenceLevel
|
|
}
|