mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
ConfigureSandbox was only triggered by IsInstallationCommand(), missing update commands (npm update, pnpm update, etc.) that pull new versions and run postinstall scripts. Use MayDownloadPackages() as the sandbox signal so all package-downloading commands are sandboxed. Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
138 lines
4.5 KiB
Go
138 lines
4.5 KiB
Go
package flows
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/safedep/pmg/analyzer"
|
|
"github.com/safedep/pmg/config"
|
|
"github.com/safedep/pmg/guard"
|
|
"github.com/safedep/pmg/internal/audit"
|
|
"github.com/safedep/pmg/internal/runner"
|
|
"github.com/safedep/pmg/internal/ui"
|
|
"github.com/safedep/pmg/packagemanager"
|
|
)
|
|
|
|
type commonFlow struct {
|
|
pm packagemanager.PackageManager
|
|
packageResolver packagemanager.PackageResolver
|
|
}
|
|
|
|
// Creates a common flow of execution for all package managers. This should work for most
|
|
// of the cases unless a package manager has its own unique requirements. Configuration
|
|
// should be passed through the context (Global Config)
|
|
func Common(pm packagemanager.PackageManager, pkgResolver packagemanager.PackageResolver) *commonFlow {
|
|
return &commonFlow{
|
|
pm: pm,
|
|
packageResolver: pkgResolver,
|
|
}
|
|
}
|
|
|
|
func (f *commonFlow) Run(ctx context.Context, args []string, parsedCmd *packagemanager.ParsedCommand) error {
|
|
var analyzers []analyzer.PackageVersionAnalyzer
|
|
|
|
// Configure sandbox based on command type and enforcement policy
|
|
config.ConfigureSandbox(parsedCmd.IsInstallationCommand() || parsedCmd.MayDownloadPackages())
|
|
|
|
cfg := config.Get()
|
|
|
|
// Initialize report data at the start
|
|
reportData := ui.NewReportData()
|
|
reportData.PackageManagerName = f.pm.Name()
|
|
reportData.FlowType = ui.FlowTypeGuard
|
|
reportData.DryRun = cfg.DryRun
|
|
reportData.InsecureMode = cfg.InsecureInstallation
|
|
reportData.TransitiveEnabled = cfg.Config.Transitive
|
|
reportData.ParanoidMode = cfg.Config.Paranoid
|
|
reportData.SandboxEnabled = cfg.Config.Sandbox.Enabled
|
|
|
|
if cfg.Config.Sandbox.Enabled {
|
|
if policyRef, exists := cfg.Config.Sandbox.Policies[f.pm.Name()]; exists {
|
|
reportData.SandboxProfile = policyRef.Profile
|
|
}
|
|
}
|
|
if cfg.SandboxProfileOverride != "" {
|
|
reportData.SandboxProfile = cfg.SandboxProfileOverride
|
|
}
|
|
|
|
startTime := time.Now()
|
|
|
|
malysisQueryAnalyzer, err := analyzer.NewMalysisQueryAnalyzer(analyzer.MalysisQueryAnalyzerConfig{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create malware analyzer: %w", err)
|
|
}
|
|
|
|
analyzers = append(analyzers, malysisQueryAnalyzer)
|
|
|
|
interaction := guard.PackageManagerGuardInteraction{
|
|
SetStatus: ui.SetStatus,
|
|
ClearStatus: ui.ClearStatus,
|
|
ShowWarning: ui.ShowWarning,
|
|
GetConfirmationOnMalware: ui.GetConfirmationOnMalware,
|
|
Block: ui.BlockNoExit,
|
|
}
|
|
|
|
guardConfig := guard.DefaultPackageManagerGuardConfig()
|
|
guardConfig.DryRun = cfg.DryRun
|
|
guardConfig.InsecureInstallation = cfg.InsecureInstallation
|
|
|
|
pmName := f.pm.Name()
|
|
executor := func(ctx context.Context, pc *packagemanager.ParsedCommand) error {
|
|
return runner.Execute(ctx, pc, pmName, cfg.DryRun)
|
|
}
|
|
|
|
guardManager, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction, executor)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create package manager guard: %s", err)
|
|
}
|
|
|
|
guardResult, err := guardManager.Run(ctx, args, parsedCmd)
|
|
|
|
// Populate report data from guard result
|
|
reportData.StartTime = startTime
|
|
if guardResult != nil {
|
|
reportData.TotalAnalyzed = guardResult.TotalAnalyzed
|
|
reportData.TrustedSkipped = guardResult.TrustedSkipped
|
|
reportData.AllowedCount = guardResult.AllowedCount
|
|
reportData.ConfirmedCount = guardResult.ConfirmedCount
|
|
reportData.BlockedCount = guardResult.BlockedCount
|
|
reportData.BlockedPackages = guardResult.BlockedPackages
|
|
reportData.ConfirmedPackages = guardResult.ConfirmedPackages
|
|
}
|
|
|
|
// Infer outcome from data and config using shared inference logic
|
|
blockedCount := 0
|
|
userCancelledCount := 0
|
|
|
|
if guardResult != nil {
|
|
blockedCount = guardResult.BlockedCount
|
|
// In guard flow, if user cancelled, all blocked packages are due to user cancellation
|
|
// (guard returns immediately on ActionBlock, so we can't have both types)
|
|
if guardResult.WasUserCancelled {
|
|
userCancelledCount = guardResult.BlockedCount
|
|
}
|
|
}
|
|
|
|
reportData.Outcome = inferOutcome(cfg.InsecureInstallation, cfg.DryRun, blockedCount, userCancelledCount, err)
|
|
|
|
// Session complete is called here (not deferred) because guard.Run() calls
|
|
// LogInstallStarted internally, and all paths after guard.Run() reach this point.
|
|
audit.LogSessionComplete(audit.Outcome(reportData.Outcome.String()), audit.FlowTypeGuard)
|
|
|
|
// Show the report
|
|
ui.Report(reportData)
|
|
|
|
// Exit after report for blocked/cancelled outcomes
|
|
if reportData.Outcome == ui.OutcomeBlocked || reportData.Outcome == ui.OutcomeUserCancelled {
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to run package manager guard: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|