agentscope-ai/agentscope · error · Exception
unsafe archive member: ' + name
Error message
unsafe archive member: ' + name
What it means
Same Zip-Slip guard as the tar shim, but for the zip/tar archive-expansion shim: each member name (and symlink/hardlink targets) must realpath inside the destination. The member name that failed is included in the message.
Source
Thrown at src/agentscope/workspace/_base.py:193
"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"
" or target.startswith(dst_real + os.sep)):\n"
" raise Exception('unsafe archive member: ' + name)\n"
"if fmt == 'zip':\n"
" ar = zipfile.ZipFile(src)\n"
" members = ar.infolist()\n"
" total = sum(m.file_size for m in members)\n"
" names = [m.filename for m in members]\n"
"else:\n"
" ar = tarfile.open(src)\n"
" members = ar.getmembers()\n"
" total = sum(m.size for m in members)\n"
" names = [m.name for m in members]\n"
" for m in members:\n"
" if m.issym() or m.islnk():\n"
" check(os.path.join(os.path.dirname(m.name), m.linkname))\n"
"try:\n"
" if total > limit:\n"
" raise Exception('archive expands to %d bytes, limit is %d'\n"
" % (total, limit))\n"
" for name in names:\n"View on GitHub (pinned to e90f1c7592)
Solutions
- List the archive contents (`unzip -l skill.zip` / `tar -tvf`) and find the offending entry named in the message
- Repack with relative, forward-slash member names
- Verify no entry is a symlink pointing outside the archive root
- Only install archives from trusted sources
Example fix
# before: entry 'C:\\skills\\my\\SKILL.md' or '../../x' # after: repack so entries are relative zip skill.zip SKILL.md assets/ -r # run from inside the skill dir
Defensive patterns
Strategy: validation
Validate before calling
import zipfile, tarfile
def archive_members_safe(path: str) -> bool:
if path.endswith('.zip'):
names = [m.filename for m in zipfile.ZipFile(path).infolist()]
else:
names = [m.name for m in tarfile.open(path)]
for n in names:
if n.startswith(('/', '\\')) or '..' in n.replace('\\', '/').split('/'):
return False
return True Try / catch
try:
await ws.add_skill_archive(path, name)
except ValueError as e:
if 'unsafe archive member' in str(e):
quarantine(path) # treat as hostile input
raise Prevention
- Validate downloaded archives before install
- Reject members with absolute paths or '..' segments
- Prefer archives produced by standard tools from within the skill dir
When it happens
Trigger: add_skill_archive with a zip (or tar) whose entries have names resolving outside the extraction dir — absolute names, ../ segments, or link members whose linkname escapes.
Common situations: Zips created on Windows with absolute paths or backslash traversal, downloaded skill archives containing symlinks out of the root, repackaged content from untrusted marketplaces.
Related errors
- unsafe tar member: ' + m.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/d0e7166409284256.
Report an issue: GitHub.