Skip to content

Commit e639d40

Browse files
joaomdmouraclaude
andcommitted
fix(tools): sandbox FileWriterTool writes and fix file tool rough edges
FileReadTool confined reads to the working directory, but FileWriterTool only checked that `filename` stayed inside `directory` — and `directory` itself is an LLM-supplied schema field. An agent could therefore write anywhere the process had permission to, including ~/.ssh and site-packages, while the reader refused to read back what the writer had just written. FileWriterTool was the only filesystem tool in the package that did not go through validate_file_path; files_compressor_tool validates even its output path. Writes are now confined to base_dir (the working directory by default): the resolved directory must sit inside base_dir, and the resolved file must sit inside that directory. The pre-existing filename containment check is kept as-is and still applies even when the unsafe-paths escape hatch is on, so no existing guarantee is weakened. Both tools gain a base_dir field so a developer can widen the sandbox deliberately instead of reaching for the process-wide CREWAI_TOOLS_ALLOW_UNSAFE_PATHS kill switch. FileReadTool also stops rejecting a file_path given to its own constructor: that is developer-declared intent, and declaring one file does not expose its siblings. Also fixed: - FileReadTool scanned the whole file when reading a line window; it now stops via islice once the requested lines are collected. - FileWriterTool._run(**kwargs) made the documented positional call signature raise TypeError and turned a missing overwrite into "error accessing key". It now takes named parameters in the documented (filename, content, directory) order. - A directory naming an existing file reported "already exists and overwrite option was not passed" even with overwrite=True; it now explains the real problem. - Subdirectories inside filename are created, matching what passing directory already did. - Both tools now write and decode UTF-8 by default instead of the platform locale encoding, with an encoding field to override. The docs already claimed UTF-8 and recommended the writer to Windows users. - The writer's schema fields had no descriptions for the LLM. - Docs claimed FileReadTool parses JSON into a dict (it never has), shipped a snippet that raised TypeError, and did not mention the path sandbox. The writer README also began with a stray "Here's the rewritten README" preamble. BREAKING CHANGE: FileWriterTool no longer writes outside the working directory. Pass base_dir to authorize a different tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 97981ed commit e639d40

14 files changed

Lines changed: 648 additions & 88 deletions

File tree

docs/edge/ar/tools/file-document/filereadtool.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mode: "wide"
1111
لا نزال نعمل على تحسين الأدوات، لذا قد يحدث سلوك غير متوقع أو تغييرات في المستقبل.
1212
</Note>
1313

14-
تمثل أداة FileReadTool مفهومياً مجموعة من الوظائف ضمن حزمة crewai_tools تهدف إلى تسهيل قراءة الملفات واسترجاع المحتوى. تتضمن هذه المجموعة أدوات لمعالجة ملفات نصية دفعية، وقراءة ملفات التكوين أثناء التشغيل، واستيراد البيانات للتحليلات. تدعم مجموعة متنوعة من صيغ الملفات النصية مثل `.txt` و `.csv` و `.json` وغيرها. اعتماداً على نوع الملف، توفر المجموعة وظائف متخصصة، مثل تحويل محتوى JSON إلى قاموس Python لسهولة الاستخدام.
14+
تمثل أداة FileReadTool مفهومياً مجموعة من الوظائف ضمن حزمة crewai_tools تهدف إلى تسهيل قراءة الملفات واسترجاع المحتوى. تتضمن هذه المجموعة أدوات لمعالجة ملفات نصية دفعية، وقراءة ملفات التكوين أثناء التشغيل، واستيراد البيانات للتحليلات. تدعم مجموعة متنوعة من صيغ الملفات النصية مثل `.txt` و `.csv` و `.json` وغيرها. يُعاد المحتوى دائماً كنص عادي.
1515

1616
## التثبيت
1717

docs/edge/ar/tools/file-document/filewritetool.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ from crewai_tools import FileWriterTool
3030
file_writer_tool = FileWriterTool()
3131

