From 5098649c72545dddebf3d7ef92a68dfb9be8a48c Mon Sep 17 00:00:00 2001 From: Abhisek Datta Date: Thu, 15 Jan 2026 08:44:26 +0530 Subject: [PATCH] refactor: Misc cleanup --- README.md | 2 +- sandbox/platform/bubblewrap_config_linux.go | 5 ++++ .../platform/bubblewrap_translator_linux.go | 28 +++++++++++-------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d27b4f5..e5c1305 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ See [example](https://safedep.io/malicious-npm-package-express-cookie-parser/) - Maintains package installation event log for transparency and audit trail - Enforces least privilege and defense in depth using OS native sandboxing -PMG guarantees it's own artifact integrity using GitHub and npm attestations. Users can cryptographically prove that the binary they run +PMG guarantees its own artifact integrity using GitHub and npm attestations. Users can cryptographically prove that the binary they run matches the source code they reviewed, eliminating the risk of tampered or malicious builds. See [why and how to trust PMG](docs/trust.md). ## PMG in Action diff --git a/sandbox/platform/bubblewrap_config_linux.go b/sandbox/platform/bubblewrap_config_linux.go index 477f08b..d773852 100644 --- a/sandbox/platform/bubblewrap_config_linux.go +++ b/sandbox/platform/bubblewrap_config_linux.go @@ -85,6 +85,9 @@ type seccompConfig struct { // These defaults are based on: // - Common Linux filesystem layouts // - Anthropic Sandbox Runtime implementation patterns +// +// This config is for maintaining safe defaults for the sandbox. Future enhancements +// will allow the user to override the config through policy or sandbox config available at PMG level. func newDefaultBubblewrapConfig() *bubblewrapConfig { return &bubblewrapConfig{ // Essential system paths (read-only) @@ -195,6 +198,7 @@ func (c *bubblewrapConfig) getEssentialSystemPaths() []string { existingPaths = append(existingPaths, path) } } + return existingPaths } @@ -207,5 +211,6 @@ func (c *bubblewrapConfig) getEssentialDevices() []string { existingDevices = append(existingDevices, device) } } + return existingDevices } diff --git a/sandbox/platform/bubblewrap_translator_linux.go b/sandbox/platform/bubblewrap_translator_linux.go index 5383388..1facf96 100644 --- a/sandbox/platform/bubblewrap_translator_linux.go +++ b/sandbox/platform/bubblewrap_translator_linux.go @@ -155,6 +155,7 @@ func (t *bubblewrapPolicyTranslator) addIsolationNamespaces(policy *sandbox.Sand // - --ro-bind: Read-only bind mount // - --bind: Read-write bind mount // - --dev-bind: Device file bind mount +// - --tmpfs: Temporary file system mount (used to hide specific files/directories) // - Paths not mounted are inaccessible (deny-by-default) // // Strategy: @@ -168,18 +169,20 @@ func (t *bubblewrapPolicyTranslator) addIsolationNamespaces(policy *sandbox.Sand func (t *bubblewrapPolicyTranslator) translateFilesystem(policy *sandbox.SandboxPolicy) ([]string, error) { args := []string{} - // Track paths we've already bound to avoid duplicates - boundPaths := make(map[string]bool) + // Track paths we've already bound for read and write to avoid duplicates + // Bubblewrap later mounts win, so we need to track both read and write bound paths. + readBoundPaths := make(map[string]bool) + writeBoundPaths := make(map[string]bool) // Add essential system paths to bound paths (already handled separately) for _, path := range t.config.getEssentialSystemPaths() { - boundPaths[path] = true + readBoundPaths[path] = true } // Mark tmpdir as already bound (will be handled by addTmpdirSupport()) // This prevents conflicts from policy patterns like /tmp/** tmpDir := os.TempDir() - boundPaths[tmpDir] = true + writeBoundPaths[tmpDir] = true // 1. Process allow_read rules FIRST (read-only bind mounts) // This establishes the base read-only filesystem view (including "/" if specified) @@ -190,18 +193,19 @@ func (t *bubblewrapPolicyTranslator) translateFilesystem(policy *sandbox.Sandbox continue } - readArgs, err := t.processReadRule(expanded, boundPaths) + // Glob chars are handled by the processReadRule function. + readArgs, err := t.processReadRule(expanded, readBoundPaths) if err != nil { log.Warnf("Failed to process allow_read rule '%s': %v", expanded, err) continue } + args = append(args, readArgs...) } // 2. Process allow_write rules SECOND (read-write bind mounts) // These OVERRIDE earlier read-only binds (bwrap: later mounts win) // Use a separate map so we don't skip paths that need write access - writeBoundPaths := make(map[string]bool) writeBoundPaths[tmpDir] = true // tmpdir handled by addTmpdirSupport for _, pattern := range policy.Filesystem.AllowWrite { expanded, err := util.ExpandVariables(pattern) @@ -210,11 +214,13 @@ func (t *bubblewrapPolicyTranslator) translateFilesystem(policy *sandbox.Sandbox continue } + // Glob chars are handled by the processWriteRule function. writeArgs, err := t.processWriteRule(expanded, writeBoundPaths) if err != nil { log.Warnf("Failed to process allow_write rule '%s': %v", expanded, err) continue } + args = append(args, writeArgs...) } @@ -242,16 +248,16 @@ func (t *bubblewrapPolicyTranslator) translateFilesystem(policy *sandbox.Sandbox denyArgs, err := t.processDenyRule(expanded, allowedWritePaths) if err != nil { - // Deny rules failing is not critical (file may not exist yet) log.Debugf("Deny rule '%s' skipped: %v", expanded, err) continue } + args = append(args, denyArgs...) } // 4. Process mandatory credential directories - completely hide them with tmpfs // This blocks both read AND write access (more secure than read-only mount) - hiddenDirs := make(map[string]bool) // Track to avoid duplicates + hiddenDirs := make(map[string]bool) for _, pattern := range mandatoryDenies { expanded, err := util.ExpandVariables(pattern) if err != nil { @@ -260,11 +266,11 @@ func (t *bubblewrapPolicyTranslator) translateFilesystem(policy *sandbox.Sandbox var dirsToHide []string if util.ContainsGlob(expanded) { - // Expand glob pattern to find matching directories matches, err := filepath.Glob(expanded) if err != nil { continue } + dirsToHide = matches } else { dirsToHide = []string{expanded} @@ -274,9 +280,11 @@ func (t *bubblewrapPolicyTranslator) translateFilesystem(policy *sandbox.Sandbox if hiddenDirs[dir] { continue } + if info, err := os.Stat(dir); err == nil && info.IsDir() { args = append(args, "--tmpfs", dir) hiddenDirs[dir] = true + log.Debugf("Hiding credential directory '%s' with tmpfs", dir) } } @@ -304,7 +312,6 @@ func (t *bubblewrapPolicyTranslator) translateFilesystem(policy *sandbox.Sandbox } } } else { - // Literal path if info, err := os.Stat(expanded); err == nil && !info.IsDir() { args = append(args, "--ro-bind", "/dev/null", expanded) log.Debugf("Blocked execution of '%s'", expanded) @@ -709,7 +716,6 @@ func (t *bubblewrapPolicyTranslator) addPTYSupport() []string { // Package managers need writable temp space for downloads, extraction, etc. func (t *bubblewrapPolicyTranslator) addTmpdirSupport() []string { args := []string{} - tmpDir := os.TempDir() // Bind tmp directory as writable