router-for-me/CLIProxyAPI · error

zip entry %s is not a regular file

Error message

zip entry %s is not a regular file

What it means

Thrown by readTargetLibrary while scanning the archive: a zip entry that is neither a directory nor a regular file (regularZipFile false — e.g. a symlink, device, or FIFO mode). The extractor refuses non-regular entries to prevent symlink-based attacks and unextractable special files; it scans every entry with a dynamic-library extension, so an unexpected mode on any matching entry aborts the install.

Source

Thrown at internal/pluginstore/install.go:331

		return "", fmt.Errorf("invalid plugin version %q", version)
	}
	return filepath.Join(options.PluginsDir, options.GOOS, options.GOARCH, versionedPluginFileName(id, version, options.GOOS)), nil
}

func readTargetLibrary(reader *zip.Reader, id string, version string, goos string) ([]byte, os.FileMode, error) {
	targetName := strings.TrimSpace(id) + pluginExtension(goos)
	versionedTargetName := versionedPluginFileName(id, version, goos)
	var target *zip.File
	for _, file := range reader.File {
		cleanedName, errClean := cleanZipName(file.Name)
		if errClean != nil {
			return nil, 0, errClean
		}
		if file.FileInfo().IsDir() {
			continue
		}
		if !regularZipFile(file) {
			return nil, 0, fmt.Errorf("zip entry %s is not a regular file", file.Name)
		}
		if !hasDynamicLibraryExtension(cleanedName) {
			continue
		}
		if cleanedName != targetName && cleanedName != versionedTargetName {
			if path.Base(cleanedName) == targetName || path.Base(cleanedName) == versionedTargetName {
				return nil, 0, fmt.Errorf("target dynamic library must be at zip root")
			}
			return nil, 0, fmt.Errorf("dynamic library filename must be %s or %s", targetName, versionedTargetName)
		}
		if target != nil {
			return nil, 0, fmt.Errorf("zip contains multiple target dynamic libraries")
		}
		target = file
	}
	if target == nil {
		return nil, 0, fmt.Errorf("zip does not contain %s", targetName)
	}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Rebuild the archive with real file contents instead of symlinks (zip without -y, or cp -L before zipping)
  2. If consuming third-party artifacts, reject the artifact at the checksum/verification layer and report it upstream
  3. Inspect with 'zipinfo -l' to find the non-regular entry

Example fix

# before
ln -s /opt/lib/real.so myplugin.so && zip -y plugin.zip myplugin.so # symlink stored

# after
cp /opt/lib/real.so myplugin.so && zip plugin.zip myplugin.so # regular file stored
Defensive patterns

Strategy: validation

Validate before calling

func archiveHasOnlyRegularEntries(data []byte) error {
    zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
    if err != nil {
        return err
    }
    for _, f := range zr.File {
        mode := f.FileInfo().Mode()
        if mode.IsDir() {
            continue
        }
        if !mode.IsRegular() {
            return fmt.Errorf("entry %s has mode %v (not a regular file)", f.Name, mode)
        }
    }
    return nil
}

Type guard

func isNonRegularZipEntry(err error) bool {
    return err != nil && strings.Contains(err.Error(), "is not a regular file")
}

Try / catch

if _, err := pluginstore.InstallArchive(data, plugin, options); err != nil {
    if isNonRegularZipEntry(err) {
        // reject artifact: rebuild archive without symlinks (zip without -y)
    }
}

Prevention

When it happens

Trigger: An archive containing a symlink named like the plugin library (e.g. 'myplugin.so -> ../../system.so'), or entries created by archive tools that record unusual unix modes; triggered as soon as such an entry has a dynamic library extension.

Common situations: Repacking plugins with 'zip -y' (preserves symlinks) on Linux; malicious or tampered artifacts attempting path redirection; archives built by nonstandard tooling emitting odd external attributes.

Related errors


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