agentscope-ai/agentscope · error · Exception

unsafe tar member: ' + m.name

Error message

unsafe tar member: ' + m.name

What it means

Generic Exception raised inside the sandboxed extraction shim when a tar member would resolve outside the destination directory (path traversal, e.g. names containing .. or absolute paths). This is a deliberate Zip-Slip protection before extractall.

Source

Thrown at src/agentscope/workspace/_base.py:173

    "    os.rename(staging, partition)\n"
    "except OSError:\n"
    "    shutil.rmtree(staging, ignore_errors=True)\n"
    "print('equipped')\n"
)

_EXTRACT_TAR_SHIM = (
    "import tarfile, sys, os\n"
    "src, dst = sys.argv[1], sys.argv[2]\n"
    "os.makedirs(dst, exist_ok=True)\n"
    "dst_real = os.path.realpath(dst)\n"
    "tf = tarfile.open(src)\n"
    "try:\n"
    "    members = tf.getmembers()\n"
    "    for m in members:\n"
    "        target = os.path.realpath(os.path.join(dst, m.name))\n"
    "        if not (target == dst_real"
    " or target.startswith(dst_real + os.sep)):\n"
    "            raise Exception('unsafe tar member: ' + m.name)\n"
    "    tf.extractall(dst, members=members)\n"
    "finally:\n"
    "    tf.close()\n"
    "os.unlink(src)\n"
)

#: Expands an archive inside the sandbox. Runs there rather than on the
#: server so a traversing or bomb-sized archive detonates in the
#: isolated environment. Argv: src, dst, format, max extracted bytes.
_EXTRACT_ARCHIVE_SHIM = (
    "import os, sys, tarfile, zipfile\n"
    "src, dst, fmt, limit = sys.argv[1:5]\n"
    "limit = int(limit)\n"
    "os.makedirs(dst, exist_ok=True)\n"
    "dst_real = os.path.realpath(dst)\n"
    "def check(name):\n"
    "    target = os.path.realpath(os.path.join(dst, name))\n"
    "    if not (target == dst_real"

View on GitHub (pinned to e90f1c7592)

Solutions

  1. Inspect the archive: `tar -tvf skill.tar` and look for absolute paths, .., or symlinks
  2. Rebuild the archive with relative member names (create it from inside the skill directory)
  3. Reject/trust only skill archives from known sources
  4. If you control the content, remove symlinks that point outside the skill root

Example fix

# before: archive built from parent dir
# tar -cf skill.tar ../my-skill  -> members like '../my-skill/SKILL.md'

# after: build from inside the directory
cd my-skill && tar -cf ../skill.tar .
# members are now 'SKILL.md', etc.
Defensive patterns

Strategy: validation

Validate before calling

import tarfile

def tar_is_safe(path: str, expected_root: str = '') -> bool:
    with tarfile.open(path) as tf:
        for m in tf.getmembers():
            if m.name.startswith('/') or '..' in m.name.split('/'):
                return False
            if (m.issym() or m.islnk()) and (m.linkname.startswith('/') or '..' in m.linkname.split('/')):
                return False
    return True

Type guard

def is_safe_tarfile(path: str) -> bool:
    try:
        return tar_is_safe(path)
    except tarfile.TarError:
        return False

Try / catch

try:
    await ws.add_skill_archive('skill.tar', 'my-skill')
except (RuntimeError, ValueError) as e:
    if 'unsafe tar member' in str(e):
        raise SecurityError(f'rejecting malicious skill archive: {e}')
    raise

Prevention

When it happens

Trigger: Calling APIs that transfer/extract a tar of skill files (add_skill, add_skill_archive) where the archive contains members whose realpath escapes the destination — e.g. entries like ../../etc/passwd or absolute paths or symlinks/hardlinks pointing outside.

Common situations: Malicious or malformed third-party skill tarballs, archives built with unusual tooling producing absolute member names, symlink members targeting files outside the extraction dir.

Related errors


AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28). Data as JSON: /api/errors/0ebfb0b551954590. Report an issue: GitHub.