mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: add suppport for bypassing the blocking behavior of malicious packages (#53)
* feat: add suppport for bypassing the blocking behavior of malicious packages * feat: add InsecureInstallation config to bypass malware scanning with tests * ui: introduce ShowWarning interaction method * guard test fix
This commit is contained in:
@@ -24,6 +24,9 @@ type PackageManagerGuardInteraction struct {
|
||||
// ClearStatus is called to clear the status of the guard in the UI
|
||||
ClearStatus func()
|
||||
|
||||
// ShowWarning is called to show a warning message to the user
|
||||
ShowWarning func(message string)
|
||||
|
||||
// GetConfirmationOnMalware is called to get the confirmation of the user on the malware packages
|
||||
GetConfirmationOnMalware func(malwarePackages []*analyzer.PackageVersionAnalysisResult) (bool, error)
|
||||
|
||||
@@ -38,6 +41,7 @@ type PackageManagerGuardConfig struct {
|
||||
MaxConcurrentAnalyzes int
|
||||
AnalysisTimeout time.Duration
|
||||
DryRun bool
|
||||
InsecureInstallation bool
|
||||
}
|
||||
|
||||
func DefaultPackageManagerGuardConfig() PackageManagerGuardConfig {
|
||||
@@ -46,6 +50,7 @@ func DefaultPackageManagerGuardConfig() PackageManagerGuardConfig {
|
||||
MaxConcurrentAnalyzes: 10,
|
||||
AnalysisTimeout: 5 * time.Minute,
|
||||
DryRun: false,
|
||||
InsecureInstallation: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +80,12 @@ func NewPackageManagerGuard(config PackageManagerGuardConfig,
|
||||
func (g *packageManagerGuard) Run(ctx context.Context, args []string, parsedCommand *packagemanager.ParsedCommand) error {
|
||||
log.Debugf("Running package manager guard with args: %v", args)
|
||||
|
||||
if g.config.InsecureInstallation {
|
||||
log.Debugf("Bypassing block for unconfirmed malicious packages due to PMG_INSECURE_INSTALLATION")
|
||||
g.showWarning("⚠️ WARNING: INSECURE INSTALLATION MODE - Malware protection bypassed!")
|
||||
return g.continueExecution(ctx, parsedCommand)
|
||||
}
|
||||
|
||||
if !parsedCommand.HasInstallTarget() {
|
||||
// Check if this is a manifest-based installation
|
||||
if parsedCommand.ShouldExtractFromManifest() {
|
||||
@@ -283,6 +294,14 @@ func (g *packageManagerGuard) clearStatus() {
|
||||
g.interaction.ClearStatus()
|
||||
}
|
||||
|
||||
func (g *packageManagerGuard) showWarning(message string) {
|
||||
if g.interaction.ShowWarning == nil {
|
||||
return
|
||||
}
|
||||
|
||||
g.interaction.ShowWarning(message)
|
||||
}
|
||||
|
||||
func (g *packageManagerGuard) handleManifestInstallation(ctx context.Context, parsedCommand *packagemanager.ParsedCommand) error {
|
||||
extractorConfig := extractor.NewDefaultExtractorConfig()
|
||||
extractorConfig.ExtractorPackageManager = extractor.PackageManagerName(g.packageManager.Name())
|
||||
|
||||
+224
-1
@@ -6,6 +6,8 @@ import (
|
||||
|
||||
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
||||
"github.com/safedep/pmg/analyzer"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -16,7 +18,9 @@ func TestGuardConcurrentlyAnalyzePackagesMalwareQueryService(t *testing.T) {
|
||||
}
|
||||
|
||||
pg, err := NewPackageManagerGuard(DefaultPackageManagerGuardConfig(), nil, nil,
|
||||
[]analyzer.PackageVersionAnalyzer{mq}, PackageManagerGuardInteraction{})
|
||||
[]analyzer.PackageVersionAnalyzer{mq}, PackageManagerGuardInteraction{
|
||||
ShowWarning: func(message string) {},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pg: %v", err)
|
||||
}
|
||||
@@ -45,3 +49,222 @@ func TestGuardConcurrentlyAnalyzePackagesMalwareQueryService(t *testing.T) {
|
||||
assert.Equal(t, analyzer.ActionBlock, r[0].Action)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGuardInsecureInstallation(t *testing.T) {
|
||||
mq, err := analyzer.NewMalysisQueryAnalyzer(analyzer.MalysisQueryAnalyzerConfig{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create mq: %v", err)
|
||||
}
|
||||
|
||||
t.Run("should bypass malware blocking when InsecureInstallation is enabled", func(t *testing.T) {
|
||||
// Create guard with InsecureInstallation enabled
|
||||
config := DefaultPackageManagerGuardConfig()
|
||||
config.InsecureInstallation = true
|
||||
config.DryRun = true // Enable dry run to avoid actual command execution
|
||||
config.ResolveDependencies = false // Disable dependency resolution to avoid nil pointer issues
|
||||
|
||||
blockCalled := false
|
||||
warningCalled := false
|
||||
var warningMessage string
|
||||
|
||||
interaction := PackageManagerGuardInteraction{
|
||||
ShowWarning: func(message string) {
|
||||
warningCalled = true
|
||||
warningMessage = message
|
||||
},
|
||||
Block: func(config *ui.BlockConfig) error {
|
||||
blockCalled = true
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
pg, err := NewPackageManagerGuard(config, nil, nil,
|
||||
[]analyzer.PackageVersionAnalyzer{mq}, interaction)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pg: %v", err)
|
||||
}
|
||||
|
||||
// Create a parsed command with a known malicious package
|
||||
parsedCommand := &packagemanager.ParsedCommand{
|
||||
Command: packagemanager.Command{
|
||||
Exe: "npm",
|
||||
Args: []string{"install", "nyc-config@10.0.0"},
|
||||
},
|
||||
InstallTargets: []*packagemanager.PackageInstallTarget{
|
||||
{
|
||||
PackageVersion: &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
Name: "nyc-config",
|
||||
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
|
||||
},
|
||||
Version: "10.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = pg.Run(context.Background(), []string{"npm", "install", "nyc-config@10.0.0"}, parsedCommand)
|
||||
|
||||
// With dry run enabled, we expect no error even though we're bypassing execution
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Block should not be called because InsecureInstallation bypasses the analysis
|
||||
assert.False(t, blockCalled, "Block should not be called when InsecureInstallation is enabled")
|
||||
|
||||
// Warning should be called to inform user about insecure installation
|
||||
assert.True(t, warningCalled, "Warning should be called when InsecureInstallation is enabled")
|
||||
assert.Contains(t, warningMessage, "INSECURE INSTALLATION MODE", "Warning message should mention insecure installation")
|
||||
})
|
||||
|
||||
t.Run("should block malware when InsecureInstallation is disabled", func(t *testing.T) {
|
||||
// Create guard with InsecureInstallation disabled (default)
|
||||
config := DefaultPackageManagerGuardConfig()
|
||||
config.InsecureInstallation = false
|
||||
config.DryRun = true
|
||||
config.ResolveDependencies = false // Disable dependency resolution to avoid nil pointer issues
|
||||
|
||||
blockCalled := false
|
||||
var blockedPackages []*analyzer.PackageVersionAnalysisResult
|
||||
|
||||
interaction := PackageManagerGuardInteraction{
|
||||
ShowWarning: func(message string) {},
|
||||
Block: func(config *ui.BlockConfig) error {
|
||||
blockCalled = true
|
||||
blockedPackages = config.MalwarePackages
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
pg, err := NewPackageManagerGuard(config, nil, nil,
|
||||
[]analyzer.PackageVersionAnalyzer{mq}, interaction)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pg: %v", err)
|
||||
}
|
||||
|
||||
// Create a parsed command with a known malicious package
|
||||
parsedCommand := &packagemanager.ParsedCommand{
|
||||
Command: packagemanager.Command{
|
||||
Exe: "npm",
|
||||
Args: []string{"install", "nyc-config@10.0.0"},
|
||||
},
|
||||
InstallTargets: []*packagemanager.PackageInstallTarget{
|
||||
{
|
||||
PackageVersion: &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
Name: "nyc-config",
|
||||
Ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM,
|
||||
},
|
||||
Version: "10.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = pg.Run(context.Background(), []string{"npm", "install", "nyc-config@10.0.0"}, parsedCommand)
|
||||
|
||||
// We expect no error from the guard itself (blocking is handled via the Block callback)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Block should be called because InsecureInstallation is disabled
|
||||
assert.True(t, blockCalled, "Block should be called when InsecureInstallation is disabled")
|
||||
|
||||
// Verify that the malicious package was detected and blocked
|
||||
assert.NotEmpty(t, blockedPackages, "Blocked packages should not be empty")
|
||||
if len(blockedPackages) > 0 {
|
||||
assert.Equal(t, "nyc-config", blockedPackages[0].PackageVersion.GetPackage().GetName())
|
||||
assert.Equal(t, "10.0.0", blockedPackages[0].PackageVersion.GetVersion())
|
||||
assert.Equal(t, analyzer.ActionBlock, blockedPackages[0].Action)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should continue execution for commands without install targets when InsecureInstallation is enabled", func(t *testing.T) {
|
||||
// Create guard with InsecureInstallation enabled
|
||||
config := DefaultPackageManagerGuardConfig()
|
||||
config.InsecureInstallation = true
|
||||
config.DryRun = true
|
||||
config.ResolveDependencies = false // Disable dependency resolution to avoid nil pointer issues
|
||||
|
||||
blockCalled := false
|
||||
|
||||
interaction := PackageManagerGuardInteraction{
|
||||
ShowWarning: func(message string) {},
|
||||
Block: func(config *ui.BlockConfig) error {
|
||||
blockCalled = true
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
pg, err := NewPackageManagerGuard(config, nil, nil,
|
||||
[]analyzer.PackageVersionAnalyzer{mq}, interaction)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pg: %v", err)
|
||||
}
|
||||
|
||||
// Create a parsed command without install targets (e.g., npm list)
|
||||
parsedCommand := &packagemanager.ParsedCommand{
|
||||
Command: packagemanager.Command{
|
||||
Exe: "npm",
|
||||
Args: []string{"list"},
|
||||
},
|
||||
InstallTargets: []*packagemanager.PackageInstallTarget{}, // No install targets
|
||||
}
|
||||
|
||||
err = pg.Run(context.Background(), []string{"npm", "list"}, parsedCommand)
|
||||
|
||||
// Should not error since there are no install targets to analyze
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Block should not be called since there are no packages to analyze
|
||||
assert.False(t, blockCalled, "Block should not be called when there are no install targets")
|
||||
})
|
||||
|
||||
t.Run("should handle manifest-based installation when InsecureInstallation is enabled", func(t *testing.T) {
|
||||
// Create guard with InsecureInstallation enabled
|
||||
config := DefaultPackageManagerGuardConfig()
|
||||
config.InsecureInstallation = true
|
||||
config.DryRun = true
|
||||
config.ResolveDependencies = false // Disable dependency resolution to avoid nil pointer issues
|
||||
|
||||
blockCalled := false
|
||||
|
||||
interaction := PackageManagerGuardInteraction{
|
||||
ShowWarning: func(message string) {},
|
||||
Block: func(config *ui.BlockConfig) error {
|
||||
blockCalled = true
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
pg, err := NewPackageManagerGuard(config, nil, nil,
|
||||
[]analyzer.PackageVersionAnalyzer{mq}, interaction)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pg: %v", err)
|
||||
}
|
||||
|
||||
// Create a parsed command for manifest-based installation
|
||||
parsedCommand := &packagemanager.ParsedCommand{
|
||||
Command: packagemanager.Command{
|
||||
Exe: "npm",
|
||||
Args: []string{"install"},
|
||||
},
|
||||
InstallTargets: []*packagemanager.PackageInstallTarget{}, // No direct install targets
|
||||
IsManifestInstall: true,
|
||||
ManifestFiles: []string{"package.json"},
|
||||
}
|
||||
|
||||
err = pg.Run(context.Background(), []string{"npm", "install"}, parsedCommand)
|
||||
|
||||
// Should not error and should bypass malware checking
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Block should not be called because InsecureInstallation bypasses analysis
|
||||
assert.False(t, blockCalled, "Block should not be called when InsecureInstallation is enabled for manifest installation")
|
||||
})
|
||||
|
||||
t.Run("should verify InsecureInstallation defaults to false", func(t *testing.T) {
|
||||
config := DefaultPackageManagerGuardConfig()
|
||||
|
||||
// Verify that InsecureInstallation defaults to false
|
||||
assert.False(t, config.InsecureInstallation, "InsecureInstallation should default to false")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user