anthropics/skills · error · ValueError
unsafe archive entry: {m.filename!r}
Error message
unsafe archive entry: {m.filename!r} What it means
safe_extract() raises this when an archive entry name resolves (after dest resolution) to a path outside the destination directory — absolute names like '/etc/cron.d/x' or traversal names like '../../home/u/.bashrc'. It is the classic zip-slip defense; without it, extraction could overwrite arbitrary files. Present in both docx and pptx office/helpers.
Source
Thrown at skills/docx/scripts/office/helpers/__init__.py:81
def part_text(data: bytes) -> str:
return data.decode("utf-8", "surrogateescape")
XML_SPACE = " \t\r\n"
def rendered_text(text: str, preserve: bool) -> str:
return text if preserve else text.strip(XML_SPACE)
def safe_extract(zf: zipfile.ZipFile, dest: Path) -> None:
dest = dest.resolve()
for m in zf.infolist():
if stat.S_ISLNK(m.external_attr >> 16):
raise ValueError(f"symlink archive entry not allowed: {m.filename!r}")
target = (dest / m.filename).resolve()
if not target.is_relative_to(dest):
raise ValueError(f"unsafe archive entry: {m.filename!r}")
zf.extract(m, dest)
def rezip(src_dir: Path, out_path: Path) -> None:
files = sorted(p for p in src_dir.rglob("*") if p.is_file())
ct = src_dir / "[Content_Types].xml"
fd, tmp_name = tempfile.mkstemp(
prefix=out_path.name + ".", suffix=".tmp", dir=out_path.parent
)
tmp_out = Path(tmp_name)
try:
with os.fdopen(fd, "wb") as fh:
with zipfile.ZipFile(fh, "w", zipfile.ZIP_DEFLATED) as zf:
if ct.exists():
zf.write(ct, ct.relative_to(src_dir), compress_type=zipfile.ZIP_STORED)
for f in files:
if f == ct:
continueView on GitHub (pinned to f6656c1256)
Solutions
- Reject and quarantine the input — the extraction guard fired on a genuinely dangerous or malformed archive.
- Inspect the offending entry name from the message and, if legitimate corruption, rebuild the archive with normalized relative names.
- Pre-scan archives before processing (check for absolute names or '..' segments) so malicious files never reach the tool.
- Ensure your pipeline only feeds .docx/.pptx from trusted sources through these scripts.
Example fix
# before
with zipfile.ZipFile(path) as zf:
zf.extractall(dest) # zip-slip vulnerable
# after
from office.helpers import safe_extract
with zipfile.ZipFile(path) as zf:
safe_extract(zf, Path(dest)) # raises on escaping entries Defensive patterns
Strategy: validation
Validate before calling
import zipfile
def has_escaping_entries(path: str) -> bool:
with zipfile.ZipFile(path) as zf:
return any(n.startswith("/") or n.startswith("\\") or ".." in n.replace("\\", "/").split("/") for n in zf.namelist()) Try / catch
from office.helpers import safe_extract
try:
safe_extract(zf, dest)
except ValueError as e:
if "unsafe archive entry" in str(e):
quarantine(path) # zip-slip attempt; never extract with a fallback extractor
else:
raise Prevention
- Pre-scan namelist() for absolute paths and '..' segments.
- Extract only into a fresh, disposable temp directory.
- Never replace safe_extract with raw ZipFile.extractall on untrusted input.
- Quarantine and report files that trip this guard.
When it happens
Trigger: Unpacking a crafted or corrupted OOXML/zip whose member filename is absolute ('/tmp/evil') or contains '..' segments escaping dest; also seen when a non-OOXML zip is mistakenly fed to the unpack flow.
Common situations: Malicious uploads targeting naive extract(); archives produced by buggy writers that mangle names; processing random zips found in the wild with tooling that assumes Office files.
Related errors
- symlink archive entry not allowed: {m.filename!r}
- relationship target escapes the package: {target!r}
- symlink archive entry not allowed: {m.filename!r}
- unsafe archive entry: {m.filename!r}
- relationship target is not a POSIX part name: {target!r}
AI-assisted analysis of anthropics/skills@f6656c1256 (2026-08-14).
Data as JSON: /api/errors/93491881c67d6c3c.
Report an issue: GitHub.