{"record":{"id":"a4d807a0e8a7e71a","repo":"zed-industries/zed","slug":"archive-links-are-not-supported-member-name","errorCode":null,"errorMessage":"archive links are not supported: {member.name}","messagePattern":"archive links are not supported: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"crates/eval_cli/zed_eval/common.py","lineNumber":48,"sourceCode":"def write_json(path: pathlib.Path, data: dict[str, Any]) -> None:\n    path.parent.mkdir(parents=True, exist_ok=True)\n    path.write_text(json.dumps(data, indent=2, sort_keys=True) + \"\\n\")\n\n\ndef load_json(path: pathlib.Path) -> dict[str, Any] | None:\n    try:\n        data = json.loads(path.read_text())\n    except (OSError, json.JSONDecodeError):\n        return None\n    return data if isinstance(data, dict) else None\n\n\ndef safe_extract_archive(archive: tarfile.TarFile, destination: pathlib.Path) -> None:\n    destination = destination.resolve()\n    members = archive.getmembers()\n    for member in members:\n        if member.issym() or member.islnk():\n            raise ValueError(f\"archive links are not supported: {member.name}\")\n        target = (destination / member.name).resolve()\n        if destination != target and destination not in target.parents:\n            raise ValueError(f\"archive member escapes destination: {member.name}\")\n    archive.extractall(destination, members=members)\n\n\ndef command_exists(name: str) -> bool:\n    return shutil.which(name) is not None\n\n\ndef run_command(\n    command: list[str], *, capture: bool = False\n) -> subprocess.CompletedProcess[str]:\n    return subprocess.run(command, check=True, text=True, capture_output=capture)\n\n\ndef dedupe_preserving_order(values: list[str]) -> list[str]:\n    seen = set()","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/eval_cli/zed_eval/common.py#L30-L66","documentation":"safe_extract_archive() deliberately refuses tar members that are symlinks or hardlinks (member.issym()/islnk()). Links are the classic vehicle for tar-extraction attacks — a link pointing outside the destination with a later member writing through it — so the helper rejects any archive containing them rather than trying to resolve them safely.","triggerScenarios":"Extracting an archive created with plain `tar czf` over a tree containing symlinks (node_modules, doc-compression links, linked repo assets) or hardlinks (nix store, pnpm content-addressed stores) — the links become members and trip the check.","commonSituations":"Bundling dependency trees or repo snapshots with links; archives produced on systems that hardlink duplicate files; hand-rolled packaging scripts.","solutions":["Repack with dereferenced files: `tar --dereference --hard-dereference -czf bundle.tar.gz dir/`","Remove incidental links from the source tree before packing","If links are essential, extract manually after reviewing every link target"],"exampleFix":"# before\ntar czf bundle.tar.gz repo/          # symlinks preserved → ValueError\n\n# after\ntar --dereference --hard-dereference -czf bundle.tar.gz repo/","handlingStrategy":"validation","validationCode":"import tarfile\nwith tarfile.open(archive_path) as archive:\n    links = [m.name for m in archive.getmembers() if m.issym() or m.islnk()]\nif links:\n    raise SystemExit(f\"archive contains links: {links[:5]}; repack with tar --dereference\")","typeGuard":"import tarfile\n\ndef is_link_free(members):\n    return not any(m.issym() or m.islnk() for m in members)","tryCatchPattern":"try:\n    safe_extract_archive(archive, destination)\nexcept ValueError as e:\n    raise SystemExit(f\"unsafe archive rejected: {e}\")","preventionTips":["Create bundles with tar -h (--dereference) and --hard-dereference","Always extract through safe_extract_archive instead of bare extractall","Treat link members in incoming archives as suspicious by default"],"tags":["python","security","tarfile","archive"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}