feat: add defaultDenyRead mode for strict filesystem isolation (#24)
This commit is contained in:
29
internal/templates/code-strict.json
Normal file
29
internal/templates/code-strict.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "code",
|
||||
"filesystem": {
|
||||
// Deny reads by default, only system paths and allowRead are accessible
|
||||
"defaultDenyRead": true,
|
||||
"allowRead": [
|
||||
// Current working directory
|
||||
".",
|
||||
|
||||
// macOS preferences (needed by many apps)
|
||||
"~/Library/Preferences",
|
||||
|
||||
// AI coding tool configs (need to read their own settings)
|
||||
"~/.claude",
|
||||
"~/.claude.json",
|
||||
"~/.codex",
|
||||
"~/.cursor",
|
||||
"~/.opencode",
|
||||
"~/.gemini",
|
||||
"~/.factory",
|
||||
|
||||
// XDG config directory
|
||||
"~/.config",
|
||||
|
||||
// Cache directories (some tools read from cache)
|
||||
"~/.cache"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ var templateDescriptions = map[string]string{
|
||||
"git-readonly": "Blocks destructive commands like git push, rm -rf, etc.",
|
||||
"code": "Production-ready config for AI coding agents (Claude Code, Codex, Copilot, etc.)",
|
||||
"code-relaxed": "Like 'code' but allows direct network for apps that ignore HTTP_PROXY (cursor-agent, opencode)",
|
||||
"code-strict": "Like 'code' but denies reads by default; only allows reading the current project directory and essential system paths",
|
||||
}
|
||||
|
||||
// List returns all available template names sorted alphabetically.
|
||||
|
||||
@@ -126,6 +126,63 @@ func TestCodeTemplate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodeStrictTemplate(t *testing.T) {
|
||||
cfg, err := Load("code-strict")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load code-strict template: %v", err)
|
||||
}
|
||||
|
||||
// Should inherit AllowPty from code template
|
||||
if !cfg.AllowPty {
|
||||
t.Error("code-strict should inherit AllowPty=true from code")
|
||||
}
|
||||
|
||||
// Should have defaultDenyRead enabled
|
||||
if !cfg.Filesystem.DefaultDenyRead {
|
||||
t.Error("code-strict should have DefaultDenyRead=true")
|
||||
}
|
||||
|
||||
// Should have allowRead with current directory
|
||||
if len(cfg.Filesystem.AllowRead) == 0 {
|
||||
t.Error("code-strict should have allowRead paths")
|
||||
}
|
||||
hasCurrentDir := false
|
||||
for _, path := range cfg.Filesystem.AllowRead {
|
||||
if path == "." {
|
||||
hasCurrentDir = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasCurrentDir {
|
||||
t.Error("code-strict should allow reading current directory")
|
||||
}
|
||||
|
||||
// Should inherit allowWrite from code
|
||||
if len(cfg.Filesystem.AllowWrite) == 0 {
|
||||
t.Error("code-strict should inherit allowWrite from code")
|
||||
}
|
||||
|
||||
// Should inherit denyWrite from code
|
||||
if len(cfg.Filesystem.DenyWrite) == 0 {
|
||||
t.Error("code-strict should inherit denyWrite from code")
|
||||
}
|
||||
|
||||
// Should inherit allowed domains from code
|
||||
if len(cfg.Network.AllowedDomains) == 0 {
|
||||
t.Error("code-strict should inherit allowed domains from code")
|
||||
}
|
||||
|
||||
// Should inherit denied commands from code
|
||||
if len(cfg.Command.Deny) == 0 {
|
||||
t.Error("code-strict should inherit denied commands from code")
|
||||
}
|
||||
|
||||
// Extends should be cleared after resolution
|
||||
if cfg.Extends != "" {
|
||||
t.Error("extends should be cleared after loading")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodeRelaxedTemplate(t *testing.T) {
|
||||
cfg, err := Load("code-relaxed")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user