Files
goclaw/cmd/gateway_tools_wiring_test.go
Zezae Oh ce0472b580 fix(gateway): re-apply allowed_paths to filesystem tools after system_configs overlay (#1274)
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.
2026-06-24 15:26:26 +07:00

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{})
}