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
- Inspect the archive: `tar -tvf skill.tar` and look for absolute paths, .., or symlinks
- Rebuild the archive with relative member names (create it from inside the skill directory)
- Reject/trust only skill archives from known sources
- 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
- Only install skill archives from trusted sources
- Pre-scan archives for traversal entries before installing
- Build archives from inside the skill directory
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
- unsafe archive member: ' + name
- Unsafe upload path: {entry.path!r}
- Blob key {key!r} escapes the root directory.
- Bubblewrap workdir escapes basedir.
- workspace_id {workspace_id!r} escapes the workspace base dir
AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28).
Data as JSON: /api/errors/0ebfb0b551954590.
Report an issue: GitHub.