@@ -1,10 +1,12 @@
11package permission
22
33import (
4+ "context"
45 "errors"
56 "path/filepath"
67 "slices"
78 "sync"
9+ "time"
810
911 "github.com/charmbracelet/crush/internal/config"
1012 "github.com/charmbracelet/crush/internal/pubsub"
@@ -44,9 +46,11 @@ type Service interface {
4446type permissionService struct {
4547 *pubsub.Broker[PermissionRequest]
4648
47− sessionPermissions []PermissionRequest
48− pendingRequests sync.Map
49− autoApproveSessions []string
49+ sessionPermissions []PermissionRequest
50+ sessionPermissionsMu sync.RWMutex
51+ pendingRequests sync.Map
52+ autoApproveSessions []string
53+ autoApproveSessionsMu sync.RWMutex
5054}
5155
5256func (s *permissionService) GrantPersistent(permission PermissionRequest) {
@@ -54,7 +58,10 @@ func (s *permissionService) GrantPersistent(permission PermissionRequest) {
5458 if ok {
5559 respCh.(chan bool) <- true
5660 }
61+
62+ s.sessionPermissionsMu.Lock()
5763 s.sessionPermissions = append(s.sessionPermissions, permission)
64+ s.sessionPermissionsMu.Unlock()
5865}
5966
6067func (s *permissionService) Grant(permission PermissionRequest) {
@@ -72,9 +79,14 @@ func (s *permissionService) Deny(permission PermissionRequest) {
7279}
7380
7481func (s *permissionService) Request(opts CreatePermissionRequest) bool {
75− if slices.Contains(s.autoApproveSessions, opts.SessionID) {
82+ s.autoApproveSessionsMu.RLock()
83+ autoApprove := slices.Contains(s.autoApproveSessions, opts.SessionID)
84+ s.autoApproveSessionsMu.RUnlock()
85+
86+ if autoApprove {
7687 return true
7788 }
89+
7890 dir := filepath.Dir(opts.Path)
7991 if dir == "." {
8092 dir = config.WorkingDirectory()
@@ -89,11 +101,14 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
89101 Params: opts.Params,
90102 }
91103
104+ s.sessionPermissionsMu.RLock()
92105 for _, p := range s.sessionPermissions {
93106 if p.ToolName == permission.ToolName && p.Action == permission.Action && p.SessionID == permission.SessionID && p.Path == permission.Path {
107+ s.sessionPermissionsMu.RUnlock()
94108 return true
95109 }
96110 }
111+ s.sessionPermissionsMu.RUnlock()
97112
98113 respCh := make(chan bool, 1)
99114
@@ -102,13 +117,22 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
102117
103118 s.Publish(pubsub.CreatedEvent, permission)
104119
105− // Wait for the response with a timeout
106− resp := <-respCh
107− return resp
120+ // Wait for the response with a timeout to prevent indefinite blocking
121+ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
122+ defer cancel()
123+
124+ select {
125+ case resp := <-respCh:
126+ return resp
127+ case <-ctx.Done():
128+ return false // Timeout - deny by default
129+ }
108130}
109131
110132func (s *permissionService) AutoApproveSession(sessionID string) {
133+ s.autoApproveSessionsMu.Lock()
111134 s.autoApproveSessions = append(s.autoApproveSessions, sessionID)
135+ s.autoApproveSessionsMu.Unlock()
112136}
113137
114138func NewPermissionService() Service {