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 <noreply@anthropic.com>
This commit is contained in:
Rip&Tear
2026-03-16 00:34:14 +08:00
parent 96383abd8b
commit fd18e81364

View File

@@ -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"):