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 string

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Do not install the artifact — treat it as untrusted and discard it
  2. Rebuild the archive from trusted sources using relative, root-level entry names
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/a6b32b53fccca134. Report an issue: GitHub.