siyuan-note/siyuan · error

invalid archive entry [%s]

Error message

invalid archive entry [%s]

What it means

During workspace-archive extraction, unzipWorkspaceArchive validates each entry name. An entry is rejected if its name is not a local relative path (absolute paths, `..` segments, drive letters) or if the entry is a symlink. This prevents zip-slip attacks (escaping the destination via crafted paths) and symlink-based overwrites. The entry name is GB18030-decoded first when flagged non-UTF-8.

Source

Thrown at kernel/api/archive.go:253

// unzipWorkspaceArchive 先校验全部条目,阻止已知非法路径导致部分写入,再从同一个归档句柄解压。
func unzipWorkspaceArchive(zipPath, destination string) error {
	reader, err := archivezip.OpenReader(zipPath)
	if err != nil {
		return err
	}
	defer reader.Close()

	paths := make([]string, len(reader.File))
	for i, entry := range reader.File {
		name := entry.Name
		if !utf8.ValidString(name) {
			if name, err = simplifiedchinese.GB18030.NewDecoder().String(name); err != nil {
				return err
			}
		}
		name = strings.ReplaceAll(name, "\\", "/")
		if !filepath.IsLocal(filepath.FromSlash(name)) || entry.Mode()&os.ModeSymlink != 0 {
			return fmt.Errorf("invalid archive entry [%s]", name)
		}
		paths[i] = filepath.Join(destination, filepath.FromSlash(name))
		if err = validateArchiveEntryPath(destination, paths[i]); err != nil {
			return err
		}
	}
	for i, entry := range reader.File {
		// 解压前再次检查已有符号链接和加密身份,不复用预检阶段的路径判定结果。
		if err = validateArchiveEntryPath(destination, paths[i]); err != nil {
			return err
		}
		if err = extractWorkspaceArchiveEntry(entry, paths[i]); err != nil {
			return err
		}
	}
	return nil
}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Rebuild the archive with relative, plain file/dir entries: `cd folder && zip -r ../out.zip .` without -y.
  2. Inspect the archive (unzip -l) and remove symlink or absolute-path entries before re-uploading.
  3. If the symlink content is needed, replace the link with a copy of the target file and re-zip.

Example fix

// before (shell)
zip -ry out.zip ./notebook  # stores symlinks

// after (shell)
cd notebook && zip -r ../out.zip .  # relative paths, symlinks followed
Defensive patterns

Strategy: validation

Validate before calling

// pre-check archive entries with a zip reader before upload
for _, f := range r.File {
    if strings.HasPrefix(f.Name, "/") || strings.Contains(f.Name, "..") || strings.Contains(f.Name, ":") {
        return fmt.Errorf("unsafe entry %q", f.Name)
    }
    if f.Mode()&os.ModeSymlink != 0 {
        return fmt.Errorf("symlink entry %q not allowed", f.Name)
    }
}

Try / catch

err := unzipArchive(f, dest)
if err != nil && strings.Contains(err.Error(), "invalid archive entry") {
    // reject/flag the archive; ask user to re-zip without symlinks and relative paths
}

Prevention

When it happens

Trigger: Calling the unzip API on an archive containing an entry named like `/etc/passwd`, `C:\evil.txt`, `../outside.txt`, or an entry whose mode includes os.ModeSymlink. Any archive built on Unix with `zip -y` (symlinks stored) or with absolute/`..` paths triggers this.

Common situations: Archives downloaded from the internet containing symlinks; archives created with `zip -y` on macOS/Linux; maliciously crafted zip-slip archives; archives with Windows absolute entry names.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/80971d94a1ff8b2b. Report an issue: GitHub.