{"record":{"id":"80971d94a1ff8b2b","repo":"siyuan-note/siyuan","slug":"invalid-archive-entry-s","errorCode":null,"errorMessage":"invalid archive entry [%s]","messagePattern":"invalid archive entry \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/archive.go","lineNumber":253,"sourceCode":"// unzipWorkspaceArchive 先校验全部条目，阻止已知非法路径导致部分写入，再从同一个归档句柄解压。\nfunc unzipWorkspaceArchive(zipPath, destination string) error {\n\treader, err := archivezip.OpenReader(zipPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer reader.Close()\n\n\tpaths := make([]string, len(reader.File))\n\tfor i, entry := range reader.File {\n\t\tname := entry.Name\n\t\tif !utf8.ValidString(name) {\n\t\t\tif name, err = simplifiedchinese.GB18030.NewDecoder().String(name); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tname = strings.ReplaceAll(name, \"\\\\\", \"/\")\n\t\tif !filepath.IsLocal(filepath.FromSlash(name)) || entry.Mode()&os.ModeSymlink != 0 {\n\t\t\treturn fmt.Errorf(\"invalid archive entry [%s]\", name)\n\t\t}\n\t\tpaths[i] = filepath.Join(destination, filepath.FromSlash(name))\n\t\tif err = validateArchiveEntryPath(destination, paths[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor i, entry := range reader.File {\n\t\t// 解压前再次检查已有符号链接和加密身份，不复用预检阶段的路径判定结果。\n\t\tif err = validateArchiveEntryPath(destination, paths[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = extractWorkspaceArchiveEntry(entry, paths[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/api/archive.go#L235-L271","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (shell)\nzip -ry out.zip ./notebook  # stores symlinks\n\n// after (shell)\ncd notebook && zip -r ../out.zip .  # relative paths, symlinks followed","handlingStrategy":"validation","validationCode":"// pre-check archive entries with a zip reader before upload\nfor _, f := range r.File {\n    if strings.HasPrefix(f.Name, \"/\") || strings.Contains(f.Name, \"..\") || strings.Contains(f.Name, \":\") {\n        return fmt.Errorf(\"unsafe entry %q\", f.Name)\n    }\n    if f.Mode()&os.ModeSymlink != 0 {\n        return fmt.Errorf(\"symlink entry %q not allowed\", f.Name)\n    }\n}","typeGuard":null,"tryCatchPattern":"err := unzipArchive(f, dest)\nif err != nil && strings.Contains(err.Error(), \"invalid archive entry\") {\n    // reject/flag the archive; ask user to re-zip without symlinks and relative paths\n}","preventionTips":["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"],"tags":["archive","security","zip-slip"],"backgroundTag":"path-traversal-blocked","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}