Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ Raquel Alegre
Ravi Chandra
Reagan Lee
Reilly Brogan
rinopatrick
Rob Arrow
Robert Holt
Roberto Aldera
Expand Down
4 changes: 4 additions & 0 deletions changelog/15295.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fixed an ``INTERNALERROR`` at startup when the cache directory path contains a
``..`` segment (e.g. tox with a ``toxworkdir`` outside the ``tox.ini``
directory): the base directory is now normalized the same way as the joined
path before checking containment.
4 changes: 4 additions & 0 deletions src/_pytest/cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ def _join_within(base: Path, name: str) -> Path:
allowing a ``..`` that cancels out within ``base``.
"""
path = Path(os.path.normpath(base.joinpath(name)))
# Normalize the base too: it may itself contain `..` segments
# (e.g. tox passes `$TOX_ENV_DIR` through verbatim), which would
# otherwise fail the containment check (see #15295).
base = Path(os.path.normpath(base))
if not path.is_relative_to(base):
raise ValueError(f"{name!r} is not allowed to escape the cache directory")
return path
Expand Down
12 changes: 12 additions & 0 deletions testing/test_cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ def test_cache_key_normalized(self, pytester: Pytester) -> None:
config.cache.set("plugin/sub/../value", 42)
assert config.cache.get("plugin/value", None) == 42

def test_cache_dir_with_dotdot(self, pytester: Pytester) -> None:
"""A `..` inside cache_dir itself (e.g. tox's `$TOX_ENV_DIR`, see
#15295) must not break cache access as long as the key stays
inside."""
base = pytester.path / "sub"
base.mkdir()
cache_dir = base / ".." / ".pytest_cache"
config = pytester.parseconfigure("-o", f"cache_dir={cache_dir}")
assert config.cache is not None
config.cache.set("plugin/value", 42)
assert config.cache.get("plugin/value", None) == 42

def test_cache_dir_permissions(self, pytester: Pytester) -> None:
"""The .pytest_cache directory should have world-readable permissions
(depending on umask).
Expand Down
Loading