Fix glob parent directory allowance for patterns with glob characters (#331)

* fix(sandbox): support pnpm workspaces and macOS cache dir in pnpm profile

pnpm in a workspace (monorepo) creates a node_modules directory inside
every workspace package to symlink direct dependencies. The profile only
allowed writes to the root node_modules, so installs failed with EPERM
on mkdir of e.g. apps/mobile/node_modules.

pnpm on macOS also writes its cache (lockfile verification, metadata)
under ~/Library/Caches/pnpm, while the base profile only covers the XDG
path ~/.cache/pnpm.

Fixes are scoped to the pnpm leaf profile, not the shared
npm-restrictive base.

Ref: https://github.com/safedep/pmg/issues/329

* fix(sandbox): emit regex parent rule for nested-glob allow patterns on Seatbelt

For allow patterns ending in /**, the translator auto-allows the parent
directory so mkdir/stat of the directory itself succeeds. The rule was
always emitted as a literal, which can never match when the parent still
contains glob characters (e.g. ${CWD}/**/node_modules from a workspace
allowance) — silently leaving the directory's own creation denied.

Emit a regex rule for glob-bearing parents instead. This stays strictly
narrower than the Linux drivers (Bubblewrap binds the prefix before the
first /** read-write; Landlock grants the glob expansion or its parent),
and deny rules are emitted after allows, so mandatory credential denies
still override.

Ref: https://github.com/safedep/pmg/issues/329

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Abhisek Datta
2026-06-12 12:11:12 +05:30
committed by GitHub
co-authored by Claude
parent c7244f921a
commit 788a031003
3 changed files with 46 additions and 1 deletions
+15 -1
View File
@@ -109,8 +109,16 @@ func getAncestorDirectories(pathStr string) []string {
return ancestors
}
// globDoubleStarAutoAllowParentDirIfNeeded checks if a pattern ends with /** and emits a literal rule for the parent directory.
// globDoubleStarAutoAllowParentDirIfNeeded checks if a pattern ends with /** and emits a rule for the parent directory.
// This ensures operations like mkdir('dir') or stat('dir') succeed before accessing dir/** contents.
//
// When the parent itself still contains glob characters (e.g. ${CWD}/**/node_modules),
// a literal rule can never match a real path, so a regex rule is emitted instead.
// This grants access only to directories matching the parent pattern — strictly
// narrower than the Linux drivers, which bind the prefix before the first /**
// (Bubblewrap) or the glob's expansion/parent (Landlock) read-write. Deny rules
// are emitted after allows and override them, so mandatory credential denies
// are unaffected.
func globDoubleStarAutoAllowParentDirIfNeeded(sb *strings.Builder, pattern string, expanded string, operation string) {
if !strings.HasSuffix(expanded, "/**") {
return
@@ -127,6 +135,12 @@ func globDoubleStarAutoAllowParentDirIfNeeded(sb *strings.Builder, pattern strin
sb.WriteString("\n")
sb.WriteString("(allow ")
sb.WriteString(operation)
if util.ContainsGlob(parentDir) {
sb.WriteString(" (regex #\"")
sb.WriteString(util.GlobToRegex(parentDir))
sb.WriteString("\"))\n")
return
}
sb.WriteString(" (literal \"")
sb.WriteString(parentDir)
sb.WriteString("\"))\n")