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
|
||||
|
||||
- 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:
|
||||
name: Sandbox E2E - Linux (Bubblewrap)
|
||||
@@ -500,4 +500,4 @@ jobs:
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
|
||||
- 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)")
|
||||
cmd.PersistentFlags().BoolVar(&globalConfig.Config.Sandbox.Enabled, "sandbox",
|
||||
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",
|
||||
globalConfig.SandboxProfileOverride, "Override sandbox policy profile (built-in name or path to custom YAML)")
|
||||
}
|
||||
|
||||
+15
-1
@@ -70,6 +70,11 @@ type SandboxConfig struct {
|
||||
// Enabled enables sandbox mode (opt-in by default for backward compatibility).
|
||||
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.
|
||||
// Key is package manager name (e.g., "npm", "pip"), value is policy reference.
|
||||
Policies map[string]SandboxPolicyRef `mapstructure:"policies"`
|
||||
@@ -168,7 +173,8 @@ func DefaultConfig() RuntimeConfig {
|
||||
ExperimentalProxyMode: false,
|
||||
TrustedPackages: []TrustedPackage{},
|
||||
Sandbox: SandboxConfig{
|
||||
Enabled: false,
|
||||
Enabled: false,
|
||||
EnforceAlways: false,
|
||||
},
|
||||
},
|
||||
DryRun: false,
|
||||
@@ -284,6 +290,14 @@ func Get() *RuntimeConfig {
|
||||
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.
|
||||
func WriteTemplateConfig() error {
|
||||
configDir, err := configDir()
|
||||
|
||||
@@ -69,6 +69,12 @@ sandbox:
|
||||
# Enable sandbox mode (opt-in, default: false for backward compatibility)
|
||||
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.
|
||||
# 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
|
||||
@@ -87,7 +93,7 @@ sandbox:
|
||||
# npm ecosystem. npm-restrictive is a built-in profile.
|
||||
npm:
|
||||
enabled: true
|
||||
profile: npm-restrictive # Built-in profile, template name, or path to custom YAML
|
||||
profile: npm-restrictive # Built-in profile, template name, or path to custom YAML
|
||||
|
||||
pnpm:
|
||||
enabled: true
|
||||
@@ -125,4 +131,3 @@ sandbox:
|
||||
uv:
|
||||
enabled: true
|
||||
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 {
|
||||
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())
|
||||
if err != nil {
|
||||
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.DryRun = config.DryRun
|
||||
guardConfig.InsecureInstallation = config.InsecureInstallation
|
||||
guardConfig.DryRun = cfg.DryRun
|
||||
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 {
|
||||
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 {
|
||||
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())
|
||||
}
|
||||
|
||||
// Configure sandbox based on command type and enforcement policy
|
||||
config.ConfigureSandbox(parsedCmd.IsInstallationCommand())
|
||||
|
||||
cfg := config.Get()
|
||||
|
||||
// Check if dry-run mode is enabled
|
||||
|
||||
@@ -40,6 +40,11 @@ type ParsedCommand struct {
|
||||
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 {
|
||||
return len(pc.InstallTargets) > 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user