{"record":{"id":"1e186221be4e71b3","repo":"router-for-me/CLIProxyAPI","slug":"zip-entry-has-empty-name","errorCode":null,"errorMessage":"zip entry has empty name","messagePattern":"zip entry has empty name","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":377,"sourceCode":"\t}()\n\tdata, errRead := io.ReadAll(handle)\n\tif errRead != nil {\n\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}","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L359-L395","documentation":"Returned by cleanZipName (install.go:375-378) when a zip entry's name is empty or only whitespace. Entry names are sanitized before any matching happens, and an unnamed entry is treated as malformed input — it cannot be safely mapped to a filesystem path, so the whole install is rejected.","triggerScenarios":"InstallArchive on a zip containing an entry whose Name field is \"\" or \"   \". This happens with hand-crafted zips, some zip libraries that allow zero-length names, or deliberately malformed archives (the check is also a hardening measure against path manipulation).","commonSituations":"A test fixture or script-generated zip built with a library that permits empty entry names; fuzzed or hostile archives fed to an install endpoint.","solutions":["Rebuild the zip from a known-good source and confirm every entry has a name: unzip -l artifact.zip","If producing archives programmatically, assert entry names are non-empty before writing them","Reject or quarantine the artifact at whatever boundary accepted it — an empty entry name signals a malformed or hostile archive"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func entriesNamed(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.TrimSpace(f.Name) == \"\" {\n            return fmt.Errorf(\"rejecting archive: entry %d has empty name\", f.Name)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := entriesNamed(data); err != nil {\n    // quarantine artifact before it reaches InstallArchive\n} else if _, err := store.InstallArchive(data, plugin, opts); err != nil { /* handle */ }","preventionTips":["Only accept archives from trusted release pipelines, never arbitrary uploads","When building zips programmatically, assert non-empty entry names at write time","Fuzz-test any HTTP endpoint that accepts archives for install"],"tags":["pluginstore","zip","validation","security"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}