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:
Sahil Bansal
2026-01-19 22:24:59 +05:30
committed by GitHub
parent cc2dd993ed
commit 736c63a7b6
7 changed files with 44 additions and 11 deletions
+2 -2
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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()
+7 -2
View File
@@ -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
+10 -6
View File
@@ -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)
}
+3
View File
@@ -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
+5
View File
@@ -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
}