From 494cc381b5bf390bcd4243627ef12dd78dd50ec6 Mon Sep 17 00:00:00 2001 From: SiYue <2835601846@qq.com> Date: Fri, 24 Apr 2026 23:13:09 +0800 Subject: [PATCH] build(onboard): support codespace placeholder and path checks --- cmd/picoclaw/internal/onboard/command.go | 2 +- scripts/copydir.go | 80 +++++++++++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/cmd/picoclaw/internal/onboard/command.go b/cmd/picoclaw/internal/onboard/command.go index bf8f4104f..3f0ff0d8d 100644 --- a/cmd/picoclaw/internal/onboard/command.go +++ b/cmd/picoclaw/internal/onboard/command.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" ) -//go:generate go run ../../../../scripts/copydir.go ../../../../workspace ./workspace +//go:generate go run ../../../../scripts/copydir.go "${DOLLAR}{codespace}/workspace" ./workspace //go:embed workspace var embeddedFiles embed.FS diff --git a/scripts/copydir.go b/scripts/copydir.go index 74eff6c72..35622ab17 100644 --- a/scripts/copydir.go +++ b/scripts/copydir.go @@ -5,6 +5,7 @@ import ( "io" "os" "path/filepath" + "strings" ) func main() { @@ -13,8 +14,36 @@ func main() { os.Exit(2) } - src := os.Args[1] - dst := os.Args[2] + repoRoot, err := findRepoRoot() + if err != nil { + fmt.Fprintf(os.Stderr, "locate repo root: %v\n", err) + os.Exit(1) + } + + src, err := normalizePathArg(os.Args[1], repoRoot) + if err != nil { + fmt.Fprintf(os.Stderr, "resolve src path: %v\n", err) + os.Exit(1) + } + + dst, err := normalizePathArg(os.Args[2], repoRoot) + if err != nil { + fmt.Fprintf(os.Stderr, "resolve dst path: %v\n", err) + os.Exit(1) + } + + if err := ensurePathWithinRepo(repoRoot, src); err != nil { + fmt.Fprintf(os.Stderr, "invalid src path: %v\n", err) + os.Exit(1) + } + if err := ensurePathWithinRepo(repoRoot, dst); err != nil { + fmt.Fprintf(os.Stderr, "invalid dst path: %v\n", err) + os.Exit(1) + } + if samePath(repoRoot, dst) { + fmt.Fprintln(os.Stderr, "invalid dst path: destination cannot be repo root") + os.Exit(1) + } if err := os.RemoveAll(dst); err != nil { fmt.Fprintf(os.Stderr, "remove %s: %v\n", dst, err) @@ -27,6 +56,53 @@ func main() { } } +func findRepoRoot() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + + cur, err := filepath.Abs(wd) + if err != nil { + return "", err + } + + for { + if _, err := os.Stat(filepath.Join(cur, ".git")); err == nil { + return filepath.Clean(cur), nil + } + parent := filepath.Dir(cur) + if parent == cur { + return "", fmt.Errorf("could not find .git from %s", wd) + } + cur = parent + } +} + +func normalizePathArg(arg, repoRoot string) (string, error) { + resolved := strings.ReplaceAll(arg, "${codespace}", repoRoot) + abs, err := filepath.Abs(resolved) + if err != nil { + return "", err + } + return filepath.Clean(abs), nil +} + +func ensurePathWithinRepo(repoRoot, path string) error { + rel, err := filepath.Rel(repoRoot, path) + if err != nil { + return err + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return fmt.Errorf("path %s is outside repository root %s", path, repoRoot) + } + return nil +} + +func samePath(a, b string) bool { + return filepath.Clean(a) == filepath.Clean(b) +} + func copyTree(src, dst string) error { info, err := os.Stat(src) if err != nil {