@@ -1,10 +1,12 @@
1package permission
1package permission
5 "path/filepath"
6 "path/filepath"
9 "github.com/charmbracelet/crush/internal/config"
11 "github.com/charmbracelet/crush/internal/config"
10 "github.com/charmbracelet/crush/internal/pubsub"
12 "github.com/charmbracelet/crush/internal/pubsub"
@@ -44,9 +46,11 @@ type Service interface {
44type permissionService struct {
46type permissionService struct {
45 *pubsub.Broker[PermissionRequest]
47 *pubsub.Broker[PermissionRequest]
47− sessionPermissions []PermissionRequest
49+ sessionPermissions []PermissionRequest
50+ sessionPermissionsMu sync.RWMutex
48− pendingRequests sync.Map
51+ pendingRequests sync.Map
49− autoApproveSessions []string
52+ autoApproveSessions []string
53+ autoApproveSessionsMu sync.RWMutex
52func (s *permissionService) GrantPersistent(permission PermissionRequest) {
56func (s *permissionService) GrantPersistent(permission PermissionRequest) {
@@ -54,7 +58,10 @@ func (s *permissionService) GrantPersistent(permission PermissionRequest) {
55 respCh.(chan bool) <- true
59 respCh.(chan bool) <- true
62+ s.sessionPermissionsMu.Lock()
57 s.sessionPermissions = append(s.sessionPermissions, permission)
63 s.sessionPermissions = append(s.sessionPermissions, permission)
64+ s.sessionPermissionsMu.Unlock()
60func (s *permissionService) Grant(permission PermissionRequest) {
67func (s *permissionService) Grant(permission PermissionRequest) {
@@ -72,9 +79,14 @@ func (s *permissionService) Deny(permission PermissionRequest) {
74func (s *permissionService) Request(opts CreatePermissionRequest) bool {
81func (s *permissionService) Request(opts CreatePermissionRequest) bool {
82+ s.autoApproveSessionsMu.RLock()
75− if slices.Contains(s.autoApproveSessions, opts.SessionID) {
83+ autoApprove := slices.Contains(s.autoApproveSessions, opts.SessionID)
84+ s.autoApproveSessionsMu.RUnlock()
76 return true
87 return true
78 dir := filepath.Dir(opts.Path)
90 dir := filepath.Dir(opts.Path)
79 if dir == "." {
91 if dir == "." {
80 dir = config.WorkingDirectory()
92 dir = config.WorkingDirectory()
@@ -89,11 +101,14 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
89 Params: opts.Params,
101 Params: opts.Params,
104+ s.sessionPermissionsMu.RLock()
92 for _, p := range s.sessionPermissions {
105 for _, p := range s.sessionPermissions {
93 if p.ToolName == permission.ToolName && p.Action == permission.Action && p.SessionID == permission.SessionID && p.Path == permission.Path {
106 if p.ToolName == permission.ToolName && p.Action == permission.Action && p.SessionID == permission.SessionID && p.Path == permission.Path {
107+ s.sessionPermissionsMu.RUnlock()
94 return true
108 return true
111+ s.sessionPermissionsMu.RUnlock()
98 respCh := make(chan bool, 1)
113 respCh := make(chan bool, 1)
@@ -102,13 +117,22 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
103 s.Publish(pubsub.CreatedEvent, permission)
118 s.Publish(pubsub.CreatedEvent, permission)
105− // Wait for the response with a timeout
120+ // Wait for the response with a timeout to prevent indefinite blocking
121+ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
106− resp := <-respCh
125+ case resp := <-respCh:
107− return resp
126+ return resp
128+ return false // Timeout - deny by default
110func (s *permissionService) AutoApproveSession(sessionID string) {
132func (s *permissionService) AutoApproveSession(sessionID string) {
133+ s.autoApproveSessionsMu.Lock()
111 s.autoApproveSessions = append(s.autoApproveSessions, sessionID)
134 s.autoApproveSessions = append(s.autoApproveSessions, sessionID)
135+ s.autoApproveSessionsMu.Unlock()
114func NewPermissionService() Service {
138func NewPermissionService() Service {