@@ -35,6 +35,8 @@ type MacOSSandboxParams struct {
3535 AllowAllUnixSockets bool
3636 AllowLocalBinding bool
3737 AllowLocalOutbound bool
38+ DefaultDenyRead bool
39+ ReadAllowPaths []string
3840 ReadDenyPaths []string
3941 WriteAllowPaths []string
4042 WriteDenyPaths []string
@@ -143,13 +145,54 @@ func getTmpdirParent() []string {
143145}
144146
145147// generateReadRules generates filesystem read rules for the sandbox profile.
146- func generateReadRules (denyPaths []string , logTag string ) []string {
148+ func generateReadRules (defaultDenyRead bool , allowPaths , denyPaths []string , logTag string ) []string {
147149 var rules []string
148150
149- // Allow all reads by default
150- rules = append (rules , "(allow file-read*)" )
151+ if defaultDenyRead {
152+ // When defaultDenyRead is enabled:
153+ // 1. Allow file-read-metadata globally (needed for directory traversal, stat, etc.)
154+ // 2. Allow file-read-data only for system paths + user-specified allowRead paths
155+ // This lets programs see what files exist but not read their contents.
151156
152- // Deny specific paths
157+ // Allow metadata operations globally (stat, readdir, etc.) and root dir (for path resolution)
158+ rules = append (rules , "(allow file-read-metadata)" )
159+ rules = append (rules , `(allow file-read-data (literal "/"))` )
160+
161+ // Allow reading data from essential system paths
162+ for _ , systemPath := range GetDefaultReadablePaths () {
163+ rules = append (rules ,
164+ "(allow file-read-data" ,
165+ fmt .Sprintf (" (subpath %s))" , escapePath (systemPath )),
166+ )
167+ }
168+
169+ // Allow reading data from user-specified paths
170+ for _ , pathPattern := range allowPaths {
171+ normalized := NormalizePath (pathPattern )
172+
173+ if ContainsGlobChars (normalized ) {
174+ regex := GlobToRegex (normalized )
175+ rules = append (rules ,
176+ "(allow file-read-data" ,
177+ fmt .Sprintf (" (regex %s))" , escapePath (regex )),
178+ )
179+ } else {
180+ rules = append (rules ,
181+ "(allow file-read-data" ,
182+ fmt .Sprintf (" (subpath %s))" , escapePath (normalized )),
183+ )
184+ }
185+ }
186+ } else {
187+ // Allow all reads by default
188+ rules = append (rules , "(allow file-read*)" )
189+ }
190+
191+ // In both modes, deny specific paths (denyRead takes precedence).
192+ // Note: We use file-read* (not file-read-data) so denied paths are fully hidden.
193+ // In defaultDenyRead mode, this overrides the global file-read-metadata allow,
194+ // meaning denied paths can't even be listed or stat'd - more restrictive than
195+ // default mode where denied paths are still visible but unreadable.
153196 for _ , pathPattern := range denyPaths {
154197 normalized := NormalizePath (pathPattern )
155198
@@ -494,7 +537,7 @@ func GenerateSandboxProfile(params MacOSSandboxParams) string {
494537
495538 // Read rules
496539 profile .WriteString ("; File read\n " )
497- for _ , rule := range generateReadRules (params .ReadDenyPaths , logTag ) {
540+ for _ , rule := range generateReadRules (params .DefaultDenyRead , params . ReadAllowPaths , params . ReadDenyPaths , logTag ) {
498541 profile .WriteString (rule + "\n " )
499542 }
500543 profile .WriteString ("\n " )
@@ -566,6 +609,8 @@ func WrapCommandMacOS(cfg *config.Config, command string, httpPort, socksPort in
566609 AllowAllUnixSockets : cfg .Network .AllowAllUnixSockets ,
567610 AllowLocalBinding : allowLocalBinding ,
568611 AllowLocalOutbound : allowLocalOutbound ,
612+ DefaultDenyRead : cfg .Filesystem .DefaultDenyRead ,
613+ ReadAllowPaths : cfg .Filesystem .AllowRead ,
569614 ReadDenyPaths : cfg .Filesystem .DenyRead ,
570615 WriteAllowPaths : allowPaths ,
571616 WriteDenyPaths : cfg .Filesystem .DenyWrite ,
0 commit comments