python-poetry/poetry · critical · ValueError
Attempting to write {path} outside of the target directory T
Error message
Attempting to write {path} outside of the target directory
Target directory: {target_dir}
Target path: {target_path_str} What it means
Raised by WheelDestination.write_to_fs at src/poetry/installation/wheel_installer.py:83-90 when an entry in a wheel would be written outside its target scheme directory. The check uses os.path.abspath on target_dir joined with the entry path and verifies the result still starts with target_dir + os.sep — defeating absolute paths and `..` traversal. This is a Zip-Slip / path-traversal security guard. ValueError.
Source
Thrown at src/poetry/installation/wheel_installer.py:86
# Attention: Path.absolute() is not sufficient because it does not
# normalize, i.e. does not remove "..".
#
# We want to avoid Path.resolve() because it is significantly slower
# than os.path.abspath()!
#
# We operate on plain strings and only build a Path at the end because
# this method is called once per file in the wheel: pathlib operations
# (especially Path.is_relative_to(), which materializes Path.parents)
# add up to a significant overhead during installation.
target_dir = self._abspath_scheme_dir(scheme)
target_path_str = os.path.abspath(os.path.join(target_dir, path))
# We do not need os.path.normcase() for this comparison
# because both paths are built from target_dir.
if target_path_str != target_dir and not target_path_str.startswith(
target_dir + os.sep
):
raise ValueError(
f"Attempting to write {path} outside of the target directory\n"
f"Target directory: {target_dir}\n"
f"Target path: {target_path_str}"
)
target_path = Path(target_path_str)
if target_path.exists():
# Contrary to the base library we don't raise an error here since it can
# break pkgutil-style and pkg_resource-style namespace packages.
logger.warning(f"Installing {target_path} over existing file")
parent_folder = target_path.parent
if not parent_folder.exists():
# Due to the parallel installation it can happen
# that two threads try to create the directory.
parent_folder.mkdir(parents=True, exist_ok=True)
View on GitHub (pinned to 92b74dcfe3)
Solutions
- Do not install the offending wheel — treat it as untrusted; audit how it was produced.
- Rebuild the wheel with a mainstream build backend (setuptools/hatchling) so RECORD paths are sane.
- If you control the source, ensure no file path in the package is absolute or contains '..'.
Example fix
# before: wheel contains an entry like '/etc/badfile' or '../../escape' $ poetry install ValueError: Attempting to write ... outside of the target directory ... # fix: rebuild the wheel with correct (relative, in-package) paths $ python -m build
Defensive patterns
Strategy: try-catch
Validate before calling
import os
def is_safe_member(target_dir: str, member_path: str) -> bool:
abs_target = os.path.abspath(target_dir)
abs_member = os.path.abspath(os.path.join(target_dir, member_path))
return abs_member == abs_target or abs_member.startswith(abs_target + os.sep) Try / catch
try:
install(wheel_source, wheel_destination, {...})
except ValueError as e:
if 'outside of the target directory' in str(e):
# do NOT bypass; quarantine the wheel and report
raise SystemExit(f'Unsafe wheel rejected: {e}') from e
raise Prevention
- Treat this error as a security signal — never silence it; the wheel is untrusted.
- Build wheels only with reputable backends; scan private-index wheels before install.
- Audit RECORD paths of any third-party wheel before deployment.
When it happens
Trigger: Installing a wheel that contains a member whose path is absolute (leading '/') or contains '..' segments that escape the scheme dir. The installer iterates every file in the wheel and writes via this method.
Common situations: A maliciously crafted or buggy wheel (rare on public PyPI thanks to upload validation, but possible from private indexes), or a wheel produced by a faulty custom build backend that emits bad RECORD paths.
Related errors
- {root} is not a valid repository cache
- {wheel.filename} is not a supported wheel for this platform.
- Unable to parse build tag: {wheel.build_tag}
- Package {link.url} cannot be installed in the current enviro
- Hash for {package} from archive {archive.name} not found in
AI-assisted analysis of python-poetry/poetry@92b74dcfe3 (2026-08-04).
Data as JSON: /data/errors/d63d60ac93f17574.json.
Report an issue: GitHub.