package engine import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "somegit.dev/Owlibou/gnoma/internal/message" "somegit.dev/Owlibou/gnoma/internal/tool" ) // isUnderAllowedPaths reports whether target is equal to or a descendant of any // path in allowed. Both sides are cleaned before comparison. Returns false when // allowed is empty. // // The trailing-separator check prevents "/tmp" from matching "/tmpx/foo". func isUnderAllowedPaths(target string, allowed []string) bool { target = filepath.Clean(target) sep := string(filepath.Separator) for _, a := range allowed { a = filepath.Clean(a) if target == a || strings.HasPrefix(target, a+sep) { return true } } return false } // checkPathRestriction enforces AllowedPaths on a single tool call. // // Rules (in order): // 1. If allowed is empty, everything is permitted (fast-path). // 2. "bash" is always denied when path restrictions are active. // 3. Tools implementing tool.PathSensitiveTool have their extracted paths // checked against allowed. An empty extracted path is resolved to cwd. // 4. Tools that do not implement PathSensitiveTool are permitted (they don't // declare filesystem access). // // Returns (denied result, true) when blocked, or (zero, false) when allowed. func checkPathRestriction(call message.ToolCall, t tool.Tool, args json.RawMessage, allowed []string) (message.ToolResult, bool) { if len(allowed) == 0 { return message.ToolResult{}, false } if call.Name == "bash" { return message.ToolResult{ ToolCallID: call.ID, Content: "bash is not permitted when skill path restrictions are active", IsError: true, }, true } pt, ok := t.(tool.PathSensitiveTool) if !ok { return message.ToolResult{}, false } for _, p := range pt.ExtractPaths(args) { var resolved string if p == "" { cwd, err := os.Getwd() if err != nil { return message.ToolResult{ ToolCallID: call.ID, Content: fmt.Sprintf("path access denied: cannot determine current directory: %v", err), IsError: true, }, true } resolved = cwd } else { resolved = p } if !isUnderAllowedPaths(resolved, allowed) { return message.ToolResult{ ToolCallID: call.ID, Content: fmt.Sprintf("path access denied: %q is not in allowed paths %v", resolved, allowed), IsError: true, }, true } } return message.ToolResult{}, false }