router-for-me/CLIProxyAPI · error
zip entry %s escapes archive root
Error message
zip entry %s escapes archive root
What it means
Returned by cleanZipName (install.go:385-388) when an entry, after path.Clean, is '.', '..', or starts with '../' — i.e. it would resolve outside the archive root. This is the core zip-slip defense: such a name could escape the plugins directory when extracted, so install.go refuses the archive entirely.
Source
Thrown at internal/pluginstore/install.go:387
}
func versionedPluginFileName(id string, version string, goos string) string {
return strings.TrimSpace(id) + "-v" + normalizeVersion(version) + pluginExtension(goos)
}
func cleanZipName(name string) (string, error) {
if strings.TrimSpace(name) == "" {
return "", fmt.Errorf("zip entry has empty name")
}
if strings.Contains(name, `\`) {
return "", fmt.Errorf("zip entry %s uses backslash path separators", name)
}
if path.IsAbs(name) {
return "", fmt.Errorf("zip entry %s is absolute", name)
}
cleaned := path.Clean(name)
if cleaned == "." || cleaned == ".." || strings.HasPrefix(cleaned, "../") {
return "", fmt.Errorf("zip entry %s escapes archive root", name)
}
return cleaned, nil
}
func regularZipFile(file *zip.File) bool {
mode := file.FileInfo().Mode()
return mode.IsRegular() || mode.Type() == 0
}
func hasDynamicLibraryExtension(name string) bool {
lowerName := strings.ToLower(name)
return strings.HasSuffix(lowerName, ".dylib") || strings.HasSuffix(lowerName, ".so") || strings.HasSuffix(lowerName, ".dll")
}
type pluginFileInfo struct {
ID string
Path string
Version stringView on GitHub (pinned to 78f0c4079e)
Solutions
- Do not install the artifact — treat it as untrusted and discard it
- Rebuild the archive from trusted sources using relative, root-level entry names
- If you archive files yourself, sanitize names before adding entries (strip leading '../' segments)
Defensive patterns
Strategy: validation
Validate before calling
func zipEntriesContained(archiveData []byte) error {
r, err := zip.NewReader(bytes.NewReader(archiveData), int64(len(archiveData)))
if err != nil { return err }
for _, f := range r.File {
c := path.Clean(f.Name)
if c == "." || c == ".." || strings.HasPrefix(c, "../") {
return fmt.Errorf("entry %q escapes archive root", f.Name)
}
}
return nil
} Try / catch
if err := zipEntriesContained(data); err != nil {
// hostile or corrupt artifact: drop it and alert; never retry
} else {
res, err := store.InstallArchive(data, plugin, opts)
} Prevention
- Never install archives from untrusted origins — the store hard-rejects traversal names by design
- Audit third-party plugin sources before adding them to a registry
- Keep the plugins dir path fixed and predictable so escapes are detectable in audits
When it happens
Trigger: InstallArchive on a zip with entries named '..', '../..', 'dir/../../escape.so', or constructions like 'a/./../..' that clean to a parent escape. The check fires for every entry during the scan, regardless of file type.
Common situations: Malicious archives targeting zip-slip (CVE-2018-12689-style); corrupted zip central directories producing garbage names; rare archiver bugs writing dot components.
Related errors
- zip entry %s is absolute
- zip entry has empty name
- zip entry %s uses backslash path separators
- invalid name
- invalid auth file name
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/a6b32b53fccca134.
Report an issue: GitHub.