{"record":{"id":"0ebfb0b551954590","repo":"agentscope-ai/agentscope","slug":"unsafe-tar-member-m-name","errorCode":null,"errorMessage":"unsafe tar member: ' + m.name","messagePattern":"unsafe tar member: ' \\+ m\\.name","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/agentscope/workspace/_base.py","lineNumber":173,"sourceCode":"    \"    os.rename(staging, partition)\\n\"\n    \"except OSError:\\n\"\n    \"    shutil.rmtree(staging, ignore_errors=True)\\n\"\n    \"print('equipped')\\n\"\n)\n\n_EXTRACT_TAR_SHIM = (\n    \"import tarfile, sys, os\\n\"\n    \"src, dst = sys.argv[1], sys.argv[2]\\n\"\n    \"os.makedirs(dst, exist_ok=True)\\n\"\n    \"dst_real = os.path.realpath(dst)\\n\"\n    \"tf = tarfile.open(src)\\n\"\n    \"try:\\n\"\n    \"    members = tf.getmembers()\\n\"\n    \"    for m in members:\\n\"\n    \"        target = os.path.realpath(os.path.join(dst, m.name))\\n\"\n    \"        if not (target == dst_real\"\n    \" or target.startswith(dst_real + os.sep)):\\n\"\n    \"            raise Exception('unsafe tar member: ' + m.name)\\n\"\n    \"    tf.extractall(dst, members=members)\\n\"\n    \"finally:\\n\"\n    \"    tf.close()\\n\"\n    \"os.unlink(src)\\n\"\n)\n\n#: Expands an archive inside the sandbox. Runs there rather than on the\n#: server so a traversing or bomb-sized archive detonates in the\n#: isolated environment. Argv: src, dst, format, max extracted bytes.\n_EXTRACT_ARCHIVE_SHIM = (\n    \"import os, sys, tarfile, zipfile\\n\"\n    \"src, dst, fmt, limit = sys.argv[1:5]\\n\"\n    \"limit = int(limit)\\n\"\n    \"os.makedirs(dst, exist_ok=True)\\n\"\n    \"dst_real = os.path.realpath(dst)\\n\"\n    \"def check(name):\\n\"\n    \"    target = os.path.realpath(os.path.join(dst, name))\\n\"\n    \"    if not (target == dst_real\"","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/workspace/_base.py#L155-L191","documentation":"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.","triggerScenarios":"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.","commonSituations":"Malicious or malformed third-party skill tarballs, archives built with unusual tooling producing absolute member names, symlink members targeting files outside the extraction dir.","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"],"exampleFix":"# before: archive built from parent dir\n# tar -cf skill.tar ../my-skill  -> members like '../my-skill/SKILL.md'\n\n# after: build from inside the directory\ncd my-skill && tar -cf ../skill.tar .\n# members are now 'SKILL.md', etc.","handlingStrategy":"validation","validationCode":"import tarfile\n\ndef tar_is_safe(path: str, expected_root: str = '') -> bool:\n    with tarfile.open(path) as tf:\n        for m in tf.getmembers():\n            if m.name.startswith('/') or '..' in m.name.split('/'):\n                return False\n            if (m.issym() or m.islnk()) and (m.linkname.startswith('/') or '..' in m.linkname.split('/')):\n                return False\n    return True","typeGuard":"def is_safe_tarfile(path: str) -> bool:\n    try:\n        return tar_is_safe(path)\n    except tarfile.TarError:\n        return False","tryCatchPattern":"try:\n    await ws.add_skill_archive('skill.tar', 'my-skill')\nexcept (RuntimeError, ValueError) as e:\n    if 'unsafe tar member' in str(e):\n        raise SecurityError(f'rejecting malicious skill archive: {e}')\n    raise","preventionTips":["Only install skill archives from trusted sources","Pre-scan archives for traversal entries before installing","Build archives from inside the skill directory"],"tags":["tar","path-traversal","zip-slip","security","skill"],"backgroundTag":"archive-path-traversal","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}