{"id":"d01c0c2d9c824590","repo":"pytest-dev/pytest","slug":"the-temporary-directory-rootdir-is-a-symbolic-li","errorCode":null,"errorMessage":"The temporary directory {rootdir} is a symbolic link. Fix this and try again.","messagePattern":"The temporary directory (.+?) is a symbolic link\\. Fix this and try again\\.","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"src/_pytest/tmpdir.py","lineNumber":189,"sourceCode":"            # temproot is usually shared).\n            # Also, to keep things private, fixup any world-readable temp\n            # rootdir's permissions. Historically 0o755 was used, so we can't\n            # just error out on this, at least for a while.\n            # Don't follow symlinks, otherwise we're open to symlink-swapping\n            # TOCTOU vulnerability.\n            # This check makes us vulnerable to a DoS - a user can `mkdir\n            # /tmp/pytest-of-otheruser` and then `otheruser` will fail this\n            # check. For now we don't consider it a real problem. otheruser can\n            # change their TMPDIR or --basetemp, and maybe give the prankster a\n            # good scolding.\n            uid = get_user_id()\n            if uid is not None:\n                stat_follow_symlinks = (\n                    False if os.stat in os.supports_follow_symlinks else True\n                )\n                rootdir_stat = rootdir.stat(follow_symlinks=stat_follow_symlinks)\n                if stat.S_ISLNK(rootdir_stat.st_mode):\n                    raise OSError(\n                        f\"The temporary directory {rootdir} is a symbolic link. \"\n                        \"Fix this and try again.\"\n                    )\n                if rootdir_stat.st_uid != uid:\n                    raise OSError(\n                        f\"The temporary directory {rootdir} is not owned by the current user. \"\n                        \"Fix this and try again.\"\n                    )\n                if (rootdir_stat.st_mode & 0o077) != 0:\n                    chmod_follow_symlinks = (\n                        False if os.chmod in os.supports_follow_symlinks else True\n                    )\n                    rootdir.chmod(\n                        rootdir_stat.st_mode & ~0o077,\n                        follow_symlinks=chmod_follow_symlinks,\n                    )\n            keep = self._retention_count\n            if self._retention_policy == \"none\":","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/tmpdir.py#L171-L207","documentation":"Raised by TempPathFactory.getbasetemp as an OSError when the auto-created basetemp root directory (typically /tmp/pytest-of-<user>) is itself a symbolic link. pytest deliberately refuses to follow a symlink here to avoid a TOCTOU symlink-swapping attack where another user redirects the shared temp root to an attacker-controlled location. The stat is taken without following symlinks where the platform allows it.","triggerScenarios":"Someone (a human, a setup script, or another tool) created /tmp/pytest-of-<user> as a symlink to elsewhere, then pytest tries to initialize its basetemp under it. Triggered on the first test/fixture that needs tmp_path or any call to tmp_path_factory.getbasetemp().","commonSituations":"Shared CI runners where /tmp is symlinked (e.g. to a larger disk); a previous `ln -s` for debugging; Docker/k8s volume mounts that manifest as symlinks; misconfigured TMPDIR pointing at a symlinked dir.","solutions":["Remove the symlink: `rm /tmp/pytest-of-<user>` and let pytest recreate it as a real directory.","Set a safe basetemp explicitly via `pytest --basetemp=/real/dir` or TMPDIR env var pointing at a non-symlink directory.","If the OS layout forces a symlink at /tmp, point TMPDIR to a real directory on a non-linkified filesystem.","Audit setup scripts and provisioning that may `ln -s` into /tmp."],"exampleFix":"// before\n# /tmp/pytest-of-alice -> /mnt/big/pytest-alice (symlink)\n$ pytest tests/\n\n// after\n$ rm /tmp/pytest-of-alice\n$ TMPDIR=/mnt/big/realtmp pytest tests/\n# or\n$ pytest --basetemp=/mnt/big/realtmp/pytest-alice tests/","handlingStrategy":"validation","validationCode":"import os, pathlib\n\ndef ensure_basetemp_not_symlink(p: pathlib.Path):\n    if p.is_symlink():\n        raise OSError(f\"{p} is a symlink; refusing to use as basetemp\")\n    return p","typeGuard":"import os\n\ndef is_real_dir(p) -> bool:\n    return os.path.isdir(p) and not os.path.islink(p)","tryCatchPattern":null,"preventionTips":["Always pass --basetemp to a real directory in CI instead of relying on /tmp layout.","Provision TMPDIR to a non-symlinked filesystem in container images.","Document the basetemp requirement in the project README for shared runners."],"tags":["tmpdir","symlink","security","toctou","filesystem"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}