3232
# Write content to a file in a specified directory
33-
result = file_writer_tool._run('example.txt', 'This is a test content.', 'test_directory')
33+
result = file_writer_tool.run(
34+
filename='example.txt',
35+
content='This is a test content.',
36+
directory='test_directory',
37+
)
3438
print(result)
3539
```
3640

docs/edge/en/tools/file-document/filereadtool.mdx

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ mode: "wide"
1111
We are still working on improving tools, so there might be unexpected behavior or changes in the future.
1212
</Note>
1313

14-
The FileReadTool conceptually represents a suite of functionalities within the crewai_tools package aimed at facilitating file reading and content retrieval.
15-
This suite includes tools for processing batch text files, reading runtime configuration files, and importing data for analytics.
16-
It supports a variety of text-based file formats such as `.txt`, `.csv`, `.json`, and more. Depending on the file type, the suite offers specialized functionality,
17-
such as converting JSON content into a Python dictionary for ease of use.
14+
The `FileReadTool` reads the contents of a file from the local file system and returns it as text.
15+
It is useful for batch text file processing, reading runtime configuration files, and importing data for analytics.
16+
It supports any text-based file format, such as `.txt`, `.csv`, `.json`, and `.md`.
17+
Content is always returned as plain text — parsing it (for example, `json.loads` on a `.json` file) is up to the agent or your own code.
18+
19+
For large files, `start_line` and `line_count` read just a window of lines instead of loading the whole file.
1820

1921
## Installation
2022

@@ -31,15 +33,48 @@ To get started with the FileReadTool:
3133
```python Code
3234
from crewai_tools import FileReadTool
3335

34-
# Initialize the tool to read any files the agents knows or lean the path for
36+
# Initialize the tool to read any file the agent knows or learns the path for
3537
file_read_tool = FileReadTool()
3638

3739
# OR
3840

39-
# Initialize the tool with a specific file path, so the agent can only read the content of the specified file
41+
# Initialize with a specific file path, so the agent reads that file by default
4042
file_read_tool = FileReadTool(file_path='path/to/your/file.txt')
43+
44+
# Read a window of lines (lines 100-149) instead of the whole file
45+
partial_content = file_read_tool.run(
46+
file_path='path/to/your/file.txt',
47+
start_line=100,
48+
line_count=50,
49+
)
4150
```
4251

4352
## Arguments
4453

45-
- `file_path`: The path to the file you want to read. It accepts both absolute and relative paths. Ensure the file exists and you have the necessary permissions to access it.
54+
The agent supplies these at runtime:
55+
56+
- `file_path`: The path to the file you want to read. Accepts absolute and relative paths. Ensure the file exists and you have the necessary permissions to access it.
57+
- `start_line`: (Optional) The line number to start reading from (1-indexed). Defaults to `1`.
58+
- `line_count`: (Optional) The number of lines to read. If omitted, reads from `start_line` to the end of the file.
59+
60+
You set these when constructing the tool:
61+
62+
- `file_path`: (Optional) A default file to read when the agent calls the tool with no arguments.
63+
- `base_dir`: (Optional) The directory that runtime paths must stay inside. Defaults to the current working directory.
64+
- `encoding`: (Optional) Text encoding used to decode the file. Defaults to `utf-8`.
65+
66+
## Allowed paths
67+
68+
Because the file path is usually chosen by an LLM at runtime, reads are confined to a sandbox:
69+
70+
- Paths supplied at runtime must resolve inside `base_dir`, which defaults to the current working directory. `..` segments and symlinks are resolved before the check, so they cannot be used to escape.
71+
- A `file_path` passed to the constructor is developer-declared intent, so it is always readable — even outside `base_dir`. Declaring one file does not expose its siblings.
72+
73+
To let an agent read a directory tree outside the working directory, point `base_dir` at it:
74+
75+
```python Code
76+
# The agent may read anything under /data, and nothing outside it
77+
file_read_tool = FileReadTool(base_dir='/data')
78+
```
79+
80+
As a last resort, setting `CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true` disables path validation. This applies process-wide to every crewai-tools tool, including the SSRF protections on URL-fetching tools, so prefer `base_dir`.

docs/edge/en/tools/file-document/filewritetool.mdx

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mode: "wide"
1111

1212
The `FileWriterTool` is a component of the crewai_tools package, designed to simplify the process of writing content to files with cross-platform compatibility (Windows, Linux, macOS).
1313
It is particularly useful in scenarios such as generating reports, saving logs, creating configuration files, and more.
14-
This tool handles path differences across operating systems, supports UTF-8 encoding, and automatically creates directories if they don't exist, making it easier to organize your output reliably across different platforms.
14+
This tool handles path differences across operating systems, writes UTF-8 by default rather than the platform's locale encoding, and automatically creates directories if they don't exist, making it easier to organize your output reliably across different platforms.
1515

1616
## Installation
1717

@@ -32,15 +32,45 @@ from crewai_tools import FileWriterTool
3232
file_writer_tool = FileWriterTool()
3333

3434
# Write content to a file in a specified directory
35-
result = file_writer_tool._run('example.txt', 'This is a test content.', 'test_directory')
35+
result = file_writer_tool.run(
36+
filename='example.txt',
37+
content='This is a test content.',
38+
directory='test_directory',
39+
)
3640
print(result)
3741
```
3842

