anthropics/skills · error · ValueError

unsafe archive entry: {m.filename!r}

Error message

unsafe archive entry: {m.filename!r}

What it means

safe_extract() zip-slip guard: after resolving dest/filename against the resolved destination, any entry whose real path falls outside dest is rejected. This blocks names like '../../etc/cron.d/x' or absolute-ish paths that would write outside the extraction directory.

Source

Thrown at skills/xlsx/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:
                        continue

View on GitHub (pinned to f6656c1256)

Solutions

  1. List entry names before extracting: `python -c "import zipfile; [print(i.filename) for i in zipfile.ZipFile('f.xlsx').infolist()]"` — look for leading / or .. segments
  2. Treat it as malicious input: reject the file and alert, do not sanitize-and-continue
  3. Keep using safe_extract (never raw zf.extractall) for anything from outside your trust boundary
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def archive_entries_are_safe(zf, dest: Path) -> bool:
    dest = dest.resolve()
    return all((dest / m.filename).resolve().is_relative_to(dest) for m in zf.infolist())

Try / catch

try:
    safe_extract(zf, dest)
except ValueError as e:
    if "unsafe archive entry" in str(e):
        log.security("zip-slip attempt: %s", e)
        reject(file_path)
    raise

Prevention

When it happens

Trigger: Extracting an archive containing '../evil.txt', '....//evil', or entries whose resolved path escapes dest (symlinked parents inside the archive contribute too, though symlinks are rejected first by the check above); calling safe_extract on a hostile or corrupt zip.

Common situations: Untrusted uploads processed server-side; archives renamed from .zip to .xlsx to slip past type checks; path manipulation bugs in producers writing absolute names like '/etc/passwd' as entry names.

Related errors


AI-assisted analysis of anthropics/skills@f6656c1256 (2026-08-14). Data as JSON: /api/errors/93e955991d0e299e. Report an issue: GitHub.