{"record":{"id":"87c7dad3c8a70f2b","repo":"binary-husky/gpt_academic","slug":"attempted-symlink-in-member-name","errorCode":null,"errorMessage":"Attempted Symlink in {member.name}","messagePattern":"Attempted Symlink in (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"warning","filePath":"shared_utils/handle_upload.py","lineNumber":141,"sourceCode":"    file_extension = os.path.splitext(file_path)[1]\n\n    # Extract the archive based on its extension\n    if file_extension == \".zip\":\n        with zipfile.ZipFile(file_path, \"r\") as zipobj:\n            zipobj._extract_member = lambda a,b,c: zip_extract_member_new(zipobj, a,b,c)    # 修复中文乱码的问题\n            zipobj.extractall(path=dest_dir)\n            logger.info(\"Successfully extracted zip archive to {}\".format(dest_dir))\n\n    elif file_extension in [\".tar\", \".gz\", \".bz2\"]:\n        try:\n            with tarfile.open(file_path, \"r:*\") as tarobj:\n                # 清理提取路径，移除任何不安全的元素\n                for member in tarobj.getmembers():\n                    member_path = os.path.normpath(member.name)\n                    full_path = os.path.join(dest_dir, member_path)\n                    full_path = os.path.abspath(full_path)\n                    if member.islnk() or member.issym():\n                        raise Exception(f\"Attempted Symlink in {member.name}\")\n                    if not full_path.startswith(os.path.abspath(dest_dir) + os.sep):\n                        raise Exception(f\"Attempted Path Traversal in {member.name}\")\n\n                tarobj.extractall(path=dest_dir)\n                logger.info(\"Successfully extracted tar archive to {}\".format(dest_dir))\n        except tarfile.ReadError as e:\n            if file_extension == \".gz\":\n                # 一些特别奇葩的项目，是一个gz文件，里面不是tar，只有一个tex文件\n                import gzip\n                with gzip.open(file_path, 'rb') as f_in:\n                    with open(os.path.join(dest_dir, 'main.tex'), 'wb') as f_out:\n                        f_out.write(f_in.read())\n            else:\n                raise e\n\n    # 第三方库，需要预先pip install rarfile\n    # 此外，Windows上还需要安装winrar软件，配置其Path环境变量，如\"C:\\Program Files\\WinRAR\"才可以\n    elif file_extension == \".rar\":","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/binary-husky/gpt_academic/blob/d6bde0fa54373309bd05823a49bda8da019d2c77/shared_utils/handle_upload.py#L123-L159","documentation":"During tar/gz/bz2 extraction in handle_upload, every member is inspected first; hard links (islnk) and symlinks (issym) raise Exception('Attempted Symlink in {member.name}') before extractall runs. This blocks the classic zip-slip/symlink-overwrite attack where an archive member links to /etc/passwd or another extracted file to escape the destination.","triggerScenarios":"Uploading a .tar/.tar.gz/.tar.bz2 project archive (e.g. LaTeX source) that contains a symlink or hardlink entry; extraction is aborted entirely with the offending member name in the message.","commonSituations":"Legitimate LaTeX/source archives that use symlinks for shared figures or style files are rejected too (false positive by design); malicious uploads probing the extraction pipeline; archives created on macOS/Linux with 'ln -s' entries.","solutions":["Repack the archive without symlinks/hardlinks (replace links with copies of the target files), then re-upload.","Verify with 'tar -tvf file.tar' — entries starting with 'l' or showing '->' are the culprits.","Do not bypass the check on a public deployment; it is a security guard.","If links are essential, extract manually after inspecting the archive."],"exampleFix":"# shell: locate offending members\n# tar -tvf upload.tar | grep -E '^[hl]|->'\n# then replace symlinks with real copies and re-tar:\n# cp -L $(tar -tf upload.tar) . && tar -czf clean.tar.gz .","handlingStrategy":"validation","validationCode":"import tarfile\n\ndef tar_has_links(path: str) -> list:\n    with tarfile.open(path, 'r:*') as t:\n        return [m.name for m in t.getmembers() if m.issym() or m.islnk()]\n\noffenders = tar_has_links(upload_path)\nif offenders:\n    reject_upload(f'archive contains links: {offenders[:5]}')","typeGuard":null,"tryCatchPattern":"try:\n    extract_archive(upload_path, dest_dir)\nexcept Exception as e:\n    if 'Attempted Symlink' in str(e):\n        reject_upload('repack without symlinks')\n    else:\n        raise","preventionTips":["Pre-scan archives client-side with tar -tvf for link entries.","Repack project sources with copies instead of symlinks before upload.","Keep this guard enabled — it protects the extraction directory."],"tags":["security","archive","tar","symlink","upload"],"backgroundTag":null,"analyzedSha":"d6bde0fa54373309bd05823a49bda8da019d2c77","analyzedAt":"2026-08-14T22:48:35.038Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}