{"record":{"id":"2b4f5d4f95f92b7f","repo":"router-for-me/CLIProxyAPI","slug":"zip-entry-s-uses-backslash-path-separators","errorCode":null,"errorMessage":"zip entry %s uses backslash path separators","messagePattern":"zip entry (.+?) uses backslash path separators","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":380,"sourceCode":"\t\treturn nil, 0, fmt.Errorf(\"read %s: %w\", targetName, errRead)\n\t}\n\tmode := target.FileInfo().Mode().Perm()\n\tif mode == 0 {\n\t\tmode = 0o755\n\t}\n\treturn data, mode, nil\n}\n\nfunc versionedPluginFileName(id string, version string, goos string) string {\n\treturn strings.TrimSpace(id) + \"-v\" + normalizeVersion(version) + pluginExtension(goos)\n}\n\nfunc cleanZipName(name string) (string, error) {\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"zip entry has empty name\")\n\t}\n\tif strings.Contains(name, `\\`) {\n\t\treturn \"\", fmt.Errorf(\"zip entry %s uses backslash path separators\", name)\n\t}\n\tif path.IsAbs(name) {\n\t\treturn \"\", fmt.Errorf(\"zip entry %s is absolute\", name)\n\t}\n\tcleaned := path.Clean(name)\n\tif cleaned == \".\" || cleaned == \"..\" || strings.HasPrefix(cleaned, \"../\") {\n\t\treturn \"\", fmt.Errorf(\"zip entry %s escapes archive root\", name)\n\t}\n\treturn cleaned, nil\n}\n\nfunc regularZipFile(file *zip.File) bool {\n\tmode := file.FileInfo().Mode()\n\treturn mode.IsRegular() || mode.Type() == 0\n}\n\nfunc hasDynamicLibraryExtension(name string) bool {\n\tlowerName := strings.ToLower(name)","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L362-L398","documentation":"Returned by cleanZipName (install.go:379-381) when an entry name contains a backslash. The zip spec mandates forward slashes; backslashes are ambiguous (a legal filename character on Unix, a separator on Windows) and are a classic zip-slip vector, so the store rejects them outright instead of guessing.","triggerScenarios":"InstallArchive on a zip written by a Windows tool that stored paths like 'bin\\\\myplugin.dll'. Any entry in the archive triggers it — the check runs for all entries before extension filtering, not just the target library.","commonSituations":"Artifacts zipped with PowerShell's Compress-Archive or .NET System.IO.Compression in some configurations; older Windows packers that stored native separators.","solutions":["Rebuild the archive with forward-slash paths (e.g. use Go's archive/zip, Info-ZIP, or 'tar' equivalents)","If stuck with the original producer, re-zip on a system that normalizes separators: unzip then zip fresh","Fix the release pipeline to use a spec-compliant zip library"],"exampleFix":"# before (PowerShell, stores backslashes)\nCompress-Archive -Path bin\\myplugin.dll -DestinationPath plugin.zip\n# after (forward slashes, spec compliant)\nzip plugin.zip bin/myplugin.dll","handlingStrategy":"validation","validationCode":"func zipUsesForwardSlashes(archiveData []byte) error {\n    r, err := zip.NewReader(bytes.NewReader(archiveData), int64(len(archiveData)))\n    if err != nil { return err }\n    for _, f := range r.File {\n        if strings.Contains(f.Name, `\\`) {\n            return fmt.Errorf(\"entry %q uses backslash separators\", f.Name)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Build release archives with spec-compliant tools (Info-ZIP, Go archive/zip) not path-naive Windows packers","Add an archive lint (unzip -l / custom scan) to the release pipeline","Prefer tar.gz or spec-correct zips consistently across platforms"],"tags":["pluginstore","zip","security","windows"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}