返回 DeepSeek-Reasonix
project_program_paths.go
根目录 / internal / config / project_program_paths.go
1 package config
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7
8 "reasonix/internal/sandbox"
9 )
10
11 // newSingleProgram digests a single program path with the binary's content, so
12 // a program swapped at the same path needs approval again at the next load.
13 func newSingleProgram(kind ProjectProgramKind, key, path, ws string) ProjectProgram {
14 p := NewProjectProgram(kind, key, path, ws, ws, path, nil)
15 if info, err := os.Stat(path); err == nil && info.Mode().IsRegular() && filepath.IsAbs(path) {
16 p.Files = []string{filepath.Clean(path)}
17 p.Digest = p.currentDigest()
18 }
19 return p
20 }
21
22 // namesWritablePath reports a program path that is relative, climbs with "..",
23 // or lies — existing or not — in the workspace, an allow_write root, or a
24 // temporary or toolchain cache directory the bash jail leaves writable. A bare
25 // command name is looked up on PATH, which never searches the workspace.
26 func (c *Config) namesWritablePath(ws, path string) bool {
27 path = strings.TrimSpace(path)
28 if path == "" || !strings.ContainsAny(path, `/\`) {
29 return false
30 }
31 if !filepath.IsAbs(path) || strings.Contains(path, "..") || ws == "" {
32 return true
33 }
34 return inRoots(path, c.writableRoots(ws))
35 }
36
37 // inRoots reports path at or under any of roots, by spelling or by identity.
38 func inRoots(path string, roots []string) bool {
39 resolved, err := evalSymlinksAllowMissing(path)
40 if err != nil {
41 return true
42 }
43 for _, root := range roots {
44 if real, err := evalSymlinksAllowMissing(root); err == nil {
45 root = real
46 }
47 if pathWithinRoot(root, resolved) || sameFileAncestor(root, resolved) {
48 return true
49 }
50 }
51 return false
52 }
53
54 func hostWritableDirs() []string { return sandbox.HostWritableDirs() }
55
56 func (c *Config) writableRoots(ws string) []string {
57 roots := append([]string{ws, os.TempDir()}, hostWritableDirs()...)
58 for _, dir := range c.Sandbox.AllowWrite {
59 if dir = ExpandVars(dir); dir != "" {
60 roots = append(roots, dir)
61 }
62 }
63 return roots
64 }
65
66 // sameFileAncestor asks the filesystem, not the spelling, whether root is path
67 // or one of its ancestors: a case-insensitive volume or a junction spells the
68 // same directory differently.
69 func sameFileAncestor(root, path string) bool {
70 rootInfo, err := os.Stat(root)
71 if err != nil {
72 return false
73 }
74 for dir := path; ; {
75 if info, err := os.Stat(dir); err == nil && os.SameFile(rootInfo, info) {
76 return true
77 }
78 parent := filepath.Dir(dir)
79 if parent == dir {
80 return false
81 }
82 dir = parent
83 }
84 }
85
85 lines GO