{"record":{"id":"f2cebacb2c2affc5","repo":"xtekky/gpt4free","slug":"archive-has-no-top-level-directory","errorCode":null,"errorMessage":"archive has no top-level directory","messagePattern":"archive has no top-level directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"g4f-go/process.go","lineNumber":71,"sourceCode":"func noSignalCtx() context.Context { return context.Background() }\n\n// extractZip unpacks a runtime archive into dest with zip-slip protection.\n// The archive has a single top-level directory (e.g. \"linux-x64/\"); we strip it\n// so the runtime lands directly in dest, matching the launcher layout.\nfunc extractZip(r io.ReaderAt, size int64, dest string) error {\n\tzr, err := zip.NewReader(r, size)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar top string\n\tfor _, f := range zr.File {\n\t\tparts := strings.Split(filepath.Clean(f.Name), string(os.PathSeparator))\n\t\tif len(parts) > 0 && parts[0] != \".\" && top == \"\" {\n\t\t\ttop = parts[0]\n\t\t}\n\t}\n\tif top == \"\" || top == \".\" {\n\t\treturn fmt.Errorf(\"archive has no top-level directory\")\n\t}\n\n\tfor _, f := range zr.File {\n\t\trel := f.Name\n\t\tif top != \"\" {\n\t\t\trel = strings.TrimPrefix(f.Name, top+\"/\")\n\t\t\trel = strings.TrimPrefix(rel, top)\n\t\t}\n\t\trel = strings.TrimPrefix(rel, \"/\")\n\t\tname := filepath.Clean(rel)\n\t\tif name == \".\" || name == \"\" {\n\t\t\tcontinue // skip the top dir itself\n\t\t}\n\t\tif name == \"..\" || strings.HasPrefix(name, \"..\"+string(os.PathSeparator)) {\n\t\t\treturn fmt.Errorf(\"unsafe path in archive: %s\", f.Name)\n\t\t}\n\t\ttarget := filepath.Join(dest, name)\n\t\tif !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) && target != filepath.Clean(dest) {","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/xtekky/gpt4free/blob/973504e1770928ed5fb82f43da528f441ad9ddc3/g4f-go/process.go#L53-L89","documentation":"The zip extractor in g4f-go/process.go scans entries to find the archive's single top-level directory (first path component other than '.'). If every entry cleans to '.' — i.e. the zip has no leading directory (files at the archive root) — it errors 'archive has no top-level directory', because the subsequent stripping logic assumes a top dir to remove.","triggerScenarios":"Feeding extractZip a flat archive whose members are like 'python.exe', 'lib/x.so' instead of 'python-embed/python.exe'. This includes zips produced by 'zip -j' (junk paths) or repacked artifacts.","commonSituations":"Re-zipping a runtime distribution for portability and dropping the top folder; a mirror re-packaging the official zip; manifest URL pointing to a differently-structured zip than expected.","solutions":["Repack with the top-level directory preserved: cd parent && zip -r runtime.zip mytopdir","Download the original zip from the URL pinned in the manifest instead of a re-packaged mirror","Update runtime.json to point at an archive with the expected layout","Verify layout first: unzip -l archive.zip — entries should all start with one common directory"],"exampleFix":"# before\nunzip -l runtime.zip\n# python.exe\n# lib/x.so           -> archive has no top-level directory\n\n# after\nmkdir pyrt && mv python.exe lib pyrt/\nzip -r runtime.zip pyrt\n# entries now: pyrt/python.exe, pyrt/lib/x.so","handlingStrategy":"validation","validationCode":"// confirm the zip has one common top-level directory\nfunc zipHasTopDir(path string) (bool, error) {\n    r, err := zip.OpenReader(path)\n    if err != nil { return false, err }\n    defer r.Close()\n    for _, f := range r.File {\n        p := filepath.Clean(f.Name)\n        if p != \".\" && p != \"\" {\n            return true, nil // found a real leading component\n        }\n    }\n    return false, nil\n}","typeGuard":null,"tryCatchPattern":"if err := extractZip(dest, r, size); err != nil {\n    if strings.Contains(err.Error(), \"no top-level directory\") {\n        // repack with a top dir or fetch the original archive\n    }\n    return err\n}","preventionTips":["Always zip distributions with 'zip -r out.zip topdir' from the parent directory","Verify layout with unzip -l before distributing or pinning a URL","Prefer official archives over re-packaged mirrors"],"tags":["go","zip","extraction","archive-layout"],"backgroundTag":null,"analyzedSha":"973504e1770928ed5fb82f43da528f441ad9ddc3","analyzedAt":"2026-08-14T23:45:32.408Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}