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
- Rebuild the archive with relative, plain file/dir entries: `cd folder && zip -r ../out.zip .` without -y.
- Inspect the archive (unzip -l) and remove symlink or absolute-path entries before re-uploading.
- 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
- Create archives with relative paths: cd dir && zip -r ../out.zip .
- Never use zip -y (store symlinks) for archives destined for import
- Scan archives for symlink and absolute-path entries before distributing
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
- symlink resolves into encrypted notebook [%s]
- path belongs to encrypted notebook [%s]
- invalid archive entry path [%s]
- archive entry resolves outside destination [%s]
- marketplace package contains too many files
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/80971d94a1ff8b2b.
Report an issue: GitHub.