crewAIInc/crewAI · error · ValueError
Symlink escapes allowed directory: {self.path}
Error message
Symlink escapes allowed directory: {self.path} What it means
Validation error from FilePath: the path is a symlink, and resolving it yields a location that does not start with the current working directory (Path.cwd().resolve()). The guard is string-prefix based against CWD, not against an explicit allow-list, so even a safe symlink inside your project fails whenever your process CWD is elsewhere (e.g. server started from / while files live in /app). It exists to stop symlink-based escape of the allowed directory.
Source
Thrown at lib/crewai-files/src/crewai_files/core/sources.py:231
description="Maximum file size in bytes.",
)
_content: bytes | None = PrivateAttr(default=None)
_content_type: str = PrivateAttr()
@model_validator(mode="after")
def _validate_file_exists(self) -> FilePath:
"""Validate that the file exists, is secure, and within size limits."""
from crewai_files.processing.exceptions import FileTooLargeError
path_str = str(self.path)
if ".." in path_str:
raise ValueError(f"Path traversal not allowed: {self.path}")
if self.path.is_symlink():
resolved = self.path.resolve()
cwd = Path.cwd().resolve()
if not str(resolved).startswith(str(cwd)):
raise ValueError(f"Symlink escapes allowed directory: {self.path}")
if not self.path.exists():
raise ValueError(f"File not found: {self.path}")
if not self.path.is_file():
raise ValueError(f"Path is not a file: {self.path}")
actual_size = self.path.stat().st_size
if actual_size > self.max_size_bytes:
raise FileTooLargeError(
f"File exceeds max size ({actual_size} > {self.max_size_bytes})",
file_name=str(self.path),
actual_size=actual_size,
max_size=self.max_size_bytes,
)
self._content_type = detect_content_type_from_path(self.path, self.path.name)
return self
View on GitHub (pinned to 754d7323be)
Solutions
- chdir to the intended root (or launch the process with the correct working directory) so CWD is a prefix of the resolved symlink target.
- Replace symlinks with real files or copy the target into the project.
- Pass a direct, non-symlink path to the real file's location if it is reachable.
Example fix
# before (service runs with cwd=/):
src = FilePath(path=Path("/app/data/linked.txt")) # symlink -> /app/assets/x.txt
# after:
import os
os.chdir("/app") # or fix the unit file WorkingDirectory=/app
src = FilePath(path=Path("data/linked.txt")) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def symlink_ok(p: Path) -> bool:
if not p.is_symlink():
return True
return str(p.resolve()).startswith(str(Path.cwd().resolve())) Try / catch
try:
FilePath(path=p)
except ValidationError as e:
if "Symlink escapes" in str(e):
FilePath(path=p.resolve()) # use the real target directly Prevention
- Set the process working directory (or container WORKDIR / systemd WorkingDirectory) to the app root.
- Avoid symlinks in user-data directories; copy files instead.
- The allow-list is CWD-based — validate against Path.cwd() in tests too.
When it happens
Trigger: FilePath(path=Path("data/linked.txt")) where linked.txt -> /etc/secret, or a legitimate in-project symlink while the process was started from a directory that is not a prefix of the resolved target (systemd services, Docker WORKDIR mismatches).
Common situations: Monorepo symlinks to shared assets; Docker containers where CWD differs from the app dir; daemon processes launched from /; symlinked home-dir configs.
Related errors
- Path traversal not allowed: {self.path}
- Invalid skill reference: org and name must be single, non-em
- Blocked unsupported tar member: {member.name!r}
- Blocked link target escaping destination: {member.name!r} ->
- [tool.crewai] definition must resolve inside the project roo
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/3687a8ef9af019b1.
Report an issue: GitHub.