{"record":{"id":"fef6d63de2a74a58","repo":"CoplayDev/unity-mcp","slug":"zippath-required","errorCode":null,"errorMessage":"zipPath required","messagePattern":"zipPath required","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"MCPForUnity/Editor/Services/AssetGen/Import/SafeZipExtractor.cs","lineNumber":23,"sourceCode":"\nnamespace MCPForUnity.Editor.Services.AssetGen.Import\n{\n    /// <summary>\n    /// Extracts a .zip into a destination directory while rejecting Zip-Slip path traversal:\n    /// every entry's resolved target must stay inside <c>destDir</c>. Directory entries are\n    /// created; file entries are written by copying the entry stream (no reliance on the\n    /// ZipFileExtensions helper). Used to unpack marketplace model archives (e.g. Sketchfab).\n    ///\n    /// When <paramref name=\"allowedExtensions\"/> is supplied, file entries whose extension is not\n    /// on the allowlist are SKIPPED (not written). Callers that extract UNTRUSTED archives into the\n    /// Assets tree MUST pass an allowlist of inert asset types so executable content (.cs/.dll/\n    /// .asmdef) can never land under Assets/ and be compiled/loaded by the Editor.\n    /// </summary>\n    public static class SafeZipExtractor\n    {\n        public static void ExtractTo(string zipPath, string destDir, ISet<string> allowedExtensions = null)\n        {\n            if (string.IsNullOrEmpty(zipPath)) throw new ArgumentException(\"zipPath required\", nameof(zipPath));\n            if (string.IsNullOrEmpty(destDir)) throw new ArgumentException(\"destDir required\", nameof(destDir));\n\n            Directory.CreateDirectory(destDir);\n            string destFull = Path.GetFullPath(destDir);\n            string prefix = destFull.EndsWith(Path.DirectorySeparatorChar.ToString())\n                ? destFull\n                : destFull + Path.DirectorySeparatorChar;\n\n            using (FileStream fs = File.OpenRead(zipPath))\n            using (var archive = new ZipArchive(fs, ZipArchiveMode.Read))\n            {\n                foreach (ZipArchiveEntry entry in archive.Entries)\n                {\n                    string name = entry.FullName;\n                    if (string.IsNullOrEmpty(name)) continue;\n\n                    // Reject traversal / absolute paths up front.\n                    if (name.Contains(\"..\") || Path.IsPathRooted(name))","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/MCPForUnity/Editor/Services/AssetGen/Import/SafeZipExtractor.cs#L5-L41","documentation":"SafeZipExtractor.ExtractTo requires a non-empty zipPath and destDir; an empty zipPath throws ArgumentException with paramName zipPath. This is an entry-point argument guard before opening any file stream.","triggerScenarios":"Calling ExtractTo with a null or empty zipPath; a download step that produced no file and forwarded an empty path.","commonSituations":"An upstream marketplace download failed silently leaving no local archive; path resolution returned empty due to a missing config; a caller forgetting to pass the downloaded archive path.","solutions":["Ensure the zip download completed and produced a real file path before extracting.","Guard for null/empty zipPath at the call site and surface the upstream failure."],"exampleFix":"// before\nSafeZipExtractor.ExtractTo(downloadedPath, destDir); // downloadedPath may be \"\"\n\n// after\nif (string.IsNullOrEmpty(downloadedPath) || !File.Exists(downloadedPath))\n    throw new InvalidOperationException(\"Download produced no archive to extract.\");\nSafeZipExtractor.ExtractTo(downloadedPath, destDir);","handlingStrategy":"validation","validationCode":"if (string.IsNullOrWhiteSpace(zipPath) || !File.Exists(zipPath))\n    throw new InvalidOperationException(\"No archive file found to extract; check the download step.\");\nSafeZipExtractor.ExtractTo(zipPath, destDir, allowedExtensions);","typeGuard":"static bool IsValidArchivePath(string p) => !string.IsNullOrWhiteSpace(p) && File.Exists(p);","tryCatchPattern":"try { SafeZipExtractor.ExtractTo(zipPath, destDir, allowed); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"zipPath required\"))\n{\n    // The download produced no archive; re-run download before retrying extraction.\n    throw new InvalidOperationException(\"Download step produced no archive.\", ex);\n}","preventionTips":["Confirm the download completed and the archive file exists before extracting.","Guard for null/empty/missing path at the call site.","Surface upstream download failures clearly instead of forwarding an empty path."],"tags":["assetgen","zip","validation","argument"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}