mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-11 03:13:24 +00:00
setupToolRegistry wires the filesystem tools' AllowPaths from the config/JSON5 default before ApplySystemConfigs overlays system_configs['allowed_paths'], so DB-configured allowed paths never reached read_file / list_files / write_file / edit / send_file. Only the rate limiter was re-applied after the overlay (#1111); the AllowPaths analogue was missing, so agents were denied access to configured shared directories outside their workspace even though the DB value was present (visible as a read_file "access denied" log whose allowedPrefixes omit the configured path). Extract the user-allowed-path application into applyUserAllowedPaths and re-run it from runGateway after the overlay, mirroring the rate-limiter re-apply. The helper is idempotent (AllowPaths is additive and the prefix check is membership-based), so the initial wiring call plus the re-apply is safe. Test: cmd/gateway_tools_wiring_test.go asserts a path outside the workspace is denied before the grant and readable after, that an unrelated path stays denied, that repeated application is safe, and that an empty list is a no-op.
87 lines
2.9 KiB
Go
87 lines
2.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/tools"
|
|
)
|
|
|
|
// applyUserAllowedPaths must grant the filesystem tools access to paths outside
|
|
// the agent workspace. cmd/gateway.go relies on this to re-apply
|
|
// system_configs['allowed_paths'] after the overlay (tool wiring runs first) —
|
|
// the AllowPaths analogue of the rate-limiter re-apply in #1111.
|
|
func TestApplyUserAllowedPaths_GrantsExternalPathToReadFile(t *testing.T) {
|
|
ws := t.TempDir()
|
|
ext := t.TempDir()
|
|
extFile := filepath.Join(ext, "kb.md")
|
|
if err := os.WriteFile(extFile, []byte("knowledge"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
reg := tools.NewRegistry()
|
|
reg.Register(tools.NewReadFileTool(ws, true))
|
|
read, ok := reg.Get("read_file")
|
|
if !ok {
|
|
t.Fatal("read_file not registered")
|
|
}
|
|
|
|
// Before granting: a path outside the workspace is denied.
|
|
if res := read.Execute(context.Background(), map[string]any{"path": extFile}); !res.IsError {
|
|
t.Fatalf("expected access denied before allow, got: %s", res.ForLLM)
|
|
}
|
|
|
|
applyUserAllowedPaths(reg, []string{ext})
|
|
|
|
// After granting: the external path is readable.
|
|
if res := read.Execute(context.Background(), map[string]any{"path": extFile}); res.IsError {
|
|
t.Fatalf("expected success after allow, got error: %s", res.ForLLM)
|
|
}
|
|
|
|
// The grant is scoped — an unrelated external path stays denied.
|
|
other := t.TempDir()
|
|
otherFile := filepath.Join(other, "secret.md")
|
|
if err := os.WriteFile(otherFile, []byte("nope"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res := read.Execute(context.Background(), map[string]any{"path": otherFile}); !res.IsError {
|
|
t.Fatalf("expected unrelated external path to stay denied, got: %s", res.ForLLM)
|
|
}
|
|
}
|
|
|
|
// Applying twice (initial wiring + the gateway re-apply after ApplySystemConfigs)
|
|
// must keep the grant working and must not error.
|
|
func TestApplyUserAllowedPaths_RepeatedApplyIsSafe(t *testing.T) {
|
|
ws := t.TempDir()
|
|
ext := t.TempDir()
|
|
extFile := filepath.Join(ext, "kb.md")
|
|
if err := os.WriteFile(extFile, []byte("knowledge"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
reg := tools.NewRegistry()
|
|
reg.Register(tools.NewReadFileTool(ws, true))
|
|
read, ok := reg.Get("read_file")
|
|
if !ok {
|
|
t.Fatal("read_file not registered")
|
|
}
|
|
|
|
applyUserAllowedPaths(reg, []string{ext}) // initial wiring (from config.json)
|
|
applyUserAllowedPaths(reg, []string{ext}) // re-apply after system_configs overlay
|
|
|
|
if res := read.Execute(context.Background(), map[string]any{"path": extFile}); res.IsError {
|
|
t.Fatalf("expected success after repeated allow, got error: %s", res.ForLLM)
|
|
}
|
|
}
|
|
|
|
// An empty allow list is a no-op and must not panic (the common case when no
|
|
// allowed_paths are configured).
|
|
func TestApplyUserAllowedPaths_EmptyIsNoop(t *testing.T) {
|
|
reg := tools.NewRegistry()
|
|
reg.Register(tools.NewReadFileTool(t.TempDir(), true))
|
|
applyUserAllowedPaths(reg, nil)
|
|
applyUserAllowedPaths(reg, []string{})
|
|
}
|