Skip to content

fix(crewai-tools): let DirectoryReadTool use a fixed directory outside cwd - #6840

Open
manjunathbhaskar wants to merge 1 commit into
crewAIInc:mainfrom
manjunathbhaskar:fix/directory-read-tool-fixed-dir-outside-cwd
Open

fix(crewai-tools): let DirectoryReadTool use a fixed directory outside cwd#6840
manjunathbhaskar wants to merge 1 commit into
crewAIInc:mainfrom
manjunathbhaskar:fix/directory-read-tool-fixed-dir-outside-cwd

Conversation

@manjunathbhaskar

@manjunathbhaskar manjunathbhaskar commented Aug 6, 2026

Copy link
Copy Markdown

Summary

#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 can't move it), while runtime or LLM supplied paths still go through the containment check.

DirectoryReadTool was not updated to match this pattern. Its _run validates the directory via validate_directory_path(directory) with no base_dir argument, which defaults to os.getcwd(). Any fixed directory passed at construction time that isn't the current working directory was rejected, with an unhandled ValueError, 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:

ValueError: Path 'otherdir' is outside the allowed directory. Set CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true to bypass this check.

Fix

  • Adds a base_dir parameter mirroring FileReadTool, for widening the sandbox for runtime supplied directories.
  • Pins the construction time directory as _declared_realpath, resolved once at __init__ time, which always bypasses the containment check, since this is intent declared by the developer, exactly like FileReadTool's declared file_path, so a later chdir can't move it.
  • Runtime or LLM supplied directories (passed to _run(directory=...) when no fixed directory was configured) still go through validate_directory_path and 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 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 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 with os.path.relpath.
  • Verified not a bug (per review, see thread below): a relative directory anchors to base_dir rather than the construction time cwd. This matches FileReadTool'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_dir and cwd fixtures 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 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 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

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d95ac7e7-af4f-4374-bdcd-dd49d351e854

📥 Commits

Reviewing files that changed from the base of the PR and between 92012ae and c39eec1.

📒 Files selected for processing (2)
  • lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.py
  • lib/crewai-tools/tests/directory_read_tool_test.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/crewai-tools/tests/directory_read_tool_test.py
  • lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.py

📝 Walkthrough

Walkthrough

DirectoryReadTool adds base_dir support, anchors declared directories, validates runtime paths, and formats traversal errors. Tests cover path stability, sandbox boundaries, widened base directories, successful reads, missing paths, and root-path joining.

Changes

Directory Read Path Handling

Layer / File(s) Summary
Path configuration and anchoring
lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.py, lib/crewai-tools/tests/directory_read_tool_test.py
DirectoryReadTool accepts base_dir, normalizes paths, and anchors explicitly declared directories during construction. Tests verify fixed paths remain usable after chdir and root paths do not produce doubled separators.
Sandboxed execution and validation
lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.py, lib/crewai-tools/tests/directory_read_tool_test.py
Runtime paths are checked against base_dir. Directory traversal returns formatted filesystem errors. Tests cover sandbox boundaries, widened base directories, successful reads, and missing directories.

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: allowing DirectoryReadTool to use a fixed directory outside the current working directory.
Description check ✅ Passed The description directly explains the DirectoryReadTool changes, security behavior, error handling, path fixes, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 18c52c4 and 6e8b464.

📒 Files selected for processing (2)
  • lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.py
  • lib/crewai-tools/tests/directory_read_tool_test.py

Comment thread lib/crewai-tools/tests/directory_read_tool_test.py Outdated
@manjunathbhaskar
manjunathbhaskar marked this pull request as draft August 6, 2026 08:25
@manjunathbhaskar
manjunathbhaskar force-pushed the fix/directory-read-tool-fixed-dir-outside-cwd branch from 6e8b464 to 5c163ec Compare August 6, 2026 08:32
@manjunathbhaskar
manjunathbhaskar marked this pull request as ready for review August 6, 2026 08:33
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 18c52c4 and 5c163ec.

📒 Files selected for processing (2)
  • lib/crewai-tools/src/crewai_tools/tools/directory_read_tool/directory_read_tool.py
  • lib/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

@manjunathbhaskar
manjunathbhaskar force-pushed the fix/directory-read-tool-fixed-dir-outside-cwd branch from 5c163ec to b8541f8 Compare August 6, 2026 08:38
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

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
@manjunathbhaskar
manjunathbhaskar force-pushed the fix/directory-read-tool-fixed-dir-outside-cwd branch from b8541f8 to c39eec1 Compare August 8, 2026 10:46
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant