diff --git a/config/config.go b/config/config.go index 776bec4..05c299a 100644 --- a/config/config.go +++ b/config/config.go @@ -91,14 +91,10 @@ type CloudConfig struct { EndpointID string `mapstructure:"endpoint_id"` } -type ProxyPolicy struct { - SkipCommands []string `mapstructure:"skip_commands"` -} - type ProxyConfig struct { - Enabled bool `mapstructure:"enabled"` - InstallOnly bool `mapstructure:"install_only"` - Policies map[string]ProxyPolicy `mapstructure:"policies"` + Enabled bool `mapstructure:"enabled"` + InstallOnly bool `mapstructure:"install_only"` + SkipCommands map[string][]string `mapstructure:"skip_commands"` } // SandboxConfig configures the sandbox system for isolating package manager processes. @@ -268,7 +264,7 @@ func DefaultConfig() RuntimeConfig { Proxy: ProxyConfig{ Enabled: true, InstallOnly: false, - Policies: map[string]ProxyPolicy{}, + SkipCommands: map[string][]string{}, }, }, DryRun: false, diff --git a/config/config.template.yml b/config/config.template.yml index edec8fe..da19fc9 100644 --- a/config/config.template.yml +++ b/config/config.template.yml @@ -44,15 +44,12 @@ proxy: # (e.g., npm ls, pip list) bypass the proxy and execute directly. install_only: false - # Per-package-manager proxy policies. - # Additional commands to bypass the proxy. + # Per-package-manager commands to skip proxying (only applies when install_only is true). # Example: - # policies: - # pip: - # skip_commands: ["list", "show"] - policies: - npm: - skip_commands: [] + # skip_commands: + # pip: ["list", "show"] + skip_commands: + npm: [] # Trusted packages are packages that are trusted by the user and will be ignored by the security guardrails. # This is useful for packages that are known to be safe and are used in the application. diff --git a/config/config_test.go b/config/config_test.go index 1551b92..a94f508 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -325,7 +325,7 @@ func TestProxyConfigSection(t *testing.T) { cfg := Get() assert.Equal(t, true, cfg.Config.Proxy.Enabled) assert.Equal(t, false, cfg.Config.Proxy.InstallOnly) - assert.NotNil(t, cfg.Config.Proxy.Policies) + assert.NotNil(t, cfg.Config.Proxy.SkipCommands) }) t.Run("reads proxy section from config file", func(t *testing.T) { @@ -335,9 +335,8 @@ func TestProxyConfigSection(t *testing.T) { configYAML := `proxy: enabled: true install_only: true - policies: - npm: - skip_commands: ["my-script", "dev"] + skip_commands: + npm: ["my-script", "dev"] ` configPath := filepath.Join(tmpDir, "config.yml") err := os.WriteFile(configPath, []byte(configYAML), 0o644) @@ -348,7 +347,7 @@ func TestProxyConfigSection(t *testing.T) { assert.Equal(t, true, cfg.Config.Proxy.Enabled) assert.Equal(t, true, cfg.Config.Proxy.InstallOnly) - assert.Equal(t, []string{"my-script", "dev"}, cfg.Config.Proxy.Policies["npm"].SkipCommands) + assert.Equal(t, []string{"my-script", "dev"}, cfg.Config.Proxy.SkipCommands["npm"]) }) t.Run("falls back to legacy keys from config file", func(t *testing.T) { diff --git a/docs/proxy-mode.md b/docs/proxy-mode.md index fd91ac6..be93b7b 100644 --- a/docs/proxy-mode.md +++ b/docs/proxy-mode.md @@ -1,6 +1,6 @@ # Proxy Mode -PMG supports proxy based interception as an alternative to the current optimistic dependency resolution. When enabled via `--proxy-mode` flag: +PMG supports proxy based interception as an alternative to the current optimistic dependency resolution. When enabled: - PMG starts a micro-proxy server on a random localhost port - Runs `npm` and other supported package managers configured to use the proxy @@ -10,17 +10,51 @@ PMG supports proxy based interception as an alternative to the current optimisti ## Usage ```bash -pmg --proxy-mode npm install lodash +pmg npm install lodash ``` ## Configuration -To permanently enable proxy mode, add the following to your `config.yml` file: +Proxy behavior is configured under the `proxy:` section in `config.yml`: ```yaml -proxy_mode: true +proxy: + enabled: true ``` +| Key | Default | Description | +|---|---|---| +| `enabled` | `true` | Enable proxy-based interception. When `false`, PMG falls back to guard-based analysis. | +| `install_only` | `false` | When `true`, only install commands are proxied. Other commands (e.g., `npm ls`, `pip list`) bypass the proxy and execute directly. | +| `skip_commands` | `{}` | Per-package-manager commands to bypass the proxy. Only applies when `install_only` is `true`. | + +### Per-package-manager skip commands + +The `skip_commands` map lets you define additional commands that should bypass the proxy for specific package managers. This only takes effect when `install_only` is `true`: + +```yaml +proxy: + install_only: true + skip_commands: + npm: ["dev", "my-script"] + pip: ["list", "show"] +``` + +Commands in `skip_commands` are matched against the first non-flag argument. For example, `npm dev` would match `dev`, but `npm install dev` would not since `install` is the first non-flag argument. + +### CLI flags + +Use `--proxy-mode` to override `proxy.enabled` at runtime. + +### Environment variables + +| Variable | Description | +|---|---| +| `PMG_PROXY_ENABLED` | Override `proxy.enabled` | +| `PMG_PROXY_INSTALL_ONLY` | Override `proxy.install_only` | + +Legacy variables `PMG_PROXY_MODE` and `PMG_PROXY_INSTALL_ONLY` (for the old flat config keys) are still supported when the `proxy:` section does not exist in the config file. + ## Supported Package Managers | Package Manager | Status | diff --git a/docs/proxy.md b/docs/proxy.md index bf525bf..6aa7416 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -3,55 +3,6 @@ A generic, extensible HTTP/HTTPS proxy server with man-in-the-middle (MITM) capabilities for intercepting and analyzing package manager traffic. Built with [goproxy](https://github.com/elazarl/goproxy) library. -## Configuration - -Proxy behavior is configured under the `proxy:` section in `config.yml`: - -```yaml -proxy: - enabled: true - install_only: false - policies: - npm: - skip_commands: ["my-script"] -``` - -| Key | Default | Description | -|---|---|---| -| `enabled` | `true` | Enable proxy-based interception. When `false`, PMG falls back to guard-based analysis. | -| `install_only` | `false` | When `true`, only install commands are proxied. Other commands (e.g., `npm ls`, `pip list`) bypass the proxy and execute directly. | -| `policies` | `{}` | Per-package-manager policies. Each entry maps a package manager name to a policy with `skip_commands`. | - -### Per-package-manager skip commands - -The `policies` section lets you define additional commands that should bypass the proxy for specific package managers: - -```yaml -proxy: - policies: - npm: - skip_commands: ["dev", "my-script"] - pip: - skip_commands: ["list", "show"] -``` - -Commands in `skip_commands` are matched against the first non-flag argument. For example, `npm dev` would match `dev`, but `npm install dev` would not since `install` is the first non-flag argument. - -### CLI flags - -| Flag | Description | -|---|---| -| `--proxy-mode` | Override `proxy.enabled` | - -### Environment variables - -| Variable | Description | -|---|---| -| `PMG_PROXY_ENABLED` | Override `proxy.enabled` | -| `PMG_PROXY_INSTALL_ONLY` | Override `proxy.install_only` | - -Legacy variables `PMG_PROXY_MODE` and `PMG_PROXY_INSTALL_ONLY` (for the old flat config keys) are still supported when the `proxy:` section does not exist in the config file. - ## Features - Selective interception of HTTPS traffic diff --git a/internal/flows/proxy_flow.go b/internal/flows/proxy_flow.go index 5c705f8..237b777 100644 --- a/internal/flows/proxy_flow.go +++ b/internal/flows/proxy_flow.go @@ -52,18 +52,20 @@ func (f *proxyFlow) Run(ctx context.Context, args []string, parsedCmd *packagema cfg := config.Get() - // Skip proxy for commands that don't download packages when install_only is enabled - if cfg.Config.Proxy.InstallOnly && !parsedCmd.MayDownloadPackages() { - log.Debugf("Skipping proxy for non-download command (install_only=true)") - return runner.Execute(ctx, parsedCmd, f.pm.Name(), cfg.DryRun) - } - - // Skip proxy for user-defined skip commands - if policy, ok := cfg.Config.Proxy.Policies[f.pm.Name()]; ok && len(policy.SkipCommands) > 0 { - if packagemanager.IsFirstNonFlagArgInList(parsedCmd.Command.Args, policy.SkipCommands) { - log.Debugf("Skipping proxy for user-defined skip command") + // When install_only is enabled, skip proxy for known non-download commands + // and user-defined skip commands + if cfg.Config.Proxy.InstallOnly { + if !parsedCmd.MayDownloadPackages() { + log.Debugf("Skipping proxy for non-download command (install_only=true)") return runner.Execute(ctx, parsedCmd, f.pm.Name(), cfg.DryRun) } + + if cmds, ok := cfg.Config.Proxy.SkipCommands[f.pm.Name()]; ok && len(cmds) > 0 { + if packagemanager.IsFirstNonFlagArgInList(parsedCmd.Command.Args, cmds) { + log.Debugf("Skipping proxy for user-defined skip command (install_only=true)") + return runner.Execute(ctx, parsedCmd, f.pm.Name(), cfg.DryRun) + } + } } // Initialize report data at the start