@@ -42,12 +42,31 @@ export const allTools: Anthropic.Tool[] = [
4242
4343export const writeOnlyTools : Anthropic . Tool [ ] = [ allTools [ 2 ] ] ;
4444
45- function executeTool ( name : string , input : Record < string , string > , listFilesFn : ( dir : string ) => string [ ] ) : string {
45+ function resolveSafe ( allowedRoot : string , rawPath : string ) : string | null {
46+ const root = path . resolve ( allowedRoot ) ;
47+ const resolved = path . resolve ( root , rawPath ) ;
48+ if ( resolved !== root && ! resolved . startsWith ( root + path . sep ) ) return null ;
49+ return resolved ;
50+ }
51+
52+ function executeTool ( name : string , input : Record < string , string > , listFilesFn : ( dir : string ) => string [ ] , allowedRoot : string ) : string {
4653 switch ( name ) {
47- case "list_files" : return JSON . stringify ( listFilesFn ( input . directory ) ) ;
48- case "read_file" : return readFile ( input . file_path ) ;
49- case "write_file" : return writeFile ( input . file_path , input . content ) ;
50- default : return `Unknown tool: ${ name } ` ;
54+ case "list_files" : {
55+ const dir = resolveSafe ( allowedRoot , input . directory ) ;
56+ if ( ! dir ) return "Error: Path outside project root" ;
57+ return JSON . stringify ( listFilesFn ( dir ) ) ;
58+ }
59+ case "read_file" : {
60+ const file = resolveSafe ( allowedRoot , input . file_path ) ;
61+ if ( ! file ) return "Error: Path outside project root" ;
62+ return readFile ( file ) ;
63+ }
64+ case "write_file" : {
65+ const file = resolveSafe ( allowedRoot , input . file_path ) ;
66+ if ( ! file ) return "Error: Path outside project root" ;
67+ return writeFile ( file , input . content ) ;
68+ }
69+ default : return `Unknown tool: ${ name } ` ;
5170 }
5271}
5372
@@ -67,6 +86,7 @@ export async function runAgent(
6786 userMessage : string ,
6887 tools : Anthropic . Tool [ ] ,
6988 listFilesFn : ( dir : string ) => string [ ] ,
89+ allowedRoot : string ,
7090 review = false ,
7191) {
7292 const messages : Anthropic . MessageParam [ ] = [ { role : "user" , content : userMessage } ] ;
@@ -91,19 +111,24 @@ export async function runAgent(
91111 const input = tool . input as Record < string , string > ;
92112
93113 if ( review && tool . name === "write_file" ) {
94- const label = path . basename ( input . file_path ) ;
95- const result = await reviewContent ( label , input . content ) ;
96- if ( result === "accept" ) {
97- toolCall ( "write_file" , input ) ;
98- toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : writeFile ( input . file_path , input . content ) } ) ;
99- } else if ( result === "skip" ) {
100- toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : "User skipped this write — do not write this file." } ) ;
114+ const resolvedPath = resolveSafe ( allowedRoot , input . file_path ) ;
115+ if ( ! resolvedPath ) {
116+ toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : "Error: Path outside project root" } ) ;
101117 } else {
102- toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : `User requested changes: ${ result } \nPlease revise and call write_file again with the updated content.` } ) ;
118+ const label = path . basename ( resolvedPath ) ;
119+ const result = await reviewContent ( label , input . content ) ;
120+ if ( result === "accept" ) {
121+ toolCall ( "write_file" , input ) ;
122+ toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : writeFile ( resolvedPath , input . content ) } ) ;
123+ } else if ( result === "skip" ) {
124+ toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : "User skipped this write — do not write this file." } ) ;
125+ } else {
126+ toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : `User requested changes: ${ result } \nPlease revise and call write_file again with the updated content.` } ) ;
127+ }
103128 }
104129 } else {
105130 toolCall ( tool . name , input ) ;
106- toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : executeTool ( tool . name , input , listFilesFn ) } ) ;
131+ toolResults . push ( { type : "tool_result" , tool_use_id : tool . id , content : executeTool ( tool . name , input , listFilesFn , allowedRoot ) } ) ;
107132 }
108133 }
109134
0 commit comments