From e7b5ef874264f20fcaed743236471db6bd706933 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 2 Nov 2026 07:57:30 +0700 Subject: [PATCH] fix(cache): normalize base dir in within-cache check A .. segment inside cache_dir itself (e.g. tox TOX_ENV_DIR passed through verbatim) failed the containment check even though the key stayed inside. Normalize the base the same way as the joined path. Closes pytest-dev/pytest#15295 --- AUTHORS | 1 + changelog/15295.bugfix.rst | 4 ++++ src/_pytest/cacheprovider.py | 4 ++++ testing/test_cacheprovider.py | 12 ++++++++++++ 4 files changed, 21 insertions(+) create mode 100644 changelog/15295.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 136ffd6c5ee..aa9ce45e90a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -426,6 +426,7 @@ Raquel Alegre Ravi Chandra Reagan Lee Reilly Brogan +rinopatrick Rob Arrow Robert Holt Roberto Aldera diff --git a/changelog/15295.bugfix.rst b/changelog/15295.bugfix.rst new file mode 100644 index 00000000000..0d8b2c375ff --- /dev/null +++ b/changelog/15295.bugfix.rst @@ -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. diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 5d4a528077d..004d3e35745 100644 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -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 diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index a38a72c6011..89705e79cb5 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -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).