binary-husky/gpt_academic · warning · Exception

Attempted Symlink in {member.name}

Error message

Attempted Symlink in {member.name}

What it means

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.

Source

Thrown at shared_utils/handle_upload.py:141

    file_extension = os.path.splitext(file_path)[1]

    # Extract the archive based on its extension
    if file_extension == ".zip":
        with zipfile.ZipFile(file_path, "r") as zipobj:
            zipobj._extract_member = lambda a,b,c: zip_extract_member_new(zipobj, a,b,c)    # 修复中文乱码的问题
            zipobj.extractall(path=dest_dir)
            logger.info("Successfully extracted zip archive to {}".format(dest_dir))

    elif file_extension in [".tar", ".gz", ".bz2"]:
        try:
            with tarfile.open(file_path, "r:*") as tarobj:
                # 清理提取路径,移除任何不安全的元素
                for member in tarobj.getmembers():
                    member_path = os.path.normpath(member.name)
                    full_path = os.path.join(dest_dir, member_path)
                    full_path = os.path.abspath(full_path)
                    if member.islnk() or member.issym():
                        raise Exception(f"Attempted Symlink in {member.name}")
                    if not full_path.startswith(os.path.abspath(dest_dir) + os.sep):
                        raise Exception(f"Attempted Path Traversal in {member.name}")

                tarobj.extractall(path=dest_dir)
                logger.info("Successfully extracted tar archive to {}".format(dest_dir))
        except tarfile.ReadError as e:
            if file_extension == ".gz":
                # 一些特别奇葩的项目,是一个gz文件,里面不是tar,只有一个tex文件
                import gzip
                with gzip.open(file_path, 'rb') as f_in:
                    with open(os.path.join(dest_dir, 'main.tex'), 'wb') as f_out:
                        f_out.write(f_in.read())
            else:
                raise e

    # 第三方库,需要预先pip install rarfile
    # 此外,Windows上还需要安装winrar软件,配置其Path环境变量,如"C:\Program Files\WinRAR"才可以
    elif file_extension == ".rar":

View on GitHub (pinned to d6bde0fa54)

Solutions

  1. Repack the archive without symlinks/hardlinks (replace links with copies of the target files), then re-upload.
  2. Verify with 'tar -tvf file.tar' — entries starting with 'l' or showing '->' are the culprits.
  3. Do not bypass the check on a public deployment; it is a security guard.
  4. If links are essential, extract manually after inspecting the archive.

Example fix

# shell: locate offending members
# tar -tvf upload.tar | grep -E '^[hl]|->'
# then replace symlinks with real copies and re-tar:
# cp -L $(tar -tf upload.tar) . && tar -czf clean.tar.gz .
Defensive patterns

Strategy: validation

Validate before calling

import tarfile

def tar_has_links(path: str) -> list:
    with tarfile.open(path, 'r:*') as t:
        return [m.name for m in t.getmembers() if m.issym() or m.islnk()]

offenders = tar_has_links(upload_path)
if offenders:
    reject_upload(f'archive contains links: {offenders[:5]}')

Try / catch

try:
    extract_archive(upload_path, dest_dir)
except Exception as e:
    if 'Attempted Symlink' in str(e):
        reject_upload('repack without symlinks')
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of binary-husky/gpt_academic@d6bde0fa54 (2026-08-14). Data as JSON: /api/errors/87c7dad3c8a70f2b. Report an issue: GitHub.