{"record":{"id":"f8a92fe6bd13aa93","repo":"siyuan-note/siyuan","slug":"invalid-archive-entry-path-s","errorCode":null,"errorMessage":"invalid archive entry path [%s]","messagePattern":"invalid archive entry path \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/archive.go","lineNumber":275,"sourceCode":"\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\nfunc validateArchiveEntryPath(destination, entryPath string) error {\n\trel, err := filepath.Rel(destination, entryPath)\n\tif err != nil || !filepath.IsLocal(rel) {\n\t\treturn fmt.Errorf(\"invalid archive entry path [%s]\", entryPath)\n\t}\n\tresolved, err := resolveArchivePath(entryPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresolvedDestination, err := resolveArchivePath(destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\trel, err = filepath.Rel(resolvedDestination, resolved)\n\tif err != nil || !filepath.IsLocal(rel) {\n\t\treturn fmt.Errorf(\"archive entry resolves outside destination [%s]\", entryPath)\n\t}\n\treturn rejectEncryptedArchivePath(entryPath)\n}\n\nfunc extractWorkspaceArchiveEntry(entry *archivezip.File, destination string) error {\n\tif entry.FileInfo().IsDir() {","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/api/archive.go#L257-L293","documentation":"validateArchiveEntryPath re-checks the fully joined destination+entry path: computing filepath.Rel(destination, entryPath) must succeed and yield a local relative path. This is a second, path-joined defense after the per-entry name check; if the joined path escapes the destination directory (or Rel fails, e.g. on mismatched volumes in Windows), extraction of that entry is refused with the offending path in the message.","triggerScenarios":"An archive entry whose cleaned/joined path (destination + name) is not inside destination — e.g. entry names containing `..` that survive name normalization, or paths that Rel cannot compute against the destination (different Windows drive roots). Called from unzipWorkspaceArchive for every entry.","commonSituations":"Crafted zip-slip archives using `a/../../evil` names; mixed-separator names that normalize unexpectedly; cross-drive destinations on Windows where Rel returns an error.","solutions":["Fix the archive so all entries are plain relative names without `..` segments.","Ensure the unzip destination and the archive share the same drive/root on Windows.","Extract with a standard tool to inspect which entry carries the bad path and remove it."],"exampleFix":"// before\n// entry name: \"a/../../evil.txt\"\n\n// after\n// entry name: \"a/evil.txt\" (relative, local under destination)","handlingStrategy":"validation","validationCode":"clean := filepath.Clean(filepath.Join(dest, entryName))\nrel, err := filepath.Rel(dest, clean)\nif err != nil || !filepath.IsLocal(rel) {\n    return fmt.Errorf(\"entry escapes destination: %s\", entryName)\n}","typeGuard":null,"tryCatchPattern":"if err := unzipArchive(f, dest); err != nil && strings.Contains(err.Error(), \"invalid archive entry path\") {\n    log.Warnf(\"entry escaped destination on same-drive check: %v\", err)\n}","preventionTips":["Ensure archive entry names contain no `..` segments after cleaning","Extract on the same drive/root as the archive paths on Windows","Run the same Rel/IsLocal check in client-side pre-validation"],"tags":["archive","security","path-validation"],"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"}