返回 DeepSeek-Reasonix
remote_legacy_cleanup.go
根目录 / desktop / remote_legacy_cleanup.go
1 package main
2
3 import (
4 "fmt"
5 "io/fs"
6 "os"
7 "path/filepath"
8 "strings"
9
10 "reasonix/internal/config"
11 )
12
13 // LegacyWorkbenchDataView is a path-free summary of the files the removed
14 // Remote Workbench left behind. Mirrors are counted by file; sizes are summed
15 // recursively. The full paths never cross to the frontend.
16 type LegacyWorkbenchDataView struct {
17 MirrorCount int `json:"mirrorCount"`
18 MirrorBytes int64 `json:"mirrorBytes"`
19 TrustFile bool `json:"trustFile"`
20 }
21
22 // remoteLegacyWorkbenchMirrorDir is the fixed Reasonix private directory that
23 // the removed Remote Workbench mirror wrote session snapshots into.
24 func remoteLegacyWorkbenchMirrorDir() string {
25 return filepath.Join(config.MemoryUserDir(), "remote-mirrors")
26 }
27
28 // remoteLegacyWorkbenchTrustPath is the fixed Reasonix private file that held
29 // per-host Provider authorization for the removed Remote Workbench.
30 func remoteLegacyWorkbenchTrustPath() string {
31 return filepath.Join(config.MemoryUserDir(), "remote-provider-trust.json")
32 }
33
34 // ScanRemoteLegacyWorkbenchData reports whether legacy Remote Workbench files
35 // exist. Read-only: it never deletes anything, and it surfaces no filesystem
36 // paths. Historical source=remote usage statistics are deliberately not part
37 // of this scan.
38 func (a *App) ScanRemoteLegacyWorkbenchData() LegacyWorkbenchDataView {
39 view := LegacyWorkbenchDataView{}
40 root := remoteLegacyWorkbenchMirrorDir()
41 _ = filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
42 if err != nil {
43 return nil // missing/unreadable mirror tree counts as empty
44 }
45 if entry.Type()&os.ModeSymlink != 0 {
46 return filepath.SkipDir // never follow symlinks into foreign trees
47 }
48 if entry.IsDir() {
49 return nil
50 }
51 info, err := entry.Info()
52 if err != nil {
53 return nil
54 }
55 view.MirrorCount++
56 view.MirrorBytes += info.Size()
57 return nil
58 })
59 if info, err := os.Lstat(remoteLegacyWorkbenchTrustPath()); err == nil &&
60 info.Mode().IsRegular() && info.Mode()&os.ModeSymlink == 0 {
61 view.TrustFile = true
62 }
63 return view
64 }
65
66 // CleanRemoteLegacyWorkbenchData deletes one legacy artifact family: "mirrors"
67 // removes the remote-mirrors tree, "trust" removes remote-provider-trust.json.
68 // Only these two whitelisted targets inside the Reasonix private directory are
69 // accepted; symlinks and path escapes are rejected so cleanup can never reach
70 // outside the fixed data root.
71 func (a *App) CleanRemoteLegacyWorkbenchData(target string) error {
72 target = strings.TrimSpace(strings.ToLower(target))
73 switch target {
74 case "mirrors":
75 path := remoteLegacyWorkbenchMirrorDir()
76 info, err := os.Lstat(path)
77 if err != nil {
78 if os.IsNotExist(err) {
79 return nil
80 }
81 return fmt.Errorf("inspect legacy workbench mirrors: %w", err)
82 }
83 if info.Mode()&os.ModeSymlink != 0 {
84 return fmt.Errorf("legacy workbench mirrors path is a symlink; refusing to clean")
85 }
86 if !withinReasonixPrivateDir(path) {
87 return fmt.Errorf("legacy workbench mirrors path escapes the Reasonix data directory")
88 }
89 return os.RemoveAll(path)
90 case "trust":
91 path := remoteLegacyWorkbenchTrustPath()
92 info, err := os.Lstat(path)
93 if err != nil {
94 if os.IsNotExist(err) {
95 return nil
96 }
97 return fmt.Errorf("inspect legacy provider trust file: %w", err)
98 }
99 if info.Mode()&os.ModeSymlink != 0 {
100 return fmt.Errorf("legacy provider trust file is a symlink; refusing to clean")
101 }
102 if !withinReasonixPrivateDir(path) {
103 return fmt.Errorf("legacy provider trust path escapes the Reasonix data directory")
104 }
105 return os.Remove(path)
106 default:
107 return fmt.Errorf("unknown legacy workbench data target %q", target)
108 }
109 }
110
111 // withinReasonixPrivateDir verifies path resolves lexically inside the fixed
112 // Reasonix private state directory. MemoryUserDir is derived from the same
113 // env-scoped home the legacy artifacts were written into.
114 func withinReasonixPrivateDir(path string) bool {
115 root := strings.TrimSpace(config.MemoryUserDir())
116 if root == "" {
117 return false
118 }
119 rel, err := filepath.Rel(root, filepath.Clean(path))
120 if err != nil || rel == "." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || rel == ".." {
121 return false
122 }
123 return true
124 }
125
125 lines GO