{"record":{"id":"b6533392a2ebc9a1","repo":"siyuan-note/siyuan","slug":"marketplace-package-contains-an-invalid-path","errorCode":null,"errorMessage":"marketplace package contains an invalid path","messagePattern":"marketplace package contains an invalid path","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/bazaar/local.go","lineNumber":132,"sourceCode":"\t\t}\n\t}\n\n\tif err = os.MkdirAll(destination, 0755); err != nil {\n\t\treturn err\n\t}\n\tvar extractedTotal uint64\n\tfor _, item := range reader.File {\n\t\tif err = extractLocalPackageItem(item, destination, &extractedTotal); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc extractLocalPackageItem(item *zip.File, destination string, extractedTotal *uint64) error {\n\tname := strings.ReplaceAll(item.Name, \"\\\\\", \"/\")\n\tif name == \"\" || strings.HasPrefix(name, \"/\") {\n\t\treturn errors.New(\"marketplace package contains an invalid path\")\n\t}\n\tdestinationPath := filepath.Join(destination, filepath.FromSlash(name))\n\tif !gulu.File.IsSubPath(destination, destinationPath) {\n\t\treturn errors.New(\"marketplace package contains an invalid path\")\n\t}\n\n\tmode := item.Mode()\n\tif mode&os.ModeSymlink != 0 || (!mode.IsRegular() && !mode.IsDir()) {\n\t\treturn errors.New(\"marketplace package contains an unsupported file\")\n\t}\n\tif mode.IsDir() {\n\t\treturn os.MkdirAll(destinationPath, 0755)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(destinationPath), 0755); err != nil {\n\t\treturn err\n\t}\n\n\tsource, err := item.Open()","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/bazaar/local.go#L114-L150","documentation":"During extraction, each zip entry name is normalized and validated: it must be non-empty, must not be absolute (leading '/'), and the joined destination path must remain a sub-path of the extraction directory (blocks '../' escapes and absolute or drive-qualified targets). Any violation throws this error — this is the Zip Slip defense.","triggerScenarios":"extractLocalPackageItem encounters an entry named \"\", one starting with \"/\", or one whose resolved path (via ../ or absolute name) escapes the destination directory.","commonSituations":"Malicious package crafted to overwrite files outside the temp dir (Zip Slip); zip built on Windows with absolute paths; zip built by a buggy tool that includes leading slashes; symlink-style path tricks in entry names.","solutions":["Do not install the archive; treat it as malicious or corrupt","Rebuild the zip with relative paths only (zip from inside the package directory)","Inspect entry names (unzip -l) for leading '/' or '..' before re-uploading","Repackage using a standard tool from the package root"],"exampleFix":"// before: entry name \"/etc/passwd\" or \"../../evil.sh\"\n// after: cd my-plugin && zip -r ../my-plugin.zip .   # yields relative entries like \"plugin.json\"","handlingStrategy":"validation","validationCode":"const names = execSync(`zipinfo -1 ${zipPath}`).toString().split(\"\\n\").filter(Boolean);\nconst evil = names.some(n => n.startsWith(\"/\") || n.includes(\"..\") || /^[A-Za-z]:/.test(n));\nif (evil) throw new Error(\"archive contains unsafe entry paths\");","typeGuard":null,"tryCatchPattern":"try { await installLocalPackage(zipPath); } catch (e) { if (String(e).includes(\"invalid path\")) { /* reject the package; do not retry */ } else throw e; }","preventionTips":["Zip with relative paths from inside the package directory","Scan entry names for leading '/', '..', or drive letters before installing","Never attempt to bypass this check — it protects the filesystem (Zip Slip)"],"tags":["security","zip-slip","path-traversal","zip"],"backgroundTag":"path-traversal-blocked","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}