@@ -50,6 +50,24 @@ func isCommandDangerous(cmd string) bool {
5050 return false
5151}
5252
53+ // safeJoin joins repoPath with the user-supplied relative path and ensures the
54+ // result stays within repoPath, preventing path traversal attacks.
55+ func safeJoin (repoPath , rel string ) (string , error ) {
56+ joined := filepath .Join (repoPath , rel )
57+ absRepo , err := filepath .Abs (repoPath )
58+ if err != nil {
59+ return "" , fmt .Errorf ("invalid repo path: %w" , err )
60+ }
61+ absJoined , err := filepath .Abs (joined )
62+ if err != nil {
63+ return "" , fmt .Errorf ("invalid path: %w" , err )
64+ }
65+ if ! strings .HasPrefix (absJoined , absRepo + string (filepath .Separator )) && absJoined != absRepo {
66+ return "" , fmt .Errorf ("path %q is outside the repository" , rel )
67+ }
68+ return absJoined , nil
69+ }
70+
5371// DefaultTools returns all built-in tools available to the agent.
5472func DefaultTools (repoPath string ) []Tool {
5573 return []Tool {
@@ -86,8 +104,8 @@ func BashTool(repoPath string) Tool {
86104 var out bytes.Buffer
87105 c .Stdout = & out
88106 c .Stderr = & out
89- _ = c .Run ()
90- return out .String (), nil
107+ err : = c .Run ()
108+ return out .String (), err
91109 },
92110 }
93111}
@@ -97,7 +115,10 @@ func ReadFileTool(repoPath string) Tool {
97115 Name : "read_file" ,
98116 Description : "Read a file from the repo.\n Args: {\" path\" : \" internal/agent/agent.go\" }" ,
99117 Execute : func (ctx context.Context , args map [string ]string ) (string , error ) {
100- path := filepath .Join (repoPath , args ["path" ])
118+ path , err := safeJoin (repoPath , args ["path" ])
119+ if err != nil {
120+ return "" , err
121+ }
101122 data , err := os .ReadFile (path )
102123 if err != nil {
103124 return "" , fmt .Errorf ("read %s: %w" , args ["path" ], err )
@@ -112,7 +133,10 @@ func WriteFileTool(repoPath string) Tool {
112133 Name : "write_file" ,
113134 Description : "Write or overwrite a file in the repo.\n Args: {\" path\" : \" internal/agent/agent.go\" , \" content\" : \" ...\" }" ,
114135 Execute : func (ctx context.Context , args map [string ]string ) (string , error ) {
115- path := filepath .Join (repoPath , args ["path" ])
136+ path , err := safeJoin (repoPath , args ["path" ])
137+ if err != nil {
138+ return "" , err
139+ }
116140 if isPathProtected (path ) {
117141 return "" , fmt .Errorf ("write to %s is protected" , args ["path" ])
118142 }
@@ -132,7 +156,10 @@ func EditFileTool(repoPath string) Tool {
132156 Name : "edit_file" ,
133157 Description : "Edit a file by replacing oldString with newString.\n Args: {\" path\" : \" file.go\" , \" oldString\" : \" old\" , \" newString\" : \" new\" }" ,
134158 Execute : func (ctx context.Context , args map [string ]string ) (string , error ) {
135- path := filepath .Join (repoPath , args ["path" ])
159+ path , err := safeJoin (repoPath , args ["path" ])
160+ if err != nil {
161+ return "" , err
162+ }
136163 if isPathProtected (path ) {
137164 return "" , fmt .Errorf ("edit %s is protected" , args ["path" ])
138165 }
@@ -194,7 +221,11 @@ func SearchTool(repoPath string) Tool {
194221 }
195222 path := repoPath
196223 if args ["path" ] != "" {
197- path = filepath .Join (repoPath , args ["path" ])
224+ var err error
225+ path , err = safeJoin (repoPath , args ["path" ])
226+ if err != nil {
227+ return "" , err
228+ }
198229 }
199230
200231 c := exec .CommandContext (ctx , "grep" , "-r" , "-n" , pattern , path )
0 commit comments