{"id":"fca5ebd2ed629cba","repo":"python-poetry/poetry","slug":"refusing-hardlink-member-name-target-member-li","errorCode":null,"errorMessage":"Refusing hardlink {member.name}: target {member.linkname} outside {dest}","messagePattern":"Refusing hardlink (.+?): target (.+?) outside (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/poetry/utils/helpers.py","lineNumber":326,"sourceCode":"                    member_path = Path(os.path.abspath(dest / member.name))\n                    if not member_path.is_relative_to(dest):\n                        raise ValueError(\n                            f\"Refusing to extract {member.name}: \"\n                            f\"would write outside {dest}\"\n                        )\n                    if member.issym():\n                        link_target = Path(\n                            os.path.abspath(member_path.parent / member.linkname)\n                        )\n                        if not link_target.is_relative_to(dest):\n                            raise ValueError(\n                                f\"Refusing symlink {member.name}: \"\n                                f\"target {member.linkname} outside {dest}\"\n                            )\n                    elif member.islnk():\n                        link_target = Path(os.path.abspath(dest / member.linkname))\n                        if not link_target.is_relative_to(dest):\n                            raise ValueError(\n                                f\"Refusing hardlink {member.name}: \"\n                                f\"target {member.linkname} outside {dest}\"\n                            )\n                    safe_members.append(member)\n                archive.extractall(dest, members=safe_members)\n\n\n_DEPRECATED_DOWNLOAD_EXPORTS = {\n    \"Downloader\",\n    \"download_file\",\n    \"HTTPRangeRequestSupportedError\",\n}\n\n\ndef __getattr__(name: str) -> object:\n    if name in _DEPRECATED_DOWNLOAD_EXPORTS:\n        warnings.warn(\n            f\"Importing `{name}` from `poetry.utils.helpers` is deprecated;\"","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/python-poetry/poetry/blob/92b74dcfe348d0e01e14d40d6c1fa47a4ee04a54/src/poetry/utils/helpers.py#L308-L344","documentation":"Raised by extractall during manual tar validation for hardlink members (member.islnk()). Unlike symlinks, the hardlink target is resolved relative to dest itself (os.path.abspath(dest / member.linkname)) and rejected if it is not under dest. This prevents a hardlink member from binding to a file outside the extraction tree.","triggerScenarios":"Extracting a tar (zip=False, no working data_filter) containing a hardlink member whose linkname, joined onto dest, resolves outside dest, e.g. a hardlink to '../../etc/shadow'.","commonSituations":"Crafted tarballs using hardlinks to expose host files; toolchain output that records hardlinks against absolute paths; Python pinned to a blocklisted patch level.","solutions":["Inspect `tar -tvf` for hardlink members (type '1') and strip/rebuild entries that point outside the root.","Use a Python version with a functional tarfile.data_filter.","Re-acquire the archive from a trusted source.","Extract in an isolated environment to assess risk before touching real paths."],"exampleFix":"# before\nextractall(Path('pkg.tar'), dest, zip=False)\n# after - vet hardlink targets\nimport tarfile, os\nfrom pathlib import Path\nwith tarfile.open('pkg.tar') as a:\n    dest = Path(os.path.abspath(dest))\n    for m in a.getmembers():\n        if m.islnk():\n            t = Path(os.path.abspath(dest / m.linkname))\n            if not t.is_relative_to(dest):\n                raise ValueError(f'hardlink escapes dest: {m.name}')\n    a.extractall(dest)","handlingStrategy":"validation","validationCode":"import os, tarfile\nfrom pathlib import Path\n\ndef tar_hardlinks_safe(source: Path, dest: Path) -> bool:\n    dest = Path(os.path.abspath(dest))\n    with tarfile.open(source) as a:\n        for m in a.getmembers():\n            if m.islnk():\n                t = Path(os.path.abspath(dest / m.linkname))\n                if not t.is_relative_to(dest):\n                    return False\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    extractall(source, dest, zip=False)\nexcept ValueError as e:\n    # hardlink escaped dest; do not extract\n    raise","preventionTips":["Check hardlink members (type '1') in `tar -tvf` output.","Prefer a Python with a functional data_filter.","Reject archives with hardlinks to absolute or parent-escaping targets."],"tags":["security","tar","hardlink","path-traversal","archive"],"analyzedSha":"92b74dcfe348d0e01e14d40d6c1fa47a4ee04a54","analyzedAt":"2026-08-04T20:33:34.072Z","schemaVersion":2}