3943
## Arguments
4044

41-
- `filename`: The name of the file you want to create or overwrite.
42-
- `content`: The content to write into the file.
43-
- `directory` (optional): The path to the directory where the file will be created. Defaults to the current directory (`.`). If the directory does not exist, it will be created.
45+
The agent supplies these at runtime:
46+
47+
- `filename`: The name of the file to write, relative to `directory`. May include subdirectories, which are created if they don't exist.
48+
- `content`: The text content to write into the file.
49+
- `directory` (optional): The path to the directory where the file will be created. Defaults to the current working directory. If the directory does not exist, it will be created.
50+
- `overwrite` (optional): Whether to replace the file when it already exists. Accepts `true`/`false` (also `yes`/`no`, `on`/`off`, `1`/`0`). Defaults to `false`, which reports an error instead of replacing existing content.
51+
52+
You set these when constructing the tool:
53+
54+
- `base_dir` (optional): The directory that writes must stay inside. Defaults to the current working directory.
55+
- `encoding` (optional): Text encoding used to write the file. Defaults to `utf-8`.
56+
57+
## Allowed paths
58+
59+
Because both the directory and the filename are usually chosen by an LLM at runtime, writes are confined to a sandbox:
60+
61+
- The resolved `directory` must be inside `base_dir`, which defaults to the current working directory.
62+
- The resolved file must then be inside that `directory`. `..` segments, absolute paths, and symlinks are resolved before both checks, so they cannot be used to escape.
63+
64+
To let an agent write outside the working directory, point `base_dir` at the target tree:
65+
66+
```python Code
67+
# The agent may write anywhere under /var/output, and nowhere outside it
68+
file_writer_tool = FileWriterTool(base_dir='/var/output')
69+
```
70+
71+
<Note>
72+
Previously an absolute `directory` could write anywhere the process had permission to. If you relied on that, set `base_dir` to the tree you want to allow. Setting `CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true` restores the old behavior, but it applies process-wide to every crewai-tools tool, including the SSRF protections on URL-fetching tools, so prefer `base_dir`.
73+
</Note>
4474

4575
## Conclusion
4676

docs/edge/ko/tools/file-document/filereadtool.mdx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ mode: "wide"
1313

1414
FileReadTool은 crewai_tools 패키지 내에서 파일 읽기와 콘텐츠 검색을 용이하게 하는 기능 모음입니다.
1515
이 모음에는 배치 텍스트 파일 처리, 런타임 구성 파일 읽기, 분석을 위한 데이터 가져오기 등 다양한 도구가 포함되어 있습니다.
16-
`.txt`, `.csv`, `.json` 등 다양한 텍스트 기반 파일 형식을 지원합니다. 파일 유형에 따라 이 모음은
17-
JSON 콘텐츠를 Python 딕셔너리로 변환하여 사용을 쉽게 하는 등 특화된 기능을 제공합니다.
16+
`.txt`, `.csv`, `.json` 등 다양한 텍스트 기반 파일 형식을 지원합니다. 콘텐츠는 항상 일반 텍스트로 반환됩니다.
1817

1918
## 설치
2019

docs/edge/ko/tools/file-document/filewritetool.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ from crewai_tools import FileWriterTool
3232
file_writer_tool = FileWriterTool()
3333

3434
# Write content to a file in a specified directory
35-
result = file_writer_tool._run('example.txt', 'This is a test content.', 'test_directory')
35+
result = file_writer_tool.run(
36+
filename='example.txt',
37+
content='This is a test content.',
38+
directory='test_directory',
39+
)
3640
print(result)
3741
```
3842

docs/edge/pt-BR/tools/file-document/filereadtool.mdx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ mode: "wide"
1313

1414
O FileReadTool representa conceitualmente um conjunto de funcionalidades dentro do pacote crewai_tools voltadas para facilitar a leitura e a recuperação de conteúdo de arquivos.
1515
Esse conjunto inclui ferramentas para processar arquivos de texto em lote, ler arquivos de configuração em tempo de execução e importar dados para análise.
16-
Ele suporta uma variedade de formatos de arquivo baseados em texto, como `.txt`, `.csv`, `.json` e outros. Dependendo do tipo de arquivo, o conjunto oferece funcionalidades especializadas,
17-
como converter conteúdo JSON em um dicionário Python para facilitar o uso.
16+
Ele suporta uma variedade de formatos de arquivo baseados em texto, como `.txt`, `.csv`, `.json` e outros. O conteúdo é sempre retornado como texto simples.
1817

1918
## Instalação
2019

docs/edge/pt-BR/tools/file-document/filewritetool.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ from crewai_tools import FileWriterTool
3232
file_writer_tool = FileWriterTool()
3333

3434
# Escreva conteúdo em um arquivo em um diretório especificado
35-
result = file_writer_tool._run('example.txt', 'This is a test content.', 'test_directory')
35+
result = file_writer_tool.run(
36+
filename='example.txt',
37+
content='This is a test content.',
38+
directory='test_directory',
39+
)
3640
print(result)
3741
```
3842

