fix(crewai-tools): let DirectoryReadTool use a fixed directory outside cwd - #6840
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthrough
ChangesDirectory Read Path Handling
Sequence Diagram(s)sequenceDiagram
participant Caller
participant DirectoryReadTool
participant Filesystem
Caller->>DirectoryReadTool: Provide directory or runtime path
DirectoryReadTool->>DirectoryReadTool: Anchor declared path or validate runtime path against base_dir
DirectoryReadTool->>Filesystem: Recursively list files
Filesystem-->>DirectoryReadTool: File entries or OSError
DirectoryReadTool-->>Caller: File listing or formatted error
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.py`:
- Around line 101-105: Update the path construction in DirectoryReadTool to
preserve the root directory instead of trimming any trailing slash, and replace
string-based directory removal with os.path.relpath() followed by
os.path.join(). Ensure os.walk() receives "/" unchanged and nested paths remain
correct when directory text appears multiple times.
- Around line 103-110: Update the directory traversal in the tool’s main read
method to pass an os.walk onerror callback, collect any traversal OSError, and
return the existing formatted error result after iteration when one is reported;
retain handling for directly raised OSError. Add a focused unit test that forces
an os.walk traversal error and asserts the tool returns the expected error
string.
- Around line 73-76: Update DirectoryReadTool’s declared-path initialization to
always resolve directory with os.path.realpath(directory), without using
base_dir or the current working directory fallback. Preserve base_dir
exclusively for runtime-supplied paths, and add a regression test covering a
relative declared directory with a different base_dir.
In `@lib/crewai-tools/tests/directory_read_tool_test.py`:
- Around line 36-46: Update test_fixed_directory_outside_cwd_is_listed so it
changes into a newly created sibling working directory rather than
outside_dir.parent before constructing DirectoryReadTool. Keep the declared
outside_dir unchanged and continue asserting that _run lists a.txt and b.txt
without an error.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 80e7c3e6-e79c-4645-aa6f-a77542f55770
📒 Files selected for processing (2)
lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.pylib/crewai-tools/tests/directory_read_tool_test.py
6e8b464 to
5c163ec
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.py`:
- Around line 100-105: Update the path construction in the files_list
comprehension to use os.path.join with directory and os.path.relpath instead of
string concatenation, preserving a single root separator and the native path
separator.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a0767b8a-a8a0-45d3-a0b8-959a2d1ee459
📒 Files selected for processing (2)
lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.pylib/crewai-tools/tests/directory_read_tool_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/crewai-tools/tests/directory_read_tool_test.py
5c163ec to
b8541f8
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
…e cwd PR crewAIInc#6248 confined file tools to an allow listed root to block path traversal. FileReadTool and FileWriterTool pin a declared path from construction time as always allowed developer intent, resolved once so a later chdir cannot move it, while runtime or LLM supplied paths still go through the containment check. DirectoryReadTool was not updated to match. Its _run validated the directory via validate_directory_path(directory) with no base_dir, which defaults to os.getcwd(). Any fixed directory passed at construction time that is not the current working directory was rejected, and with an unhandled ValueError instead of a graceful error string like its sibling tools return. This broke the tool's own documented use case, DirectoryReadTool(directory="/some/dir"), whenever that directory was not the process's cwd. Fix: Adds a base_dir parameter mirroring FileReadTool, for widening the sandbox for runtime supplied directories. Pins the construction time directory as _declared_realpath so it always bypasses the containment check, since this is intent declared by the developer, exactly like FileReadTool's declared file_path, so a later chdir cannot move it. Runtime or LLM supplied directories still go through validate_directory_path and remain fully sandboxed, no change to the actual security boundary crewAIInc#6248 introduced. os.walk() does not raise on its own, it only calls onerror if given one, then moves on. A missing or unreadable directory previously returned an empty, misleadingly successful listing instead of an error. Now passes onerror to actually capture and report top level walk failures. Also fixed while touching this code, both predate this PR: a trailing slash of exactly "/" collapsed to an empty string and made os.walk list the cwd instead of the filesystem root, and file paths were built with string replace on the directory prefix, which corrupts paths through any subdirectory whose name repeats the parent directory's name. Both are replaced with os.path.relpath. Verified not a bug: a relative directory anchors to base_dir rather than the construction time cwd. This matches FileReadTool's own tested behavior, since anchoring to cwd instead would let the same relative name mean two different files depending on when it runs. Test plan: New tests in lib/crewai-tools/tests/directory_read_tool_test.py, no tests existed for this tool before. outside_dir and cwd are built from independent tmp_path_factory roots so a fixed directory can never accidentally be nested under the test's cwd, since that would make the test pass on the old, broken code too. Covers: fixed directory outside cwd is listed, fixed directory survives a later chdir, runtime directory outside base_dir is still rejected, runtime directory inside cwd still works, base_dir widens the runtime sandbox, missing fixed directory returns a graceful error instead of raising, and a permission denied directory is now reported as an error instead of an empty listing. tests/directory_read_tool_test.py tests/file_read_tool_test.py tests/tools/test_file_writer_tool.py tests/utilities/test_safe_path.py 102 passed
b8541f8 to
c39eec1
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
Summary
#6248 confined file tools to an allow listed root to block path traversal.
FileReadToolandFileWriterToolpin a declared path from construction time as always allowed developer intent (resolved once so a laterchdircan't move it), while runtime or LLM supplied paths still go through the containment check.DirectoryReadToolwas not updated to match this pattern. Its_runvalidates the directory viavalidate_directory_path(directory)with nobase_dirargument, which defaults toos.getcwd(). Any fixeddirectorypassed at construction time that isn't the current working directory was rejected, with an unhandledValueError, unlike its sibling tools, which catch this and return a graceful error string.This broke the tool's own documented use case,
DirectoryReadTool(directory="/some/dir"), whenever that directory wasn't the process's cwd:Fix
base_dirparameter mirroringFileReadTool, for widening the sandbox for runtime supplied directories.directoryas_declared_realpath, resolved once at__init__time, which always bypasses the containment check, since this is intent declared by the developer, exactly likeFileReadTool's declaredfile_path, so a laterchdircan't move it._run(directory=...)when no fixed directory was configured) still go throughvalidate_directory_pathand remain fully sandboxed. No change to the actual security boundary fix: confine file tools to an allow-listed root to block path traversal #6248 introduced.os.walk()does not raise on its own, it only callsonerrorif given one, then moves on. A missing or unreadable directory previously returned an empty, misleadingly successful listing instead of an error. Now passesonerrorto actually capture and report top level walk failures./collapsed to an empty string and madeos.walklist the cwd instead of the filesystem root, and file paths were built with a string replace on the directory prefix, which corrupts paths through any subdirectory whose name repeats the parent directory's name (for example.../data/data/file.txt). Both are replaced withos.path.relpath.base_dirrather than the construction time cwd. This matchesFileReadTool's own tested behavior (test_relative_declared_path_anchors_to_base_dir), since anchoring to cwd instead would let the same relative name mean two different files depending on when it runs.Test plan
New tests in
lib/crewai-tools/tests/directory_read_tool_test.py(no tests existed for this tool before).outside_dirandcwdfixtures are built from independenttmp_path_factoryroots so a fixed directory can never accidentally be nested under the test's cwd, since that would let the test pass on the old, broken code too.Covers: fixed directory outside cwd is listed, fixed directory survives a later
chdir, runtime directory outsidebase_diris still rejected, runtime directory inside cwd still works,base_dirwidens the runtime sandbox, missing fixed directory returns a graceful error instead of raising, and a permission denied directory is now reported as an error instead of an empty listing.