mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
Sandbox fails for unsupported cmds (#129)
* introduce enforce_always sandbox config & hooks for flows * fix sandbox failing for unsupported commands * add hooks for pypi package managers * add tests for sandbox hook * introduce enforce_always flag for ease use & CI * make comments descriptive * remove hooks & update config to add API to configure sandbox * add comments * rm unused function
This commit is contained in:
@@ -442,7 +442,7 @@ jobs:
|
|||||||
touch ~/.ssh/id_rsa
|
touch ~/.ssh/id_rsa
|
||||||
|
|
||||||
- name: Run Sandbox E2E Test
|
- name: Run Sandbox E2E Test
|
||||||
run: pmg --sandbox npm exec -- node test/sandbox-e2e.js
|
run: pmg --sandbox --sandbox-enforce npm exec -- node test/sandbox-e2e.js
|
||||||
|
|
||||||
sandbox-e2e-linux:
|
sandbox-e2e-linux:
|
||||||
name: Sandbox E2E - Linux (Bubblewrap)
|
name: Sandbox E2E - Linux (Bubblewrap)
|
||||||
@@ -500,4 +500,4 @@ jobs:
|
|||||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||||
|
|
||||||
- name: Run Sandbox E2E Test
|
- name: Run Sandbox E2E Test
|
||||||
run: pmg --sandbox --sandbox-profile npm-restrictive npm exec -- node test/sandbox-e2e.js
|
run: pmg --sandbox --sandbox-enforce --sandbox-profile npm-restrictive npm exec -- node test/sandbox-e2e.js
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ func ApplyCobraFlags(cmd *cobra.Command) {
|
|||||||
globalConfig.Config.ExperimentalProxyMode, "Use experimental proxy-based interception (EXPERIMENTAL)")
|
globalConfig.Config.ExperimentalProxyMode, "Use experimental proxy-based interception (EXPERIMENTAL)")
|
||||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Sandbox.Enabled, "sandbox",
|
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Sandbox.Enabled, "sandbox",
|
||||||
globalConfig.Config.Sandbox.Enabled, "Enable sandbox mode to isolate package manager processes (EXPERIMENTAL)")
|
globalConfig.Config.Sandbox.Enabled, "Enable sandbox mode to isolate package manager processes (EXPERIMENTAL)")
|
||||||
|
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Sandbox.EnforceAlways, "sandbox-enforce",
|
||||||
|
globalConfig.Config.Sandbox.EnforceAlways, "Apply sandbox to all commands, not just install commands (requires --sandbox)")
|
||||||
cmd.PersistentFlags().StringVar(&globalConfig.SandboxProfileOverride, "sandbox-profile",
|
cmd.PersistentFlags().StringVar(&globalConfig.SandboxProfileOverride, "sandbox-profile",
|
||||||
globalConfig.SandboxProfileOverride, "Override sandbox policy profile (built-in name or path to custom YAML)")
|
globalConfig.SandboxProfileOverride, "Override sandbox policy profile (built-in name or path to custom YAML)")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ type SandboxConfig struct {
|
|||||||
// Enabled enables sandbox mode (opt-in by default for backward compatibility).
|
// Enabled enables sandbox mode (opt-in by default for backward compatibility).
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
|
||||||
|
// EnforceAlways controls scope of sandbox enforcement:
|
||||||
|
// - When true: sandbox applies to all package manager commands
|
||||||
|
// - When false: sandbox only applies to install commands, others run unrestricted (default)
|
||||||
|
EnforceAlways bool `mapstructure:"enforce_always"`
|
||||||
|
|
||||||
// Policies maps package manager names to their sandbox policy references.
|
// Policies maps package manager names to their sandbox policy references.
|
||||||
// Key is package manager name (e.g., "npm", "pip"), value is policy reference.
|
// Key is package manager name (e.g., "npm", "pip"), value is policy reference.
|
||||||
Policies map[string]SandboxPolicyRef `mapstructure:"policies"`
|
Policies map[string]SandboxPolicyRef `mapstructure:"policies"`
|
||||||
@@ -169,6 +174,7 @@ func DefaultConfig() RuntimeConfig {
|
|||||||
TrustedPackages: []TrustedPackage{},
|
TrustedPackages: []TrustedPackage{},
|
||||||
Sandbox: SandboxConfig{
|
Sandbox: SandboxConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
|
EnforceAlways: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DryRun: false,
|
DryRun: false,
|
||||||
@@ -284,6 +290,14 @@ func Get() *RuntimeConfig {
|
|||||||
return globalConfig
|
return globalConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConfigureSandbox(isInstallationCommand bool) {
|
||||||
|
if globalConfig.Config.Sandbox.Enabled {
|
||||||
|
// Apply sandbox to all commands if EnforceAlways=true, otherwise only to
|
||||||
|
// installation commands else disable the sandbox
|
||||||
|
globalConfig.Config.Sandbox.Enabled = globalConfig.Config.Sandbox.EnforceAlways || isInstallationCommand
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WriteTemplateConfig writes the template configuration file to disk if it doesn't already exist.
|
// WriteTemplateConfig writes the template configuration file to disk if it doesn't already exist.
|
||||||
func WriteTemplateConfig() error {
|
func WriteTemplateConfig() error {
|
||||||
configDir, err := configDir()
|
configDir, err := configDir()
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ sandbox:
|
|||||||
# Enable sandbox mode (opt-in, default: false for backward compatibility)
|
# Enable sandbox mode (opt-in, default: false for backward compatibility)
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
# Controls scope of sandbox enforcement:
|
||||||
|
# - true: sandbox applies to all package manager commands
|
||||||
|
# - false (default): sandbox only applies to install commands, others run unrestricted
|
||||||
|
# Requires 'enabled: true' and per-PM policies to be active. May break workflows expecting unrestricted commands.
|
||||||
|
enforce_always: false
|
||||||
|
|
||||||
# Policy templates define policy profiles by name and path.
|
# Policy templates define policy profiles by name and path.
|
||||||
# They can be used to override a built-in profile or create a custom profile.
|
# They can be used to override a built-in profile or create a custom profile.
|
||||||
# Note: Custom profiles loaded via policy_templates can inherit from built-in
|
# Note: Custom profiles loaded via policy_templates can inherit from built-in
|
||||||
@@ -125,4 +131,3 @@ sandbox:
|
|||||||
uv:
|
uv:
|
||||||
enabled: true
|
enabled: true
|
||||||
profile: pypi-restrictive
|
profile: pypi-restrictive
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,13 @@ func Common(pm packagemanager.PackageManager, pkgResolver packagemanager.Package
|
|||||||
|
|
||||||
func (f *commonFlow) Run(ctx context.Context, args []string, parsedCmd *packagemanager.ParsedCommand) error {
|
func (f *commonFlow) Run(ctx context.Context, args []string, parsedCmd *packagemanager.ParsedCommand) error {
|
||||||
var analyzers []analyzer.PackageVersionAnalyzer
|
var analyzers []analyzer.PackageVersionAnalyzer
|
||||||
config := config.Get()
|
|
||||||
|
|
||||||
if config.Config.Paranoid {
|
// Configure sandbox based on command type and enforcement policy
|
||||||
|
config.ConfigureSandbox(parsedCmd.IsInstallationCommand())
|
||||||
|
|
||||||
|
cfg := config.Get()
|
||||||
|
|
||||||
|
if cfg.Config.Paranoid {
|
||||||
malysisActiveScanAnalyzer, err := analyzer.NewMalysisActiveScanAnalyzer(analyzer.DefaultMalysisActiveScanAnalyzerConfig())
|
malysisActiveScanAnalyzer, err := analyzer.NewMalysisActiveScanAnalyzer(analyzer.DefaultMalysisActiveScanAnalyzerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create malware analyzer: %s", err)
|
return fmt.Errorf("failed to create malware analyzer: %s", err)
|
||||||
@@ -55,15 +59,15 @@ func (f *commonFlow) Run(ctx context.Context, args []string, parsedCmd *packagem
|
|||||||
}
|
}
|
||||||
|
|
||||||
guardConfig := guard.DefaultPackageManagerGuardConfig()
|
guardConfig := guard.DefaultPackageManagerGuardConfig()
|
||||||
guardConfig.DryRun = config.DryRun
|
guardConfig.DryRun = cfg.DryRun
|
||||||
guardConfig.InsecureInstallation = config.InsecureInstallation
|
guardConfig.InsecureInstallation = cfg.InsecureInstallation
|
||||||
|
|
||||||
proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction)
|
guardManager, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create package manager guard: %s", err)
|
return fmt.Errorf("failed to create package manager guard: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = proxy.Run(ctx, args, parsedCmd)
|
err = guardManager.Run(ctx, args, parsedCmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run package manager guard: %w", err)
|
return fmt.Errorf("failed to run package manager guard: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ func (f *proxyFlow) Run(ctx context.Context, args []string, parsedCmd *packagema
|
|||||||
return fmt.Errorf("proxy mode is not supported for %s", ecosystem.String())
|
return fmt.Errorf("proxy mode is not supported for %s", ecosystem.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure sandbox based on command type and enforcement policy
|
||||||
|
config.ConfigureSandbox(parsedCmd.IsInstallationCommand())
|
||||||
|
|
||||||
cfg := config.Get()
|
cfg := config.Get()
|
||||||
|
|
||||||
// Check if dry-run mode is enabled
|
// Check if dry-run mode is enabled
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ type ParsedCommand struct {
|
|||||||
ManifestFiles []string
|
ManifestFiles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsInstallationCommand returns true if command installs packages (explicit targets or from manifest).
|
||||||
|
func (pc *ParsedCommand) IsInstallationCommand() bool {
|
||||||
|
return pc.HasInstallTarget() || pc.HasManifestInstall()
|
||||||
|
}
|
||||||
|
|
||||||
func (pc *ParsedCommand) HasInstallTarget() bool {
|
func (pc *ParsedCommand) HasInstallTarget() bool {
|
||||||
return len(pc.InstallTargets) > 0
|
return len(pc.InstallTargets) > 0
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user