fix: Fix sandbox policy generator for MacOS min permissions

This commit is contained in:
Abhisek Datta
2026-01-12 20:49:43 +05:30
parent d48ba68847
commit efca7ff711
13 changed files with 1597 additions and 68 deletions
+396 -40
View File
@@ -5,16 +5,136 @@ package platform
import (
"fmt"
"math/rand"
"path/filepath"
"strings"
"github.com/safedep/pmg/sandbox"
"github.com/safedep/pmg/sandbox/util"
)
type seatbeltPolicyTranslator struct{}
type seatbeltPolicyTranslator struct {
logTag string
}
// generateLogTag generates a unique log tag for tracking sandbox violations
func generateLogTag() string {
randomStr := fmt.Sprintf("%x", rand.Uint64())
return fmt.Sprintf("PMG_SBX_%s", randomStr[:12])
}
func newSeatbeltPolicyTranslator() *seatbeltPolicyTranslator {
return &seatbeltPolicyTranslator{}
return &seatbeltPolicyTranslator{
logTag: generateLogTag(),
}
}
// extractBaseDir extracts the base directory from a path that may contain glob patterns.
// For glob patterns, it returns the deepest directory path before any glob characters appear.
// Examples:
// - "/path/to/**" -> "/path/to"
// - "/path/to/*.txt" -> "/path/to"
// - "/path/*/subdir" -> "/path"
// - "/tmp/test[123].txt" -> "/tmp"
// - "/path/to/file" -> "/path/to/file" (no glob, return as-is)
// - "/*.txt" -> "/" (root directory)
func extractBaseDir(pattern string) string {
if !util.ContainsGlob(pattern) {
return pattern
}
// Split the path into components
components := strings.Split(pattern, string(filepath.Separator))
// Find the first component that contains a glob character
baseComponents := []string{}
for _, component := range components {
if util.ContainsGlob(component) {
// Stop before the component with glob
break
}
baseComponents = append(baseComponents, component)
}
// Join the base components back together
basePath := strings.Join(baseComponents, string(filepath.Separator))
// Handle edge cases
if basePath == "" {
// Pattern started with glob (e.g., "*.txt" or if it's an absolute path like "/*.txt", basePath would be "")
if strings.HasPrefix(pattern, string(filepath.Separator)) {
// Absolute path starting with glob at root (e.g., "/*.txt")
return string(filepath.Separator)
}
// Relative path starting with glob (e.g., "*.txt")
return "."
}
return basePath
}
// getAncestorDirectories returns all ancestor directories for a path, up to (but not including) root.
// Example: /private/tmp/test/file.txt -> ["/private/tmp/test", "/private/tmp", "/private"]
func getAncestorDirectories(pathStr string) []string {
ancestors := []string{}
currentPath := filepath.Dir(pathStr)
// Walk up the directory tree until we reach root
for currentPath != string(filepath.Separator) && currentPath != "." {
ancestors = append(ancestors, currentPath)
parentPath := filepath.Dir(currentPath)
// Break if we've reached the top (filepath.Dir returns the same path for root)
if parentPath == currentPath {
break
}
currentPath = parentPath
}
return ancestors
}
// generateMoveBlockingRules generates deny rules for file movement (file-write-unlink) to protect paths.
// This prevents bypassing read or write restrictions by moving files/directories.
//
// For each protected path pattern:
// - Blocks moving/renaming the path itself (via subpath or regex)
// - Blocks moving ancestor directories to prevent bypass
//
// Attack scenario this prevents:
//
// Policy denies write to /sensitive/file
// Attacker tries: mv /sensitive /tmp/renamed && echo "data" > /tmp/renamed/file && mv /tmp/renamed /sensitive
// This blocks the initial "mv /sensitive" operation
func generateMoveBlockingRules(pathPatterns []string, logTag string) []string {
rules := []string{}
for _, pathPattern := range pathPatterns {
if util.ContainsGlob(pathPattern) {
// For glob patterns, use regex matching for precise pattern enforcement
regexPattern := util.GlobToRegex(pathPattern)
rules = append(rules, fmt.Sprintf("(deny file-write-unlink (regex \"%s\") (with message \"%s\"))", regexPattern, logTag))
// Also block moving the base directory to prevent bypass
baseDir := extractBaseDir(pathPattern)
rules = append(rules, fmt.Sprintf("(deny file-write-unlink (subpath \"%s\") (with message \"%s\"))", baseDir, logTag))
// Block moving ancestor directories
for _, ancestorDir := range getAncestorDirectories(baseDir) {
rules = append(rules, fmt.Sprintf("(deny file-write-unlink (literal \"%s\") (with message \"%s\"))", ancestorDir, logTag))
}
} else {
// For literal paths, use subpath matching
rules = append(rules, fmt.Sprintf("(deny file-write-unlink (subpath \"%s\") (with message \"%s\"))", pathPattern, logTag))
// Block moving ancestor directories
for _, ancestorDir := range getAncestorDirectories(pathPattern) {
rules = append(rules, fmt.Sprintf("(deny file-write-unlink (literal \"%s\") (with message \"%s\"))", ancestorDir, logTag))
}
}
}
return rules
}
func (t *seatbeltPolicyTranslator) translate(policy *sandbox.SandboxPolicy) (string, error) {
@@ -27,20 +147,169 @@ func (t *seatbeltPolicyTranslator) translate(policy *sandbox.SandboxPolicy) (str
sb.WriteString(";; Generated by PMG sandbox system\n\n")
// Default policy: deny by default for maximum security
sb.WriteString("(deny default)\n\n")
// Add log tag to track what gets denied by the default rule
sb.WriteString(fmt.Sprintf("(deny default (with message \"%s\"))\n\n", t.logTag))
// Allow basic system operations required for any process
sb.WriteString(";; Basic system access\n")
// Essential system permissions - based on Chrome/Chromium sandbox policy
// These are the minimum permissions needed for stable process execution
sb.WriteString(";; Essential system permissions\n")
sb.WriteString(";; Based on Chrome/Chromium sandbox for stable process execution\n\n")
// Process permissions
sb.WriteString(";; Process permissions\n")
sb.WriteString("(allow process-exec)\n")
sb.WriteString("(allow process-fork)\n")
sb.WriteString("(allow process-exec-interpreter)\n")
sb.WriteString("(allow sysctl-read)\n")
sb.WriteString("(allow mach-lookup)\n")
sb.WriteString("(allow mach-register)\n")
sb.WriteString("(allow ipc-posix-shm)\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 process-info* (target same-sandbox))\n")
sb.WriteString("(allow signal (target same-sandbox))\n")
sb.WriteString("(allow mach-priv-task-port (target same-sandbox))\n\n")
// User preferences
sb.WriteString(";; User preferences\n")
sb.WriteString("(allow user-preference-read)\n\n")
// Mach IPC - specific services only (no wildcard for security)
sb.WriteString(";; Mach IPC - specific services only\n")
sb.WriteString("(allow mach-lookup\n")
sb.WriteString(" (global-name \"com.apple.audio.systemsoundserver\")\n")
sb.WriteString(" (global-name \"com.apple.distributed_notifications@Uv3\")\n")
sb.WriteString(" (global-name \"com.apple.FontObjectsServer\")\n")
sb.WriteString(" (global-name \"com.apple.fonts\")\n")
sb.WriteString(" (global-name \"com.apple.logd\")\n")
sb.WriteString(" (global-name \"com.apple.lsd.mapdb\")\n")
sb.WriteString(" (global-name \"com.apple.PowerManagement.control\")\n")
sb.WriteString(" (global-name \"com.apple.system.logger\")\n")
sb.WriteString(" (global-name \"com.apple.system.notification_center\")\n")
sb.WriteString(" (global-name \"com.apple.trustd.agent\")\n")
sb.WriteString(" (global-name \"com.apple.system.opendirectoryd.libinfo\")\n")
sb.WriteString(" (global-name \"com.apple.system.opendirectoryd.membership\")\n")
sb.WriteString(" (global-name \"com.apple.bsd.dirhelper\")\n")
sb.WriteString(" (global-name \"com.apple.securityd.xpc\")\n")
sb.WriteString(" (global-name \"com.apple.coreservices.launchservicesd\")\n")
sb.WriteString(")\n\n")
// POSIX IPC
sb.WriteString(";; POSIX IPC\n")
sb.WriteString("(allow ipc-posix-shm) ; Shared memory\n")
sb.WriteString("(allow ipc-posix-sem) ; Semaphores for Python multiprocessing\n\n")
// IOKit operations
sb.WriteString(";; IOKit operations\n")
sb.WriteString("(allow iokit-open\n")
sb.WriteString(" (iokit-registry-entry-class \"IOSurfaceRootUserClient\")\n")
sb.WriteString(" (iokit-registry-entry-class \"RootDomainUserClient\")\n")
sb.WriteString(" (iokit-user-client-class \"IOSurfaceSendRight\")\n")
sb.WriteString(")\n")
sb.WriteString("(allow iokit-get-properties)\n\n")
// Specific safe system socket
sb.WriteString(";; Specific safe system socket\n")
sb.WriteString("(allow system-socket (require-all (socket-domain AF_SYSTEM) (socket-protocol 2)))\n\n")
// sysctl - specific sysctls only
sb.WriteString(";; sysctl - specific sysctls only\n")
sb.WriteString("(allow sysctl-read\n")
// Hardware info
sb.WriteString(" (sysctl-name \"hw.activecpu\")\n")
sb.WriteString(" (sysctl-name \"hw.busfrequency_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.byteorder\")\n")
sb.WriteString(" (sysctl-name \"hw.cacheconfig\")\n")
sb.WriteString(" (sysctl-name \"hw.cachelinesize_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.cpufamily\")\n")
sb.WriteString(" (sysctl-name \"hw.cpufrequency\")\n")
sb.WriteString(" (sysctl-name \"hw.cpufrequency_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.cputype\")\n")
sb.WriteString(" (sysctl-name \"hw.l1dcachesize_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.l1icachesize_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.l2cachesize_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.l3cachesize_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.logicalcpu\")\n")
sb.WriteString(" (sysctl-name \"hw.logicalcpu_max\")\n")
sb.WriteString(" (sysctl-name \"hw.machine\")\n")
sb.WriteString(" (sysctl-name \"hw.memsize\")\n")
sb.WriteString(" (sysctl-name \"hw.ncpu\")\n")
sb.WriteString(" (sysctl-name \"hw.nperflevels\")\n")
sb.WriteString(" (sysctl-name \"hw.packages\")\n")
sb.WriteString(" (sysctl-name \"hw.pagesize_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.pagesize\")\n")
sb.WriteString(" (sysctl-name \"hw.physicalcpu\")\n")
sb.WriteString(" (sysctl-name \"hw.physicalcpu_max\")\n")
sb.WriteString(" (sysctl-name \"hw.tbfrequency_compat\")\n")
sb.WriteString(" (sysctl-name \"hw.vectorunit\")\n")
// Kernel info
sb.WriteString(" (sysctl-name \"kern.argmax\")\n")
sb.WriteString(" (sysctl-name \"kern.bootargs\")\n")
sb.WriteString(" (sysctl-name \"kern.hostname\")\n")
sb.WriteString(" (sysctl-name \"kern.maxfiles\")\n")
sb.WriteString(" (sysctl-name \"kern.maxfilesperproc\")\n")
sb.WriteString(" (sysctl-name \"kern.maxproc\")\n")
sb.WriteString(" (sysctl-name \"kern.ngroups\")\n")
sb.WriteString(" (sysctl-name \"kern.osproductversion\")\n")
sb.WriteString(" (sysctl-name \"kern.osrelease\")\n")
sb.WriteString(" (sysctl-name \"kern.ostype\")\n")
sb.WriteString(" (sysctl-name \"kern.osvariant_status\")\n")
sb.WriteString(" (sysctl-name \"kern.osversion\")\n")
sb.WriteString(" (sysctl-name \"kern.secure_kernel\")\n")
sb.WriteString(" (sysctl-name \"kern.tcsm_available\")\n")
sb.WriteString(" (sysctl-name \"kern.tcsm_enable\")\n")
sb.WriteString(" (sysctl-name \"kern.usrstack64\")\n")
sb.WriteString(" (sysctl-name \"kern.version\")\n")
sb.WriteString(" (sysctl-name \"kern.willshutdown\")\n")
// machdep info
sb.WriteString(" (sysctl-name \"machdep.cpu.brand_string\")\n")
sb.WriteString(" (sysctl-name \"machdep.ptrauth_enabled\")\n")
// Security info
sb.WriteString(" (sysctl-name \"security.mac.lockdown_mode_state\")\n")
// Other
sb.WriteString(" (sysctl-name \"sysctl.proc_cputype\")\n")
sb.WriteString(" (sysctl-name \"vm.loadavg\")\n")
// Prefixes for more sysctls
sb.WriteString(" (sysctl-name-prefix \"hw.optional.arm\")\n")
sb.WriteString(" (sysctl-name-prefix \"hw.optional.arm.\")\n")
sb.WriteString(" (sysctl-name-prefix \"hw.optional.armv8_\")\n")
sb.WriteString(" (sysctl-name-prefix \"hw.perflevel\")\n")
sb.WriteString(" (sysctl-name-prefix \"kern.proc.all\")\n")
sb.WriteString(" (sysctl-name-prefix \"kern.proc.pgrp.\")\n")
sb.WriteString(" (sysctl-name-prefix \"kern.proc.pid.\")\n")
sb.WriteString(" (sysctl-name-prefix \"machdep.cpu.\")\n")
sb.WriteString(" (sysctl-name-prefix \"net.routetable.\")\n")
sb.WriteString(")\n\n")
// V8 thread calculations
sb.WriteString(";; V8 thread calculations\n")
sb.WriteString("(allow sysctl-write\n")
sb.WriteString(" (sysctl-name \"kern.tcsm_enable\")\n")
sb.WriteString(")\n\n")
// Distributed notifications
sb.WriteString(";; Distributed notifications\n")
sb.WriteString("(allow distributed-notification-post)\n\n")
// Specific mach-lookup for security
sb.WriteString(";; Specific mach-lookup for security\n")
sb.WriteString("(allow mach-lookup (global-name \"com.apple.SecurityServer\"))\n\n")
// Device file I/O
sb.WriteString(";; Device file I/O\n")
sb.WriteString("(allow file-ioctl (literal \"/dev/null\"))\n")
sb.WriteString("(allow file-ioctl (literal \"/dev/zero\"))\n")
sb.WriteString("(allow file-ioctl (literal \"/dev/random\"))\n")
sb.WriteString("(allow file-ioctl (literal \"/dev/urandom\"))\n")
sb.WriteString("(allow file-ioctl (literal \"/dev/dtracehelper\"))\n")
sb.WriteString("(allow file-ioctl (literal \"/dev/tty\"))\n\n")
sb.WriteString("(allow file-ioctl file-read-data file-write-data\n")
sb.WriteString(" (require-all\n")
sb.WriteString(" (literal \"/dev/null\")\n")
sb.WriteString(" (vnode-type CHARACTER-DEVICE)\n")
sb.WriteString(" )\n")
sb.WriteString(")\n\n")
// File metadata
sb.WriteString(";; File metadata for getcwd() and similar\n")
sb.WriteString("(allow file-read-metadata)\n\n")
// System configuration and libraries
sb.WriteString(";; System configuration and libraries\n")
sb.WriteString("(allow file-read* (subpath \"/dev\"))\n")
sb.WriteString("(allow file-read* (subpath \"/etc\"))\n\n")
@@ -59,6 +328,20 @@ func (t *seatbeltPolicyTranslator) translate(policy *sandbox.SandboxPolicy) (str
return "", fmt.Errorf("failed to translate process rules: %w", err)
}
// PTY support (optional)
if policy.AllowPTY {
sb.WriteString(";; Pseudo-terminal (PTY) support\n")
sb.WriteString("(allow pseudo-tty)\n")
sb.WriteString("(allow file-ioctl\n")
sb.WriteString(" (literal \"/dev/ptmx\")\n")
sb.WriteString(" (regex #\"^/dev/ttys\")\n")
sb.WriteString(")\n")
sb.WriteString("(allow file-read* file-write*\n")
sb.WriteString(" (literal \"/dev/ptmx\")\n")
sb.WriteString(" (regex #\"^/dev/ttys\")\n")
sb.WriteString(")\n\n")
}
return sb.String(), nil
}
@@ -73,11 +356,10 @@ func (t *seatbeltPolicyTranslator) translateFilesystem(policy *sandbox.SandboxPo
return fmt.Errorf("failed to expand pattern %s: %w", pattern, err)
}
// Handle glob patterns vs literal paths
// Use regex matching for glob patterns, subpath for literals
if util.ContainsGlob(expanded) {
// For glob patterns, use subpath with the base directory
baseDir := strings.TrimSuffix(expanded, "/**")
sb.WriteString(fmt.Sprintf("(allow file-read* (subpath \"%s\"))\n", baseDir))
regexPattern := util.GlobToRegex(expanded)
sb.WriteString(fmt.Sprintf("(allow file-read* (regex \"%s\"))\n", regexPattern))
} else {
sb.WriteString(fmt.Sprintf("(allow file-read* (subpath \"%s\"))\n", expanded))
}
@@ -85,6 +367,20 @@ func (t *seatbeltPolicyTranslator) translateFilesystem(policy *sandbox.SandboxPo
sb.WriteString("\n")
// Auto-allow TMPDIR parent on macOS when write restrictions are enabled
// This is necessary because package managers need temp file access
hasWriteRestrictions := len(policy.Filesystem.AllowWrite) > 0
if hasWriteRestrictions {
tmpdirParents := util.GetTmpdirParent()
if len(tmpdirParents) > 0 {
sb.WriteString(";; Auto-allow TMPDIR parent on macOS\n")
for _, parent := range tmpdirParents {
sb.WriteString(fmt.Sprintf("(allow file-write* (subpath \"%s\"))\n", parent))
}
sb.WriteString("\n")
}
}
// Expand and add allow write rules
for _, pattern := range policy.Filesystem.AllowWrite {
expanded, err := util.ExpandVariables(pattern)
@@ -92,9 +388,10 @@ func (t *seatbeltPolicyTranslator) translateFilesystem(policy *sandbox.SandboxPo
return fmt.Errorf("failed to expand pattern %s: %w", pattern, err)
}
// Use regex matching for glob patterns, subpath for literals
if util.ContainsGlob(expanded) {
baseDir := strings.TrimSuffix(expanded, "/**")
sb.WriteString(fmt.Sprintf("(allow file-write* (subpath \"%s\"))\n", baseDir))
regexPattern := util.GlobToRegex(expanded)
sb.WriteString(fmt.Sprintf("(allow file-write* (regex \"%s\"))\n", regexPattern))
} else {
sb.WriteString(fmt.Sprintf("(allow file-write* (subpath \"%s\"))\n", expanded))
}
@@ -104,31 +401,79 @@ func (t *seatbeltPolicyTranslator) translateFilesystem(policy *sandbox.SandboxPo
// Deny rules have higher priority (applied after allow)
// Note: Seatbelt evaluates rules in order, so denies after allows will override
expandedDenyRead := []string{}
for _, pattern := range policy.Filesystem.DenyRead {
expanded, err := util.ExpandVariables(pattern)
if err != nil {
return fmt.Errorf("failed to expand pattern %s: %w", pattern, err)
}
// Use regex matching for glob patterns, subpath for literals
if util.ContainsGlob(expanded) {
baseDir := strings.TrimSuffix(expanded, "/**")
sb.WriteString(fmt.Sprintf("(deny file-read* (subpath \"%s\"))\n", baseDir))
regexPattern := util.GlobToRegex(expanded)
sb.WriteString(fmt.Sprintf("(deny file-read* (regex \"%s\") (with message \"%s\"))\n", regexPattern, t.logTag))
} else {
sb.WriteString(fmt.Sprintf("(deny file-read* (subpath \"%s\"))\n", expanded))
sb.WriteString(fmt.Sprintf("(deny file-read* (subpath \"%s\") (with message \"%s\"))\n", expanded, t.logTag))
}
expandedDenyRead = append(expandedDenyRead, expanded)
}
// Add file movement protection for deny read paths
if len(expandedDenyRead) > 0 {
sb.WriteString("\n;; Prevent bypassing read restrictions via file movement\n")
for _, rule := range generateMoveBlockingRules(expandedDenyRead, t.logTag) {
sb.WriteString(rule + "\n")
}
}
sb.WriteString("\n")
expandedDenyWrite := []string{}
for _, pattern := range policy.Filesystem.DenyWrite {
expanded, err := util.ExpandVariables(pattern)
if err != nil {
return fmt.Errorf("failed to expand pattern %s: %w", pattern, err)
}
// Use regex matching for glob patterns, subpath for literals
if util.ContainsGlob(expanded) {
baseDir := strings.TrimSuffix(expanded, "/**")
sb.WriteString(fmt.Sprintf("(deny file-write* (subpath \"%s\"))\n", baseDir))
regexPattern := util.GlobToRegex(expanded)
sb.WriteString(fmt.Sprintf("(deny file-write* (regex \"%s\") (with message \"%s\"))\n", regexPattern, t.logTag))
} else {
sb.WriteString(fmt.Sprintf("(deny file-write* (subpath \"%s\"))\n", expanded))
sb.WriteString(fmt.Sprintf("(deny file-write* (subpath \"%s\") (with message \"%s\"))\n", expanded, t.logTag))
}
expandedDenyWrite = append(expandedDenyWrite, expanded)
}
sb.WriteString("\n")
// Add mandatory deny patterns for security (credentials, git hooks, etc.)
sb.WriteString(";; Mandatory security denies (credentials, git hooks, etc.)\n")
mandatoryDenies := util.GetMandatoryDenyPatterns(policy.AllowGitConfig)
for _, pattern := range mandatoryDenies {
// Expand variables if needed
expanded, err := util.ExpandVariables(pattern)
if err != nil {
return fmt.Errorf("failed to expand mandatory deny pattern %s: %w", pattern, err)
}
// Use regex matching for glob patterns, subpath for literals
if util.ContainsGlob(expanded) {
regexPattern := util.GlobToRegex(expanded)
sb.WriteString(fmt.Sprintf("(deny file-write* (regex \"%s\") (with message \"%s\"))\n", regexPattern, t.logTag))
} else {
sb.WriteString(fmt.Sprintf("(deny file-write* (subpath \"%s\") (with message \"%s\"))\n", expanded, t.logTag))
}
expandedDenyWrite = append(expandedDenyWrite, expanded)
}
sb.WriteString("\n")
// Add file movement protection for all deny write paths (user + mandatory)
if len(expandedDenyWrite) > 0 {
sb.WriteString(";; Prevent bypassing write restrictions via file movement\n")
for _, rule := range generateMoveBlockingRules(expandedDenyWrite, t.logTag) {
sb.WriteString(rule + "\n")
}
}
@@ -141,6 +486,15 @@ func (t *seatbeltPolicyTranslator) translateFilesystem(policy *sandbox.SandboxPo
func (t *seatbeltPolicyTranslator) translateNetwork(policy *sandbox.SandboxPolicy, sb *strings.Builder) error {
sb.WriteString(";; Network access\n")
// Check if deny all is present
denyAll := false
for _, pattern := range policy.Network.DenyOutbound {
if pattern == "*:*" {
denyAll = true
break
}
}
// If there are allow outbound rules, allow network-outbound generally
// (Seatbelt doesn't support fine-grained host:port filtering in all cases)
// Note: This is a limitation of Seatbelt - for more fine-grained control,
@@ -149,16 +503,17 @@ func (t *seatbeltPolicyTranslator) translateNetwork(policy *sandbox.SandboxPolic
sb.WriteString(";; Network outbound allowed to specific hosts\n")
sb.WriteString(";; Note: Seatbelt has limited host-based filtering, consider using firewall rules for strict control\n")
sb.WriteString("(allow network-outbound)\n")
} else if denyAll {
// If there are no allow rules but deny all is set, explicitly deny network
// This handles the case where user wants to completely block network access
sb.WriteString(";; Network outbound denied (no allowed hosts specified)\n")
sb.WriteString("(deny network-outbound)\n")
}
// If deny outbound includes "*:*", block all network
for _, pattern := range policy.Network.DenyOutbound {
if pattern == "*:*" {
sb.WriteString(";; Network outbound denied\n")
sb.WriteString("(deny network-outbound)\n")
break
}
}
// Note: We don't add an explicit deny rule when both allow and deny_all are present
// because the default (deny default) at the top of the profile handles blocking
// everything that isn't explicitly allowed. Adding an explicit deny here would
// override the allow rule above, breaking network access entirely.
sb.WriteString("\n")
@@ -177,9 +532,9 @@ func (t *seatbeltPolicyTranslator) translateProcess(policy *sandbox.SandboxPolic
}
if util.ContainsGlob(expanded) {
// For glob patterns, use subpath to allow anything under that directory
baseDir := strings.TrimSuffix(expanded, "/**")
sb.WriteString(fmt.Sprintf("(allow process-exec* (subpath \"%s\"))\n", baseDir))
// For glob patterns, use regex matching for precise control
regexPattern := util.GlobToRegex(expanded)
sb.WriteString(fmt.Sprintf("(allow process-exec* (regex \"%s\"))\n", regexPattern))
} else {
sb.WriteString(fmt.Sprintf("(allow process-exec* (literal \"%s\"))\n", expanded))
}
@@ -195,10 +550,11 @@ func (t *seatbeltPolicyTranslator) translateProcess(policy *sandbox.SandboxPolic
}
if util.ContainsGlob(expanded) {
baseDir := strings.TrimSuffix(expanded, "/**")
sb.WriteString(fmt.Sprintf("(deny process-exec* (subpath \"%s\"))\n", baseDir))
// For glob patterns, use regex matching for precise control
regexPattern := util.GlobToRegex(expanded)
sb.WriteString(fmt.Sprintf("(deny process-exec* (regex \"%s\") (with message \"%s\"))\n", regexPattern, t.logTag))
} else {
sb.WriteString(fmt.Sprintf("(deny process-exec* (literal \"%s\"))\n", expanded))
sb.WriteString(fmt.Sprintf("(deny process-exec* (literal \"%s\") (with message \"%s\"))\n", expanded, t.logTag))
}
}