package tools import ( "fmt" "os" "path/filepath" "strings" ) func resolvePath(baseDir, path string) (string, error) { resolved, canonicalBase, err := resolveLexicalPath(baseDir, path) if err != nil { return "", err } canonical, err := filepath.EvalSymlinks(resolved) if err != nil { return "", err } if !isWithinDir(canonical, canonicalBase) { return "", fmt.Errorf("path escapes the project directory through a symbolic link: %s", path) } return canonical, nil } // resolvePathForWrite resolves a path that may not exist yet while preventing // existing parent-directory symlinks from escaping the project directory. func resolvePathForWrite(baseDir, path string) (string, error) { resolved, canonicalBase, err := resolveLexicalPath(baseDir, path) if err != nil { return "", err } existing := resolved var suffix []string for { _, err := os.Lstat(existing) if err == nil { break } if !os.IsNotExist(err) { return "", err } parent := filepath.Dir(existing) if parent == existing { return "", err } suffix = append(suffix, filepath.Base(existing)) existing = parent } canonical, err := filepath.EvalSymlinks(existing) if err != nil { return "", err } for i := len(suffix) - 1; i >= 0; i-- { canonical = filepath.Join(canonical, suffix[i]) } if !isWithinDir(canonical, canonicalBase) { return "", fmt.Errorf("path escapes the project directory through a symbolic link: %s", path) } return canonical, nil } func resolveLexicalPath(baseDir, path string) (string, string, error) { if path == "" { path = "." } if filepath.IsAbs(path) { return "", "", fmt.Errorf("absolute paths are not allowed; use a path relative to the project directory") } absBase, err := filepath.Abs(baseDir) if err != nil { return "", "", err } canonicalBase, err := filepath.EvalSymlinks(absBase) if err != nil { return "", "", err } resolved := filepath.Clean(filepath.Join(absBase, path)) if !isWithinDir(resolved, absBase) { return "", "", fmt.Errorf("path escapes the project directory: %s", path) } return resolved, canonicalBase, nil } func isWithinDir(path, dir string) bool { rel, err := filepath.Rel(dir, path) if err != nil { return false } if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { return false } return true }