anthropics/skills · error · ValueError

symlink archive entry not allowed: {m.filename!r}

Error message

symlink archive entry not allowed: {m.filename!r}

What it means

safe_extract() refuses to extract any zip entry whose unix mode bits (external_attr >> 16) indicate a symlink. Office files are expected to contain only regular files; symlinks in archives are a classic vehicle for arbitrary-file-write attacks, so extraction aborts before writing anything for that entry.

Source

Thrown at skills/xlsx/scripts/office/helpers/__init__.py:78

    return posixpath.join(owner_dir.as_posix(), rels_file.name[: -len(".rels")]).lstrip("./")


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)

View on GitHub (pinned to f6656c1256)

Solutions

  1. Inspect the archive: `unzip -l file.xlsx` plus `zipinfo` shows the mode column — symlinks appear as 'l' in `zipinfo`
  2. Do not attempt to bypass; reject or quarantine the file — a symlink entry in an Office package is never legitimate
  3. If it is your own fixture, re-zip without symlinks: dereference with `zip -r` (no -y) or tar the files fresh
Defensive patterns

Strategy: validation

Validate before calling

import stat, zipfile

def archive_has_symlinks(path: str) -> bool:
    with zipfile.ZipFile(path) as zf:
        return any(stat.S_ISLNK(m.external_attr >> 16) for m in zf.infolist())

Try / catch

try:
    safe_extract(zf, dest)
except ValueError as e:
    if "symlink" in str(e):
        quarantine(file_path)  # never extract anyway — this is hostile content
    raise

Prevention

When it happens

Trigger: Calling safe_extract(zf, dest) on an OOXML package (or any zip) that contains an entry created with symlink mode bits — e.g. crafted with `ln -s /etc/passwd l; zip -y evil.zip l`; archives produced on unix systems that preserved symlinks.

Common situations: Processing Office documents from untrusted sources (email attachments, uploads); test fixtures accidentally zipped with -y; hostile files specifically targeting zip-slip-adjacent extraction bugs.

Related errors


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