From fd18e81364e856084e82fed13d97b5464d7d9314 Mon Sep 17 00:00:00 2001 From: Rip&Tear <84775494+theCyberTech@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:34:14 +0800 Subject: [PATCH] fix: use Path.is_relative_to() for path containment check Replaces startswith(real_directory + os.sep) with Path.is_relative_to(), which does a proper path-component comparison. This avoids the edge case where real_directory == "/" produces a "//" prefix, and is safe on case-insensitive filesystems. Also explicitly rejects the case where the filepath resolves to the directory itself (not a valid file target). Co-Authored-By: Claude Sonnet 4.6 --- .../tools/file_writer_tool/file_writer_tool.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/tools/file_writer_tool/file_writer_tool.py b/lib/crewai-tools/src/crewai_tools/tools/file_writer_tool/file_writer_tool.py index 16e7c261f..4cd3c1566 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/file_writer_tool/file_writer_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/file_writer_tool/file_writer_tool.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from typing import Any from crewai.tools import BaseTool @@ -35,12 +36,17 @@ class FileWriterTool(BaseTool): filepath = os.path.join(directory, filename) - # Prevent path traversal: the resolved path must stay within the - # resolved directory. This blocks ../sequences, absolute paths in + # Prevent path traversal: the resolved path must be strictly inside + # the resolved directory. This blocks ../sequences, absolute paths in # filename, and symlink escapes regardless of how directory is set. - real_directory = os.path.realpath(directory) - real_filepath = os.path.realpath(filepath) - if not real_filepath.startswith(real_directory + os.sep) and real_filepath != real_directory: + # is_relative_to() does a proper path-component comparison that is + # safe on case-insensitive filesystems and avoids the "// " edge case + # that plagues startswith(real_directory + os.sep). + # We also reject the case where filepath resolves to the directory + # itself, since that is not a valid file target. + real_directory = Path(directory).resolve() + real_filepath = Path(filepath).resolve() + if not real_filepath.is_relative_to(real_directory) or real_filepath == real_directory: return "Error: Invalid file path — the filename must not escape the target directory." if kwargs.get("directory"):