diff --git a/analyzer/malysis_query.go b/analyzer/malysis_query.go index ecfcc16..f5c2c8c 100644 --- a/analyzer/malysis_query.go +++ b/analyzer/malysis_query.go @@ -20,6 +20,9 @@ type malysisQueryAnalyzer struct { Config MalysisQueryAnalyzerConfig } +var _ Analyzer = &malysisQueryAnalyzer{} +var _ MalysisAnalyzer = &malysisQueryAnalyzer{} + func NewMalysisQueryAnalyzer(config MalysisQueryAnalyzerConfig) (*malysisQueryAnalyzer, error) { client, err := drygrpc.GrpcClient("pmg-malysis-query", "community-api.safedep.io", "443", "", http.Header{}, []grpc.DialOption{}) @@ -33,6 +36,10 @@ func NewMalysisQueryAnalyzer(config MalysisQueryAnalyzerConfig) (*malysisQueryAn }, nil } +func (a *malysisQueryAnalyzer) Name() string { + return "malysis-query" +} + func (a *malysisQueryAnalyzer) Analyze(ctx context.Context, packageVersion *packagev1.PackageVersion) (*MalysisResult, error) { diff --git a/cmd/npm/common.go b/cmd/npm/common.go index 34d117e..79e3830 100644 --- a/cmd/npm/common.go +++ b/cmd/npm/common.go @@ -15,8 +15,13 @@ func executeCommonFlow(pm packagemanager.PackageManager, args []string) error { return fmt.Errorf("failed to create npm dependency resolver: %w", err) } - proxy, err := guard.NewPackageManagerGuard(guard.PackageManagerGuardConfig{}, - pm, packageResolver, []analyzer.Analyzer{}) + malysisQueryAnalyzer, err := analyzer.NewMalysisQueryAnalyzer(analyzer.MalysisQueryAnalyzerConfig{}) + if err != nil { + return fmt.Errorf("failed to create malysis query analyzer: %w", err) + } + + proxy, err := guard.NewPackageManagerGuard(guard.DefaultPackageManagerGuardConfig(), + pm, packageResolver, []analyzer.MalysisAnalyzer{malysisQueryAnalyzer}) if err != nil { return fmt.Errorf("failed to create package manager guard: %w", err) } diff --git a/guard/guard.go b/guard/guard.go index c5fe808..6efd8ed 100644 --- a/guard/guard.go +++ b/guard/guard.go @@ -6,16 +6,25 @@ import ( "os" "os/exec" + packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" "github.com/safedep/dry/log" "github.com/safedep/pmg/analyzer" "github.com/safedep/pmg/packagemanager" ) -type PackageManagerGuardConfig struct{} +type PackageManagerGuardConfig struct { + ResolveDependencies bool +} + +func DefaultPackageManagerGuardConfig() PackageManagerGuardConfig { + return PackageManagerGuardConfig{ + ResolveDependencies: true, + } +} type packageManagerGuard struct { config PackageManagerGuardConfig - analyzers []analyzer.Analyzer + analyzers []analyzer.MalysisAnalyzer packageManager packagemanager.PackageManager packageResolver packagemanager.PackageResolver } @@ -23,7 +32,7 @@ type packageManagerGuard struct { func NewPackageManagerGuard(config PackageManagerGuardConfig, packageManager packagemanager.PackageManager, packageResolver packagemanager.PackageResolver, - analyzers []analyzer.Analyzer) (*packageManagerGuard, error) { + analyzers []analyzer.MalysisAnalyzer) (*packageManagerGuard, error) { return &packageManagerGuard{ analyzers: analyzers, packageManager: packageManager, @@ -45,9 +54,68 @@ func (g *packageManagerGuard) Run(ctx context.Context, args []string) error { return g.continueExecution(ctx, parsedCommand) } - log.Debugf("Install targets: %v", parsedCommand.InstallTargets) + // TODO: We should track the dependency tree here so that we can trace a + // dependency to one of the parent packages from install targets - return nil + packagesToAnalyze := []*packagev1.PackageVersion{} + for _, installTarget := range parsedCommand.InstallTargets { + packagesToAnalyze = append(packagesToAnalyze, installTarget.PackageVersion) + } + + log.Debugf("Found %d install targets", len(parsedCommand.InstallTargets)) + + if g.config.ResolveDependencies { + for _, pkg := range parsedCommand.InstallTargets { + if pkg.PackageVersion.GetVersion() == "" { + log.Debugf("Resolving latest version for package: %s", pkg.PackageVersion.Package.Name) + latestVersion, err := g.packageResolver.ResolveLatestVersion(ctx, pkg.PackageVersion.GetPackage()) + if err != nil { + return fmt.Errorf("failed to resolve latest version: %w", err) + } + + pkg.PackageVersion.Version = latestVersion.GetVersion() + } + + log.Debugf("Resolving dependencies for package: %s@%s", pkg.PackageVersion.Package.Name, pkg.PackageVersion.Version) + + dependencies, err := g.packageResolver.ResolveDependencies(ctx, pkg.PackageVersion) + if err != nil { + return fmt.Errorf("failed to resolve dependencies: %w", err) + } + + log.Debugf("Resolved %d dependencies for package: %s@%s", len(dependencies), + pkg.PackageVersion.Package.Name, pkg.PackageVersion.Version) + + packagesToAnalyze = append(packagesToAnalyze, dependencies...) + } + } + + log.Debugf("Checking %d packages for malware", len(packagesToAnalyze)) + + maliciousPackages := []*packagev1.PackageVersion{} + for _, pkg := range packagesToAnalyze { + log.Debugf("Analyzing package: %s@%s", pkg.Package.Name, pkg.Version) + + for _, analyzer := range g.analyzers { + analysisResult, err := analyzer.Analyze(ctx, pkg) + if err != nil { + return fmt.Errorf("failed to analyze package: %w", err) + } + + if analysisResult.IsMalware() { + maliciousPackages = append(maliciousPackages, pkg) + } + } + } + + if len(maliciousPackages) > 0 { + log.Errorf("Found %d malicious packages", len(maliciousPackages)) + return fmt.Errorf("found malicious packages") + } + + log.Debugf("No malicious packages found, continuing execution") + + return g.continueExecution(ctx, parsedCommand) } func (g *packageManagerGuard) continueExecution(ctx context.Context, pc *packagemanager.ParsedCommand) error { diff --git a/packagemanager/npm.go b/packagemanager/npm.go index aa67b5a..76fbda4 100644 --- a/packagemanager/npm.go +++ b/packagemanager/npm.go @@ -52,7 +52,7 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error } // Check if this is an install command - if !npm.isInstallCommand(args[1]) { + if !npm.isInstallCommand(args[0]) { return &ParsedCommand{ Command: command, }, nil @@ -60,7 +60,7 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error // Extract packages from args var packages []string - for i := 2; i < len(args); i++ { + for i := 1; i < len(args); i++ { arg := args[i] if !strings.HasPrefix(arg, "-") { packages = append(packages, arg) diff --git a/packagemanager/npm_resolver.go b/packagemanager/npm_resolver.go index e7eb6a2..74db6ed 100644 --- a/packagemanager/npm_resolver.go +++ b/packagemanager/npm_resolver.go @@ -21,7 +21,7 @@ type NpmDependencyResolverConfig struct { func NewDefaultNpmDependencyResolverConfig() NpmDependencyResolverConfig { return NpmDependencyResolverConfig{ IncludeDevDependencies: true, - IncludeTransitiveDependencies: true, + IncludeTransitiveDependencies: false, TransitiveDepth: 5, FailFast: false, }