lib/crewai-tools/src/crewai_tools/tools/file_read_tool/README.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
## Description
44

5-
The FileReadTool is a versatile component of the crewai_tools package, designed to streamline the process of reading and retrieving content from files. It is particularly useful in scenarios such as batch text file processing, runtime configuration file reading, and data importation for analytics. This tool supports various text-based file formats including `.txt`, `.csv`, `.json`, and adapts its functionality based on the file type, for instance, converting JSON content into a Python dictionary for easy use.
5+
The FileReadTool is a versatile component of the crewai_tools package, designed to streamline the process of reading and retrieving content from files. It is particularly useful in scenarios such as batch text file processing, runtime configuration file reading, and data importation for analytics. This tool supports any text-based file format, including `.txt`, `.csv`, `.json`, and `.md`. Content is always returned as plain text — parsing it (for example, `json.loads` on a `.json` file) is up to the agent or your own code.
66

7-
The tool also supports reading specific chunks of a file by specifying a starting line and the number of lines to read, which is helpful when working with large files that don't need to be loaded entirely into memory.
7+
The tool also supports reading specific chunks of a file by specifying a starting line and the number of lines to read, which is helpful when working with large files that don't need to be loaded entirely into memory. Reading a window stops as soon as the requested lines have been collected, so it does not scan the rest of the file.
88

99
## Installation
1010

@@ -21,12 +21,12 @@ To get started with the FileReadTool:
2121
```python
2222
from crewai_tools import FileReadTool
2323

24-
# Initialize the tool to read any files the agents knows or lean the path for
24+
# Initialize the tool to read any file the agent knows or learns the path for
2525
file_read_tool = FileReadTool()
2626

2727
# OR
2828

29-
# Initialize the tool with a specific file path, so the agent can only read the content of the specified file
29+
# Initialize the tool with a specific file path, so the agent reads that file by default
3030
file_read_tool = FileReadTool(file_path='path/to/your/file.txt')
3131

3232
# Read a specific chunk of the file (lines 100-149)
@@ -35,6 +35,30 @@ partial_content = file_read_tool.run(file_path='path/to/your/file.txt', start_li
3535

3636
## Arguments
3737

38+
The agent supplies these at runtime:
39+
3840
- `file_path`: The path to the file you want to read. It accepts both absolute and relative paths. Ensure the file exists and you have the necessary permissions to access it.
3941
- `start_line`: (Optional) The line number to start reading from (1-indexed). Defaults to 1 (the first line).
4042
- `line_count`: (Optional) The number of lines to read. If not provided, reads from the start_line to the end of the file.
43+
44+
You set these when constructing the tool:
45+
46+
- `file_path`: (Optional) A default file to read when the agent calls the tool with no arguments.
47+
- `base_dir`: (Optional) The directory that runtime paths must stay inside. Defaults to the current working directory.
48+
- `encoding`: (Optional) Text encoding used to decode the file. Defaults to `utf-8`.
49+
50+
## Allowed paths
51+
52+
Because the file path is usually chosen by an LLM at runtime, reads are confined to a sandbox:
53+
54+
- Paths supplied at runtime must resolve inside `base_dir` (the current working directory by default). `..` segments and symlinks are resolved before the check, so they cannot be used to escape.
55+
- A `file_path` passed to the constructor is developer-declared intent, so it is always readable — even outside `base_dir`. Declaring one file does not expose its siblings.
56+
57+
To let an agent read a directory tree outside the working directory, point `base_dir` at it:
58+
59+
```python
60+
# The agent may read anything under /data, and nothing outside it
61+
file_read_tool = FileReadTool(base_dir='/data')
62+
```
63+
64+
Setting `CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true` disables path validation, but it applies process-wide to every crewai-tools tool, including the SSRF protections on URL-fetching tools, so prefer `base_dir`.

0 commit comments

Comments
 (0)