{"record":{"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/0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc/src/_pytest/tmpdir.py#L96-L132","documentation":"TempPathFactory.mktemp() builds a subdirectory under basetemp from a caller-supplied basename. _ensure_relative_to_basetemp() normalizes the name then asserts that (basetemp/basename).resolve().parent == basetemp — i.e. the resolved path stays directly under basetemp. Absolute paths and any name that escapes via '..' fail this check, blocking path traversal and writes outside the temp tree.","triggerScenarios":"Calling factory.mktemp('/abs/path'), factory.mktemp('../escape'), factory.mktemp('a/../../b'), or any basename whose normpath+resolve no longer sits immediately under basetemp. Reachable from internal callers (the tmp_path_factory fixture, the tmpdir factory) or plugin code that invokes mktemp directly.","commonSituations":"A plugin derives the basename from user input or test IDs and forgets to sanitize; a test passes an absolute path expecting mktemp to honor it; refactoring code that used to join paths manually.","solutions":["Pass a plain relative name with no separators or `..` segments: 'foo', 'foo-', 'subdir'.","Sanitize before calling: strip leading '/', reject any '..' component, prefer a single path component.","If you need a nested layout, create the parent with mktemp first and join inside it yourself; do not push nesting into the basename."],"exampleFix":"# before\nsub = factory.mktemp(f\"{user_supplied}/data\")  # user_supplied may be '../x'\n\n# after\nimport re\nsafe = re.sub(r\"[^A-Za-z0-9_.-]\", \"_\", user_supplied) or \"tmp\"\nsub = factory.mktemp(safe)","handlingStrategy":"validation","validationCode":"import os\n\ndef safe_basename(name: str) -> str:\n    \"\"\"Sanitize a caller-supplied name for TempPathFactory.mktemp().\"\"\"\n    # Strip any absolute prefix and reject traversal.\n    name = os.path.basename(os.path.normpath(name))\n    if not name or name in (os.curdir, os.pardir) or '..' in name.split(os.sep):\n        raise ValueError(f\"unsafe basename for mktemp: {name!r}\")\n    return name","typeGuard":"import os\n\ndef is_safe_mktemp_basename(name: str, basetemp) -> bool:\n    norm = os.path.normpath(name)\n    return (\n        not os.path.isabs(norm)\n        and (basetemp / norm).resolve().parent == basetemp.resolve()\n    )","tryCatchPattern":"from _pytest.tmpdir import TempPathFactory\n\ndef robust_mktemp(factory: TempPathFactory, name: str):\n    try:\n        return factory.mktemp(name)\n    except ValueError:\n        # Fall back to a sanitized single-component name.\n        import re\n        safe = re.sub(r\"[^A-Za-z0-9_.-]\", \"_\", name) or \"tmp\"\n        return factory.mktemp(safe)","preventionTips":["Never pass user input or test IDs straight into mktemp(); sanitize first.","Use single path components (no '/', no '..') as basenames.","If you need nested dirs, mktemp the parent and join children yourself inside the temp tree."],"tags":["tmp-path","security","validation","path-traversal"],"backgroundTag":null,"analyzedSha":"0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc","analyzedAt":"2026-08-11T20:52:36.969Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}