From 843552ad169c2f2c49a020893123ddedd51bdd33 Mon Sep 17 00:00:00 2001 From: anthrodjear Date: Tue, 5 May 2026 06:13:43 +0300 Subject: [PATCH] feat: add PermissionCache for exec tool permission system --- pkg/tools/permission.go | 48 ++++++++++++++++++++++++++++++++++++ pkg/tools/permission_test.go | 13 ++++++++++ 2 files changed, 61 insertions(+) create mode 100644 pkg/tools/permission.go create mode 100644 pkg/tools/permission_test.go diff --git a/pkg/tools/permission.go b/pkg/tools/permission.go new file mode 100644 index 000000000..073448f4e --- /dev/null +++ b/pkg/tools/permission.go @@ -0,0 +1,48 @@ +package tools + +import ( + "sync" +) + +type PermissionCache struct { + mu sync.RWMutex + perms map[string]string // path → "once"|"session"|"denied" +} + +func NewPermissionCache() *PermissionCache { + return &PermissionCache{ + perms: make(map[string]string), + } +} + +// Check returns "once", "session", "denied", or "" (no permission) +func (pc *PermissionCache) Check(path string) string { + pc.mu.RLock() + defer pc.mu.RUnlock() + + // Check exact match + if val, ok := pc.perms[path]; ok { + return val + } + + // Check if any parent path is granted "session" + for cachedPath, val := range pc.perms { + if val == "session" && len(cachedPath) < len(path) && path[:len(cachedPath)] == cachedPath { + return "session" + } + } + + return "" +} + +func (pc *PermissionCache) Grant(path, duration string) { + pc.mu.Lock() + defer pc.mu.Unlock() + pc.perms[path] = duration +} + +func (pc *PermissionCache) Revoke(path string) { + pc.mu.Lock() + defer pc.mu.Unlock() + delete(pc.perms, path) +} diff --git a/pkg/tools/permission_test.go b/pkg/tools/permission_test.go new file mode 100644 index 000000000..367072b2a --- /dev/null +++ b/pkg/tools/permission_test.go @@ -0,0 +1,13 @@ +package tools + +import ( + "testing" +) + +func TestPermissionCache_Check_NoPermission(t *testing.T) { + pc := NewPermissionCache() + result := pc.Check("/desktop") + if result != "" { + t.Errorf("Expected empty string, got %s", result) + } +}