diff --git a/config/config.go b/config/config.go index e88b83b..020bf1a 100644 --- a/config/config.go +++ b/config/config.go @@ -73,6 +73,17 @@ type SandboxConfig struct { // 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"` + + // PolicyTemplates maps template names to their paths. + PolicyTemplates map[string]SandboxPolicyTemplate `mapstructure:"policy_templates"` +} + +// SandboxPolicyTemplate defines a template for a sandbox policy, used to map +// a profile name to a path. +type SandboxPolicyTemplate struct { + // Path is the path to the template file. + // Relative path can be used to reference a template file in the config directory (example: ./npm-restrictive.yml) + Path string `mapstructure:"path"` } // SandboxPolicyRef references a sandbox policy for a specific package manager. @@ -130,6 +141,11 @@ func (r *RuntimeConfig) EventLogDir() string { return r.eventLogDir } +// ConfigDir returns the path to the config directory. +func (r *RuntimeConfig) ConfigDir() string { + return r.configDir +} + // DefaultConfig is a fail safe contract for the runtime configuration. // The config package return an appropriate RuntimeConfig based on the environment and the configuration. func DefaultConfig() RuntimeConfig { diff --git a/config/config.template.yml b/config/config.template.yml index 2c7dd51..b3f3e5d 100644 --- a/config/config.template.yml +++ b/config/config.template.yml @@ -63,13 +63,22 @@ sandbox: # Enable sandbox mode (opt-in, default: false for backward compatibility) enabled: 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. + policy_templates: + # Name for the template. Can be used to override a built-in profile or create a custom profile. + # Path is the path to the template file. + # Relative path can be used to reference a template file in the config directory (example: ./npm-restrictive.yml) + npm-restrictive-override: + path: ./profiles/npm-restrictive.yml + # Per-package-manager sandbox policies # Each package manager can have its own policy to account for unique security characteristics policies: - # npm ecosystem + # npm ecosystem. npm-restrictive is a built-in profile. npm: enabled: true - profile: npm-restrictive # Built-in profile or path to custom YAML + profile: npm-restrictive # Built-in profile, template name, or path to custom YAML pnpm: enabled: true @@ -83,7 +92,7 @@ sandbox: enabled: true profile: npm-restrictive - # PyPI ecosystem + # PyPI ecosystem. pypi-restrictive is a built-in profile. pip: enabled: true profile: pypi-restrictive diff --git a/sandbox/executor/apply.go b/sandbox/executor/apply.go index 453db10..9b60042 100644 --- a/sandbox/executor/apply.go +++ b/sandbox/executor/apply.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os/exec" + "path/filepath" "github.com/safedep/dry/log" "github.com/safedep/pmg/config" @@ -11,14 +12,33 @@ import ( "github.com/safedep/pmg/sandbox/platform" ) +type applySandboxConfig struct { + sb sandbox.Sandbox +} + +type applySandboxOpt func(*applySandboxConfig) + +// WithSandbox sets the sandbox to use for the command. +// When not set, the sandbox will be determined by the platform. +func WithSandbox(sb sandbox.Sandbox) applySandboxOpt { + return func(c *applySandboxConfig) { + c.sb = sb + } +} + // ApplySandbox applies sandbox isolation to the command if sandbox mode is enabled. // This is a helper function used by both guard and proxy flows to avoid code duplication. // // This is a security sensitive operation. If sandbox is enabled via. config but not available on the platform, // it will return an error to avoid running the command without sandbox protection. -func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string) (*sandbox.ExecutionResult, error) { +func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string, opts ...applySandboxOpt) (*sandbox.ExecutionResult, error) { cfg := config.Get() + applyConfig := &applySandboxConfig{} + for _, opt := range opts { + opt(applyConfig) + } + if !cfg.Config.Sandbox.Enabled { return sandbox.NewExecutionResult(), nil } @@ -57,9 +77,27 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string) (*sandbox.E log.Debugf("Loading sandbox policy %s", policyRef.Profile) - policy, err = registry.GetProfile(policyRef.Profile) - if err != nil { - return nil, fmt.Errorf("failed to load sandbox policy %s: %w", policyRef.Profile, err) + // Check if there is a template for the policy and use it if it exists + // This is a way to override a built-in profile or create a custom profile. + if template, exists := cfg.Config.Sandbox.PolicyTemplates[policyRef.Profile]; exists { + if filepath.IsAbs(template.Path) { + policy, err = registry.GetProfile(template.Path) + if err != nil { + return nil, fmt.Errorf("failed to load sandbox policy %s: %w", template.Path, err) + } + } else { + policyPath := filepath.Join(cfg.ConfigDir(), template.Path) + policy, err = registry.GetProfile(policyPath) + if err != nil { + return nil, fmt.Errorf("failed to load sandbox policy %s: %w", policyPath, err) + } + } + } else { + // Load the policy from the registry by name + policy, err = registry.GetProfile(policyRef.Profile) + if err != nil { + return nil, fmt.Errorf("failed to load sandbox policy %s: %w", policyRef.Profile, err) + } } } @@ -69,9 +107,14 @@ func ApplySandbox(ctx context.Context, cmd *exec.Cmd, pmName string) (*sandbox.E return nil, fmt.Errorf("sandbox policy %s does not apply to %s", policy.Name, pmName) } - sb, err := platform.NewSandbox() - if err != nil { - return nil, fmt.Errorf("sandbox not available on this platform: %v", err) + var sb sandbox.Sandbox + if applyConfig.sb != nil { + sb = applyConfig.sb + } else { + sb, err = platform.NewSandbox() + if err != nil { + return nil, fmt.Errorf("sandbox not available on this platform: %v", err) + } } if !sb.IsAvailable() { diff --git a/sandbox/platform/seatbelt_translator_darwin.go b/sandbox/platform/seatbelt_translator_darwin.go index 585fed4..2bdc030 100644 --- a/sandbox/platform/seatbelt_translator_darwin.go +++ b/sandbox/platform/seatbelt_translator_darwin.go @@ -38,7 +38,12 @@ func (t *seatbeltPolicyTranslator) translate(policy *sandbox.SandboxPolicy) (str sb.WriteString("(allow mach-lookup)\n") sb.WriteString("(allow mach-register)\n") sb.WriteString("(allow ipc-posix-shm)\n") - sb.WriteString("(allow signal)\n\n") + sb.WriteString("(allow signal)\n") + sb.WriteString(";; Allow reading file metadata for getcwd() and similar operations\n") + sb.WriteString("(allow file-read-metadata)\n") + sb.WriteString(";; Allow reading system configuration and libraries needed for process execution\n") + sb.WriteString("(allow file-read* (subpath \"/dev\"))\n") + sb.WriteString("(allow file-read* (subpath \"/etc\"))\n\n") // Filesystem rules if err := t.translateFilesystem(policy, &sb); err != nil {