{"record":{"id":"6c347503dcafbb0a","repo":"CoplayDev/unity-mcp","slug":"unsafe-zip-entry-rejected-name","errorCode":null,"errorMessage":"Unsafe zip entry rejected: {name}","messagePattern":"Unsafe zip entry rejected: (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"MCPForUnity/Editor/Services/AssetGen/Import/SafeZipExtractor.cs","lineNumber":42,"sourceCode":"            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))\n                        throw new IOException($\"Unsafe zip entry rejected: {name}\");\n\n                    string target = Path.GetFullPath(Path.Combine(destDir, name));\n                    if (!target.StartsWith(prefix, StringComparison.Ordinal))\n                        throw new IOException($\"Unsafe zip entry escapes destination: {name}\");\n\n                    // A directory entry has an empty Name (FullName ends with a separator).\n                    if (string.IsNullOrEmpty(entry.Name))\n                    {\n                        Directory.CreateDirectory(target);\n                        continue;\n                    }\n\n                    // Allowlist gate: skip anything that isn't an inert asset type the caller permits.\n                    if (allowedExtensions != null && allowedExtensions.Count > 0\n                        && !allowedExtensions.Contains(Path.GetExtension(entry.Name).ToLowerInvariant()))\n                    {\n                        continue;\n                    }","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/MCPForUnity/Editor/Services/AssetGen/Import/SafeZipExtractor.cs#L24-L60","documentation":"SafeZipExtractor rejects any archive entry whose FullName contains '..' or is rooted (Path.IsPathRooted). These are classic zip-slip and absolute-path extraction attacks; the entry is refused with IOException before any file is written.","triggerScenarios":"A malicious or malformed archive containing entries like '../evil.dll', '..\\..\\x.cs', or an absolute path such as '/etc/x' or 'C:\\x'. The check runs on every entry's FullName.","commonSituations":"An untrusted marketplace archive (e.g. Sketchfab zip) with crafted entries; a hand-made test zip written with absolute paths; a packaging tool that emitted parent-directory references.","solutions":["Treat the archive as corrupt or malicious and do not extract it; report it to the provider if it came from a marketplace.","Re-package the archive locally with clean relative entry paths.","Never disable this check for untrusted archives; it is a deliberate security boundary."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-scan entries before extraction to detect traversal/rooted names early.\nusing (var a = ZipFile.OpenRead(zipPath))\n{\n    foreach (var e in a.Entries)\n        if (e.FullName.Contains(\"..\") || Path.IsPathRooted(e.FullName))\n            throw new InvalidOperationException($\"Archive contains unsafe entry: {e.FullName}\");\n}","typeGuard":"static bool IsSafeEntryName(string fullName)\n    => !string.IsNullOrEmpty(fullName) && !fullName.Contains(\"..\") && !Path.IsPathRooted(fullName);","tryCatchPattern":"try { SafeZipExtractor.ExtractTo(zipPath, destDir, allowed); }\ncatch (IOException ex) when (ex.Message.Contains(\"Unsafe zip entry rejected\"))\n{\n    // Treat the archive as untrusted/corrupt; do not attempt to sanitize and re-extract.\n    Log.Error($\"Refusing unsafe archive: {ex.Message}\");\n    throw;\n}","preventionTips":["Never extract untrusted archives with this check disabled.","Quarantine archives that trip traversal checks and report them to the provider.","Re-package local archives with clean relative entry paths."],"tags":["assetgen","zip","security","path-traversal"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}