pytest-dev/pytest · error · ValueError

{basename} is not a normalized and relative path

Error message

{basename} is not a normalized and relative path

What it means

Raised by TempPathFactory._ensure_relative_to_basetemp when the basename passed to mktemp does not normalize to a path whose parent is the basetemp. This blocks path traversal: a basename containing '..' or an absolute/rooted path would escape the basetemp directory. After normpath, pytest resolves (basetemp/basename).parent and requires it to equal basetemp.

Source

Thrown at src/_pytest/tmpdir.py:114

        if count < 0:
            raise ValueError(
                f"tmp_path_retention_count must be >= 0. Current input: {count}."
            )

        policy: RetentionType = config.getini("tmp_path_retention_policy")

        return cls(
            given_basetemp=config.option.basetemp,
            trace=config.trace.get("tmpdir"),
            retention_count=count,
            retention_policy=policy,
            _ispytest=True,
        )

    def _ensure_relative_to_basetemp(self, basename: str) -> str:
        basename = os.path.normpath(basename)
        if (self.getbasetemp() / basename).resolve().parent != self.getbasetemp():
            raise ValueError(f"{basename} is not a normalized and relative path")
        return basename

    def mktemp(self, basename: str, numbered: bool = True) -> Path:
        """Create a new temporary directory managed by the factory.

        :param basename:
            Directory base name, must be a relative path.

        :param numbered:
            If ``True``, ensure the directory is unique by adding a numbered
            suffix greater than any existing one: ``basename="foo-"`` and ``numbered=True``
            means that this function will create directories named ``"foo-0"``,
            ``"foo-1"``, ``"foo-2"`` and so on.

        :returns:
            The path to the new directory.
        """
        basename = self._ensure_relative_to_basetemp(basename)

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Pass a simple relative basename (no slashes, no leading '/'); sanitize with re.sub(r'[\\W]+', '_', name) as the built-in _mk_tmp does.
  2. If you need subdirectories, create them inside the returned Path with Path.mkdir(parents=True) rather than encoding them into the basename.
  3. Never feed user/test-param input directly into mktemp; strip path separators first.

Example fix

// before
def test_x(tmp_path_factory, request):
    d = tmp_path_factory.mktemp(request.param)  # request.param may contain '../'

// after
import re
def test_x(tmp_path_factory, request):
    safe = re.sub(r'[\\W]+', '_', request.param)[:30]
    d = tmp_path_factory.mktemp(safe)
Defensive patterns

Strategy: validation

Validate before calling

import os, re

def safe_basename(name: str) -> str:
    name = re.sub(r'[^A-Za-z0-9_.-]+', '_', name)[:30]
    if os.path.normpath(name) != name or os.path.isabs(name):
        raise ValueError(f"unsafe basename: {name!r}")
    return name

Type guard

import os

def is_safe_basename(name: str) -> bool:
    n = os.path.normpath(name)
    return n == name and not os.path.isabs(n) and '..' not in n.split(os.sep)

Prevention

When it happens

Trigger: Calling tmp_path_factory.mktemp('../evil'), mktemp('/abs/path'), or mktemp('foo/../../bar'). Also reachable via custom fixtures that derive the basename from untrusted or user-supplied input (e.g. parameterized ids containing slashes or dots).

Common situations: A fixture builds the tmp dir name from a test id/param that includes slashes; pytester or integration code reusing mktemp with externally supplied strings; misuse of the internal mktemp API by a plugin.

Related errors


AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04). Data as JSON: /data/errors/acd042c01c55dc1d.json. Report an issue: GitHub.