feat: deny-by-default filesystem isolation
Flip the sandbox from allow-by-default reads (--ro-bind / /) to deny-by-default (--tmpfs / with selective mounts). This makes the sandbox safer by default — only system paths, CWD, and explicitly allowed paths are accessible. - Config: DefaultDenyRead is now *bool (nil = true, deny-by-default) with IsDefaultDenyRead() helper; opt out via "defaultDenyRead": false - Linux: new buildDenyByDefaultMounts() using --tmpfs / + selective --ro-bind for system paths, --symlink for merged-usr distros (Arch), --bind for CWD, and --ro-bind for user tooling/shell configs/caches - macOS: generateReadRules() adds CWD subpath, ancestor traversal, home shell configs/caches; generateWriteRules() auto-allows CWD - Landlock: deny-by-default mode allows only specific user tooling paths instead of blanket home directory read access - Sensitive .env files masked within CWD via empty-file overlay on Linux and deny rules on macOS - Learning templates now include allowRead and .env deny patterns
This commit is contained in:
@@ -87,6 +87,43 @@ func GenerateLearnedTemplate(straceLogPath, cmdName string, debug bool) (string,
|
||||
allowWrite = append(allowWrite, toTildePath(p, home))
|
||||
}
|
||||
|
||||
// Filter read paths: remove system defaults, CWD subtree, and sensitive paths
|
||||
cwd, _ := os.Getwd()
|
||||
var filteredReads []string
|
||||
defaultReadable := GetDefaultReadablePaths()
|
||||
for _, p := range result.ReadPaths {
|
||||
// Skip system defaults
|
||||
isDefault := false
|
||||
for _, dp := range defaultReadable {
|
||||
if p == dp || strings.HasPrefix(p, dp+"/") {
|
||||
isDefault = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isDefault {
|
||||
continue
|
||||
}
|
||||
// Skip CWD subtree (auto-included)
|
||||
if cwd != "" && (p == cwd || strings.HasPrefix(p, cwd+"/")) {
|
||||
continue
|
||||
}
|
||||
// Skip sensitive paths
|
||||
if isSensitivePath(p, home) {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "[greywall] Skipping sensitive read path: %s\n", p)
|
||||
}
|
||||
continue
|
||||
}
|
||||
filteredReads = append(filteredReads, p)
|
||||
}
|
||||
|
||||
// Collapse read paths and convert to tilde-relative
|
||||
collapsedReads := CollapsePaths(filteredReads)
|
||||
var allowRead []string
|
||||
for _, p := range collapsedReads {
|
||||
allowRead = append(allowRead, toTildePath(p, home))
|
||||
}
|
||||
|
||||
// Convert read paths to tilde-relative for display
|
||||
var readDisplay []string
|
||||
for _, p := range result.ReadPaths {
|
||||
@@ -103,6 +140,13 @@ func GenerateLearnedTemplate(straceLogPath, cmdName string, debug bool) (string,
|
||||
}
|
||||
}
|
||||
|
||||
if len(allowRead) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "[greywall] Additional read paths (beyond system + CWD):\n")
|
||||
for _, p := range allowRead {
|
||||
fmt.Fprintf(os.Stderr, "[greywall] %s\n", p)
|
||||
}
|
||||
}
|
||||
|
||||
if len(allowWrite) > 1 { // >1 because "." is always included
|
||||
fmt.Fprintf(os.Stderr, "[greywall] Discovered write paths (collapsed):\n")
|
||||
for _, p := range allowWrite {
|
||||
@@ -118,7 +162,7 @@ func GenerateLearnedTemplate(straceLogPath, cmdName string, debug bool) (string,
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
// Build template
|
||||
template := buildTemplate(cmdName, allowWrite)
|
||||
template := buildTemplate(cmdName, allowRead, allowWrite)
|
||||
|
||||
// Save template
|
||||
templatePath := LearnedTemplatePath(cmdName)
|
||||
@@ -345,9 +389,18 @@ func deduplicateSubPaths(paths []string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// getSensitiveProjectDenyPatterns returns denyRead entries for sensitive project files.
|
||||
func getSensitiveProjectDenyPatterns() []string {
|
||||
return []string{
|
||||
".env",
|
||||
".env.*",
|
||||
}
|
||||
}
|
||||
|
||||
// buildTemplate generates the JSONC template content for a learned config.
|
||||
func buildTemplate(cmdName string, allowWrite []string) string {
|
||||
func buildTemplate(cmdName string, allowRead, allowWrite []string) string {
|
||||
type fsConfig struct {
|
||||
AllowRead []string `json:"allowRead,omitempty"`
|
||||
AllowWrite []string `json:"allowWrite"`
|
||||
DenyWrite []string `json:"denyWrite"`
|
||||
DenyRead []string `json:"denyRead"`
|
||||
@@ -356,11 +409,15 @@ func buildTemplate(cmdName string, allowWrite []string) string {
|
||||
Filesystem fsConfig `json:"filesystem"`
|
||||
}
|
||||
|
||||
// Combine sensitive read patterns with .env project patterns
|
||||
denyRead := append(getSensitiveReadPatterns(), getSensitiveProjectDenyPatterns()...)
|
||||
|
||||
cfg := templateConfig{
|
||||
Filesystem: fsConfig{
|
||||
AllowRead: allowRead,
|
||||
AllowWrite: allowWrite,
|
||||
DenyWrite: getDangerousFilePatterns(),
|
||||
DenyRead: getSensitiveReadPatterns(),
|
||||
DenyRead: denyRead,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user