{"id":"acd042c01c55dc1d","repo":"pytest-dev/pytest","slug":"basename-is-not-a-normalized-and-relative-path","errorCode":null,"errorMessage":"{basename} is not a normalized and relative path","messagePattern":"(.+?) is not a normalized and relative path","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/_pytest/tmpdir.py","lineNumber":114,"sourceCode":"        if count < 0:\n            raise ValueError(\n                f\"tmp_path_retention_count must be >= 0. Current input: {count}.\"\n            )\n\n        policy: RetentionType = config.getini(\"tmp_path_retention_policy\")\n\n        return cls(\n            given_basetemp=config.option.basetemp,\n            trace=config.trace.get(\"tmpdir\"),\n            retention_count=count,\n            retention_policy=policy,\n            _ispytest=True,\n        )\n\n    def _ensure_relative_to_basetemp(self, basename: str) -> str:\n        basename = os.path.normpath(basename)\n        if (self.getbasetemp() / basename).resolve().parent != self.getbasetemp():\n            raise ValueError(f\"{basename} is not a normalized and relative path\")\n        return basename\n\n    def mktemp(self, basename: str, numbered: bool = True) -> Path:\n        \"\"\"Create a new temporary directory managed by the factory.\n\n        :param basename:\n            Directory base name, must be a relative path.\n\n        :param numbered:\n            If ``True``, ensure the directory is unique by adding a numbered\n            suffix greater than any existing one: ``basename=\"foo-\"`` and ``numbered=True``\n            means that this function will create directories named ``\"foo-0\"``,\n            ``\"foo-1\"``, ``\"foo-2\"`` and so on.\n\n        :returns:\n            The path to the new directory.\n        \"\"\"\n        basename = self._ensure_relative_to_basetemp(basename)","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/tmpdir.py#L96-L132","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Pass a simple relative basename (no slashes, no leading '/'); sanitize with re.sub(r'[\\\\W]+', '_', name) as the built-in _mk_tmp does.","If you need subdirectories, create them inside the returned Path with Path.mkdir(parents=True) rather than encoding them into the basename.","Never feed user/test-param input directly into mktemp; strip path separators first."],"exampleFix":"// before\ndef test_x(tmp_path_factory, request):\n    d = tmp_path_factory.mktemp(request.param)  # request.param may contain '../'\n\n// after\nimport re\ndef test_x(tmp_path_factory, request):\n    safe = re.sub(r'[\\\\W]+', '_', request.param)[:30]\n    d = tmp_path_factory.mktemp(safe)","handlingStrategy":"validation","validationCode":"import os, re\n\ndef safe_basename(name: str) -> str:\n    name = re.sub(r'[^A-Za-z0-9_.-]+', '_', name)[:30]\n    if os.path.normpath(name) != name or os.path.isabs(name):\n        raise ValueError(f\"unsafe basename: {name!r}\")\n    return name","typeGuard":"import os\n\ndef is_safe_basename(name: str) -> bool:\n    n = os.path.normpath(name)\n    return n == name and not os.path.isabs(n) and '..' not in n.split(os.sep)","tryCatchPattern":null,"preventionTips":["Never pass user/test-param strings directly to mktemp; sanitize first.","Prefer single-segment basenames; build subdirs with Path.mkdir on the result.","Mirror pytest's own re.sub(r'[\\\\W]', '_', name)[:30] sanitization."],"tags":["tmpdir","path-traversal","validation","security"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}