From 1d9045d77000ef92c7e26583edd77d50a95c681d Mon Sep 17 00:00:00 2001 From: Sahil Bansal Date: Tue, 28 Apr 2026 17:49:58 +0530 Subject: [PATCH] fix: Enforce sandbox for update commands that may download packages (#220) (#229) 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 --- config/config.go | 6 ++-- config/config_test.go | 58 +++++++++++++++++++++++++++++++++++ internal/flows/common_flow.go | 2 +- internal/flows/proxy_flow.go | 2 +- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index 1e39d6e..f29a746 100644 --- a/config/config.go +++ b/config/config.go @@ -379,11 +379,11 @@ func Get() *RuntimeConfig { return globalConfig } -func ConfigureSandbox(isInstallationCommand bool) { +func ConfigureSandbox(mayDownloadPackages 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 + // commands that may download packages (install, update, etc.) + globalConfig.Config.Sandbox.Enabled = globalConfig.Config.Sandbox.EnforceAlways || mayDownloadPackages } } diff --git a/config/config_test.go b/config/config_test.go index 891a22f..28e0819 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -221,6 +221,64 @@ func TestConfigPrecedence(t *testing.T) { }) } +func TestConfigureSandbox(t *testing.T) { + tests := []struct { + name string + sandboxEnabled bool + enforceAlways bool + mayDownloadPackages bool + expectedSandboxState bool + }{ + { + name: "sandbox disabled stays disabled regardless of command", + sandboxEnabled: false, + mayDownloadPackages: true, + expectedSandboxState: false, + }, + { + name: "sandbox enabled with download command stays enabled", + sandboxEnabled: true, + mayDownloadPackages: true, + expectedSandboxState: true, + }, + { + name: "sandbox enabled with non-download command gets disabled", + sandboxEnabled: true, + mayDownloadPackages: false, + expectedSandboxState: false, + }, + { + name: "enforce_always keeps sandbox enabled for non-download command", + sandboxEnabled: true, + enforceAlways: true, + mayDownloadPackages: false, + expectedSandboxState: true, + }, + { + name: "enforce_always with download command stays enabled", + sandboxEnabled: true, + enforceAlways: true, + mayDownloadPackages: true, + expectedSandboxState: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Setenv("PMG_CONFIG_DIR", "/tmp/pmg-test/random-does-not-exist") + initConfig() + + cfg := Get() + cfg.Config.Sandbox.Enabled = tc.sandboxEnabled + cfg.Config.Sandbox.EnforceAlways = tc.enforceAlways + + ConfigureSandbox(tc.mayDownloadPackages) + + assert.Equal(t, tc.expectedSandboxState, cfg.Config.Sandbox.Enabled) + }) + } +} + func TestWriteTemplateConfigMergesExistingConfig(t *testing.T) { tmpDir := t.TempDir() t.Setenv("PMG_CONFIG_DIR", tmpDir) diff --git a/internal/flows/common_flow.go b/internal/flows/common_flow.go index df2f702..9dc413d 100644 --- a/internal/flows/common_flow.go +++ b/internal/flows/common_flow.go @@ -34,7 +34,7 @@ func (f *commonFlow) Run(ctx context.Context, args []string, parsedCmd *packagem var analyzers []analyzer.PackageVersionAnalyzer // Configure sandbox based on command type and enforcement policy - config.ConfigureSandbox(parsedCmd.IsInstallationCommand()) + config.ConfigureSandbox(parsedCmd.IsInstallationCommand() || parsedCmd.MayDownloadPackages()) cfg := config.Get() diff --git a/internal/flows/proxy_flow.go b/internal/flows/proxy_flow.go index bca5f02..6f7b512 100644 --- a/internal/flows/proxy_flow.go +++ b/internal/flows/proxy_flow.go @@ -48,7 +48,7 @@ func (f *proxyFlow) Run(ctx context.Context, args []string, parsedCmd *packagema } // Configure sandbox based on command type and enforcement policy - config.ConfigureSandbox(parsedCmd.IsInstallationCommand()) + config.ConfigureSandbox(parsedCmd.IsInstallationCommand() || parsedCmd.MayDownloadPackages()) cfg := config.Get()