1
1
import { z } from 'zod'
2
+ import path from 'path'
2
3
import { StructuredTool , ToolParams } from '@langchain/core/tools'
3
4
import { Serializable } from '@langchain/core/load/serializable'
4
- import { NodeFileStore } from 'langchain/stores/file/node'
5
5
import { INode , INodeData , INodeParams } from '../../../src/Interface'
6
- import { getBaseClasses } from '../../../src/utils'
6
+ import { getBaseClasses , getUserHome } from '../../../src/utils'
7
+ import { SecureFileStore , FileSecurityConfig } from '../../../src/SecureFileStore'
7
8
8
9
abstract class BaseFileStore extends Serializable {
9
10
abstract readFile ( path : string ) : Promise < string >
@@ -20,30 +21,91 @@ class WriteFile_Tools implements INode {
20
21
category : string
21
22
baseClasses : string [ ]
22
23
inputs : INodeParams [ ]
24
+ warning : string
23
25
24
26
constructor ( ) {
25
27
this . label = 'Write File'
26
28
this . name = 'writeFile'
27
- this . version = 1 .0
29
+ this . version = 2 .0
28
30
this . type = 'WriteFile'
29
31
this . icon = 'writefile.svg'
30
32
this . category = 'Tools'
33
+ this . warning = 'This tool can be used to write files to the disk. It is recommended to use this tool with caution.'
31
34
this . description = 'Write file to disk'
32
35
this . baseClasses = [ this . type , 'Tool' , ...getBaseClasses ( WriteFileTool ) ]
33
36
this . inputs = [
34
37
{
35
- label : 'Base Path' ,
36
- name : 'basePath ' ,
37
- placeholder : `C:\\Users\\User\\Desktop ` ,
38
+ label : 'Workspace Path' ,
39
+ name : 'workspacePath ' ,
40
+ placeholder : `C:\\Users\\User\\MyProject ` ,
38
41
type : 'string' ,
42
+ description : 'Base workspace directory for file operations. All file paths will be relative to this directory.' ,
43
+ optional : true
44
+ } ,
45
+ {
46
+ label : 'Enforce Workspace Boundaries' ,
47
+ name : 'enforceWorkspaceBoundaries' ,
48
+ type : 'boolean' ,
49
+ description : 'When enabled, restricts file access to the workspace directory for security. Recommended: true' ,
50
+ default : true ,
51
+ optional : true
52
+ } ,
53
+ {
54
+ label : 'Max File Size (MB)' ,
55
+ name : 'maxFileSize' ,
56
+ type : 'number' ,
57
+ description : 'Maximum file size in megabytes that can be written' ,
58
+ default : 10 ,
59
+ optional : true
60
+ } ,
61
+ {
62
+ label : 'Allowed Extensions' ,
63
+ name : 'allowedExtensions' ,
64
+ type : 'string' ,
65
+ description : 'Comma-separated list of allowed file extensions (e.g., .txt,.json,.md). Leave empty to allow all.' ,
66
+ placeholder : '.txt,.json,.md,.py,.js' ,
39
67
optional : true
40
68
}
41
69
]
42
70
}
43
71
44
72
async init ( nodeData : INodeData ) : Promise < any > {
45
- const basePath = nodeData . inputs ?. basePath as string
46
- const store = basePath ? new NodeFileStore ( basePath ) : new NodeFileStore ( )
73
+ const workspacePath = nodeData . inputs ?. workspacePath as string
74
+ const enforceWorkspaceBoundaries = nodeData . inputs ?. enforceWorkspaceBoundaries !== false // Default to true
75
+ const maxFileSize = nodeData . inputs ?. maxFileSize as number
76
+ const allowedExtensions = nodeData . inputs ?. allowedExtensions as string
77
+
78
+ // Parse allowed extensions
79
+ const allowedExtensionsList = allowedExtensions ? allowedExtensions . split ( ',' ) . map ( ( ext ) => ext . trim ( ) . toLowerCase ( ) ) : [ ]
80
+
81
+ let store : BaseFileStore
82
+
83
+ if ( workspacePath ) {
84
+ // Create secure file store with workspace boundaries
85
+ const config : FileSecurityConfig = {
86
+ workspacePath,
87
+ enforceWorkspaceBoundaries,
88
+ maxFileSize : maxFileSize ? maxFileSize * 1024 * 1024 : undefined , // Convert MB to bytes
89
+ allowedExtensions : allowedExtensionsList . length > 0 ? allowedExtensionsList : undefined
90
+ }
91
+ store = new SecureFileStore ( config )
92
+ } else {
93
+ // Fallback to current working directory with security warnings
94
+ if ( enforceWorkspaceBoundaries ) {
95
+ const fallbackWorkspacePath = path . join ( getUserHome ( ) , '.flowise' )
96
+ console . warn ( `[WriteFile] No workspace path specified, using ${ fallbackWorkspacePath } with security restrictions` )
97
+ store = new SecureFileStore ( {
98
+ workspacePath : fallbackWorkspacePath ,
99
+ enforceWorkspaceBoundaries : true ,
100
+ maxFileSize : maxFileSize ? maxFileSize * 1024 * 1024 : undefined ,
101
+ allowedExtensions : allowedExtensionsList . length > 0 ? allowedExtensionsList : undefined
102
+ } )
103
+ } else {
104
+ console . warn ( '[WriteFile] SECURITY WARNING: Workspace boundaries disabled - unrestricted file access enabled' )
105
+ store = SecureFileStore . createUnsecure ( )
106
+ }
107
+ }
108
+
47
109
return new WriteFileTool ( { store } )
48
110
}
49
111
}
@@ -68,7 +130,7 @@ export class WriteFileTool extends StructuredTool {
68
130
69
131
name = 'write_file'
70
132
71
- description = 'Write file from disk'
133
+ description = 'Write file to disk'
72
134
73
135
store : BaseFileStore
74
136
@@ -80,7 +142,7 @@ export class WriteFileTool extends StructuredTool {
80
142
81
143
async _call ( { file_path, text } : z . infer < typeof this . schema > ) {
82
144
await this . store . writeFile ( file_path , text )
83
- return ' File written to successfully.'
145
+ return ` File written to ${ file_path } successfully.`
84
146
}
85
147
}
86
148
0 commit comments