{"record":{"id":"d728e0dc764dacde","repo":"n8n-io/n8n","slug":"pattern-pattern-escapes-the-base-directory","errorCode":null,"errorMessage":"Pattern \"${pattern}\" escapes the base directory","messagePattern":"Pattern \"(.+?)\" escapes the base directory","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/@n8n/computer-use/src/tools/filesystem/search-files.ts","lineNumber":114,"sourceCode":"\n\t\treturn formatCallToolResult({\n\t\t\tname,\n\t\t\tquery,\n\t\t\tmatches: matches.slice(0, limit),\n\t\t\ttruncated: matches.length > limit,\n\t\t\ttotalMatches: matches.length,\n\t\t});\n\t},\n};\n\ninterface ResolvedFile {\n\tpath: string;\n\tabsolutePath: string;\n}\n\nfunction assertPatternStaysInside(pattern: string): void {\n\tif (pattern.startsWith('/') || pattern.split('/').includes('..')) {\n\t\tthrow new Error(`Pattern \"${pattern}\" escapes the base directory`);\n\t}\n}\n\nasync function grepFile(\n\tfile: ResolvedFile,\n\tregex: RegExp,\n): Promise<Array<{ path: string; lineNumber: number; line: string }>> {\n\ttry {\n\t\tconst stat = await fs.stat(file.absolutePath);\n\t\tif (stat.size > MAX_FILE_SIZE) return [];\n\n\t\tconst buffer = await fs.readFile(file.absolutePath);\n\t\tif (isLikelyBinaryContent(buffer)) return [];\n\n\t\tconst lines = buffer.toString('utf-8').split('\\n');\n\t\tconst hits: Array<{ path: string; lineNumber: number; line: string }> = [];\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tif (regex.test(lines[i])) {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/computer-use/src/tools/filesystem/search-files.ts#L96-L132","documentation":"Thrown by assertPatternStaysInside() (called from the search_files tool) when a glob pattern starts with '/' (absolute path) or contains '..' as a segment. This prevents the search tool from escaping the base directory via the pattern itself, complementing the per-file path resolution guard.","triggerScenarios":"A search_files call with a pattern like '/etc/**' (absolute), '../secrets/**' (traversal), or 'src/../../outside/**'. The check splits on '/' and looks for '..' segments, and checks for a leading '/'.","commonSituations":"Agent constructs a search pattern with an absolute path or with '..' traversal segments, or copies a file path verbatim into the pattern field.","solutions":["Use relative patterns only (e.g. 'src/**/*.ts', '**/*.json')","Remove any leading '/' from the pattern","Remove all '..' segments from the pattern"],"exampleFix":"// before (pattern escapes base directory):\nawait search_files({ pattern: 'ERROR', glob: '../syslog/**' });\n\n// after (use a relative pattern within the base directory):\nawait search_files({ pattern: 'ERROR', glob: 'logs/**/*.log' });","handlingStrategy":"validation","validationCode":"function isPatternSafe(pattern: string): boolean {\n  return !pattern.startsWith('/') && !pattern.split('/').includes('..');\n}\n\n// Before calling search_files:\nif (!isPatternSafe(globPattern)) {\n  throw new Error(`Pattern \"${globPattern}\" must be relative and not contain '..'`);\n}","typeGuard":"function isPatternEscapeError(e: unknown): boolean {\n  return e instanceof Error && e.message.startsWith('Pattern \"') && e.message.includes('escapes the base directory');\n}","tryCatchPattern":null,"preventionTips":["Use relative glob patterns only (e.g. 'src/**/*.ts')","Never use leading '/' or '..' segments in search patterns","Validate patterns with a simple check before passing them to search_files"],"tags":["filesystem","security","validation","computer-use"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}