feat: wire permission checker into engine tool execution
Tools now go through permission.Checker before executing: - plan mode: denies all writes (fs.write, bash), allows reads - bypass mode: allows all (deny rules still enforced) - default mode: prompts user (pipe: stdin prompt, TUI: auto-approve for now) - accept_edits: auto-allows file ops, prompts for bash - deny mode: denies all without allow rules CLI flags: --permission <mode>, --incognito Pipe mode: console Y/N prompt on stderr TUI mode: auto-approve (proper overlay TODO) Verified: plan mode correctly blocks fs.write, model sees error.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"somegit.dev/Owlibou/gnoma/internal/engine"
|
||||
"encoding/json"
|
||||
"somegit.dev/Owlibou/gnoma/internal/permission"
|
||||
"somegit.dev/Owlibou/gnoma/internal/provider"
|
||||
"somegit.dev/Owlibou/gnoma/internal/router"
|
||||
@@ -131,8 +132,14 @@ func main() {
|
||||
logger.Debug("incognito mode enabled")
|
||||
}
|
||||
|
||||
// Permission checker
|
||||
_ = permission.NewChecker(permission.Mode(*permMode), nil, nil)
|
||||
// Permission checker with console prompt for pipe mode
|
||||
pipePromptFn := func(ctx context.Context, toolName string, args json.RawMessage) (bool, error) {
|
||||
fmt.Fprintf(os.Stderr, "⚠ Tool %s wants to execute. Allow? [y/N] ", toolName)
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
return strings.ToLower(response) == "y" || strings.ToLower(response) == "yes", nil
|
||||
}
|
||||
permChecker := permission.NewChecker(permission.Mode(*permMode), nil, pipePromptFn)
|
||||
|
||||
// Build system prompt with compact inventory summary
|
||||
systemPrompt := *system
|
||||
@@ -142,13 +149,14 @@ func main() {
|
||||
|
||||
// Create engine
|
||||
eng, err := engine.New(engine.Config{
|
||||
Provider: prov,
|
||||
Router: rtr,
|
||||
Tools: reg,
|
||||
Firewall: fw,
|
||||
System: systemPrompt,
|
||||
Model: *model,
|
||||
MaxTurns: *maxTurns,
|
||||
Provider: prov,
|
||||
Router: rtr,
|
||||
Tools: reg,
|
||||
Firewall: fw,
|
||||
Permissions: permChecker,
|
||||
System: systemPrompt,
|
||||
Model: *model,
|
||||
MaxTurns: *maxTurns,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -191,7 +199,17 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// TUI mode: interactive terminal
|
||||
// TUI mode: replace permission prompt with channel-based one
|
||||
permChecker.SetPromptFunc(func(ctx context.Context, toolName string, args json.RawMessage) (bool, error) {
|
||||
// Send permission request through a channel, block until TUI responds
|
||||
respCh := make(chan bool, 1)
|
||||
// The engine callback will emit this as an event
|
||||
// For now, auto-approve in TUI (proper overlay is M5+)
|
||||
// TODO: wire to TUI overlay
|
||||
respCh <- true
|
||||
return <-respCh, nil
|
||||
})
|
||||
|
||||
armModel := *model
|
||||
if armModel == "" {
|
||||
armModel = prov.DefaultModel()
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"somegit.dev/Owlibou/gnoma/internal/message"
|
||||
"somegit.dev/Owlibou/gnoma/internal/permission"
|
||||
"somegit.dev/Owlibou/gnoma/internal/provider"
|
||||
"somegit.dev/Owlibou/gnoma/internal/router"
|
||||
"somegit.dev/Owlibou/gnoma/internal/security"
|
||||
@@ -17,8 +18,9 @@ type Config struct {
|
||||
Provider provider.Provider // direct provider (used if Router is nil)
|
||||
Router *router.Router // nil = use Provider directly
|
||||
Tools *tool.Registry
|
||||
Firewall *security.Firewall // nil = no scanning
|
||||
System string // system prompt
|
||||
Firewall *security.Firewall // nil = no scanning
|
||||
Permissions *permission.Checker // nil = allow all
|
||||
System string // system prompt
|
||||
Model string // override model (empty = provider default)
|
||||
MaxTurns int // safety limit on tool loops (0 = unlimited)
|
||||
Logger *slog.Logger
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"somegit.dev/Owlibou/gnoma/internal/message"
|
||||
"somegit.dev/Owlibou/gnoma/internal/permission"
|
||||
"somegit.dev/Owlibou/gnoma/internal/provider"
|
||||
"somegit.dev/Owlibou/gnoma/internal/router"
|
||||
"somegit.dev/Owlibou/gnoma/internal/stream"
|
||||
@@ -199,6 +200,24 @@ func (e *Engine) executeTools(ctx context.Context, calls []message.ToolCall, cb
|
||||
continue
|
||||
}
|
||||
|
||||
// Permission check
|
||||
if e.cfg.Permissions != nil {
|
||||
info := permission.ToolInfo{
|
||||
Name: call.Name,
|
||||
IsReadOnly: t.IsReadOnly(),
|
||||
IsDestructive: t.IsDestructive(),
|
||||
}
|
||||
if err := e.cfg.Permissions.Check(ctx, info, call.Arguments); err != nil {
|
||||
e.logger.Info("tool permission denied", "name", call.Name, "error", err)
|
||||
results = append(results, message.ToolResult{
|
||||
ToolCallID: call.ID,
|
||||
Content: fmt.Sprintf("permission denied: %v", err),
|
||||
IsError: true,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
e.logger.Debug("executing tool", "name", call.Name, "id", call.ID)
|
||||
|
||||
result, err := t.Execute(ctx, call.Arguments)
|
||||
|
||||
@@ -51,6 +51,11 @@ func NewChecker(mode Mode, rules []Rule, promptFn PromptFunc) *Checker {
|
||||
}
|
||||
}
|
||||
|
||||
// SetPromptFunc replaces the prompt function (e.g., switching from pipe to TUI prompt).
|
||||
func (c *Checker) SetPromptFunc(fn PromptFunc) {
|
||||
c.promptFn = fn
|
||||
}
|
||||
|
||||
// SetMode changes the active permission mode.
|
||||
func (c *Checker) SetMode(mode Mode) {
|
||||
c.mode = mode
|
||||
|
||||
@@ -16,7 +16,8 @@ const (
|
||||
EventToolCallStart
|
||||
EventToolCallDelta
|
||||
EventToolCallDone
|
||||
EventToolResult // tool execution output
|
||||
EventToolResult // tool execution output
|
||||
EventPermissionReq // permission prompt needed
|
||||
EventUsage
|
||||
EventError
|
||||
)
|
||||
@@ -35,6 +36,8 @@ func (et EventType) String() string {
|
||||
return "tool_call_done"
|
||||
case EventToolResult:
|
||||
return "tool_result"
|
||||
case EventPermissionReq:
|
||||
return "permission_req"
|
||||
case EventUsage:
|
||||
return "usage"
|
||||
case EventError:
|
||||
@@ -63,6 +66,9 @@ type Event struct {
|
||||
ToolName string
|
||||
ToolOutput string
|
||||
|
||||
// PermissionReq: tool requesting permission, response channel
|
||||
PermissionResponse chan bool
|
||||
|
||||
// Usage
|
||||
Usage *message.Usage
|
||||
|
||||
|
||||
Reference in New Issue
Block a user