From fbb28de0b85ade7cf2a2eaeaa17401ddb3fa8ae7 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Sun, 5 Apr 2026 21:44:03 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20persist.Store=20=E2=80=94=20sanitize=20c?= =?UTF-8?q?allID,=20log=20save=20errors,=20document=20List=20filter=20sema?= =?UTF-8?q?ntics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/tool/persist/store.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/tool/persist/store.go b/internal/tool/persist/store.go index 5371a9c..fa383d9 100644 --- a/internal/tool/persist/store.go +++ b/internal/tool/persist/store.go @@ -2,6 +2,7 @@ package persist import ( "fmt" + "log/slog" "os" "path/filepath" "strings" @@ -45,13 +46,16 @@ func (s *Store) Save(toolName, callID, content string) (string, bool) { return "", false } if err := os.MkdirAll(s.dir, 0o755); err != nil { + slog.Warn("persist: failed to create session directory", "dir", s.dir, "error", err) return "", false } - // Sanitize tool name for filesystem (replace dots and slashes) + // Sanitize tool name and call ID for filesystem (replace dots, slashes, path traversal) safeName := strings.NewReplacer(".", "_", "/", "_").Replace(toolName) - filename := safeName + "-" + callID + ".txt" + safeCallID := strings.NewReplacer("/", "_", "..", "_").Replace(callID) + filename := safeName + "-" + safeCallID + ".txt" path := filepath.Join(s.dir, filename) if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + slog.Warn("persist: failed to write tool result", "path", path, "error", err) return "", false } return path, true @@ -67,8 +71,11 @@ func InlineReplacement(path, content string) string { path, previewSize, preview) } -// List returns all persisted results, optionally filtered by tool name prefix. +// List returns all persisted results, optionally filtered by sanitized tool name prefix. +// Tool names are stored with dots and slashes replaced by underscores — e.g., +// "fs.grep" is stored as "fs_grep". Pass the sanitized form as the filter. // An empty filter returns all results. +// Returns nil (not error) if the session directory does not yet exist. func (s *Store) List(toolNameFilter string) ([]ResultFile, error) { entries, err := os.ReadDir(s.dir) if os.IsNotExist(err) {