safe_path.go
1 package tools 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/Kocoro-lab/ShanClaw/internal/cwdctx" 10 ) 11 12 // ExpandHome expands a leading ~ in a path to the user's home directory. 13 // Returns the path unchanged if it doesn't start with ~. 14 func ExpandHome(path string) string { 15 if !strings.HasPrefix(path, "~") { 16 return path 17 } 18 home, err := os.UserHomeDir() 19 if err != nil { 20 return path 21 } 22 if path == "~" { 23 return home 24 } 25 if strings.HasPrefix(path, "~/") { 26 return filepath.Join(home, path[2:]) 27 } 28 return path 29 } 30 31 // isPathUnderSessionCWD returns true if the given path resolves to a location 32 // under the session CWD from context. Falls back to isPathUnderCWD if no 33 // session CWD is set. 34 func isPathUnderSessionCWD(ctx context.Context, path string) bool { 35 if cwdctx.FromContext(ctx) != "" { 36 return cwdctx.IsUnderSessionCWD(ctx, path) 37 } 38 return isPathUnderCWD(path) 39 } 40 41 // isPathUnderCWD returns true if the given path resolves to a location 42 // under the current working directory. Used by read-only tools to 43 // auto-approve safe paths. 44 func isPathUnderCWD(path string) bool { 45 if path == "" || path == "." { 46 return true 47 } 48 49 cwd, err := os.Getwd() 50 if err != nil { 51 return false 52 } 53 54 // Expand ~ before resolving 55 path = ExpandHome(path) 56 57 // Resolve the path to absolute 58 absPath := path 59 if !filepath.IsAbs(path) { 60 absPath = filepath.Join(cwd, path) 61 } 62 absPath = filepath.Clean(absPath) 63 64 cwdClean := filepath.Clean(cwd) 65 if absPath == cwdClean { 66 return true 67 } 68 return strings.HasPrefix(absPath, cwdClean+string(filepath.Separator)) 69 }