CoplayDev/unity-mcp · critical · IOException
Unsafe zip entry rejected: {name}
Error message
Unsafe zip entry rejected: {name} What it means
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.
Source
Thrown at MCPForUnity/Editor/Services/AssetGen/Import/SafeZipExtractor.cs:42
if (string.IsNullOrEmpty(destDir)) throw new ArgumentException("destDir required", nameof(destDir));
Directory.CreateDirectory(destDir);
string destFull = Path.GetFullPath(destDir);
string prefix = destFull.EndsWith(Path.DirectorySeparatorChar.ToString())
? destFull
: destFull + Path.DirectorySeparatorChar;
using (FileStream fs = File.OpenRead(zipPath))
using (var archive = new ZipArchive(fs, ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
string name = entry.FullName;
if (string.IsNullOrEmpty(name)) continue;
// Reject traversal / absolute paths up front.
if (name.Contains("..") || Path.IsPathRooted(name))
throw new IOException($"Unsafe zip entry rejected: {name}");
string target = Path.GetFullPath(Path.Combine(destDir, name));
if (!target.StartsWith(prefix, StringComparison.Ordinal))
throw new IOException($"Unsafe zip entry escapes destination: {name}");
// A directory entry has an empty Name (FullName ends with a separator).
if (string.IsNullOrEmpty(entry.Name))
{
Directory.CreateDirectory(target);
continue;
}
// Allowlist gate: skip anything that isn't an inert asset type the caller permits.
if (allowedExtensions != null && allowedExtensions.Count > 0
&& !allowedExtensions.Contains(Path.GetExtension(entry.Name).ToLowerInvariant()))
{
continue;
}View on GitHub (pinned to c21bf496bc)
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.
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-scan entries before extraction to detect traversal/rooted names early.
using (var a = ZipFile.OpenRead(zipPath))
{
foreach (var e in a.Entries)
if (e.FullName.Contains("..") || Path.IsPathRooted(e.FullName))
throw new InvalidOperationException($"Archive contains unsafe entry: {e.FullName}");
} Type guard
static bool IsSafeEntryName(string fullName)
=> !string.IsNullOrEmpty(fullName) && !fullName.Contains("..") && !Path.IsPathRooted(fullName); Try / catch
try { SafeZipExtractor.ExtractTo(zipPath, destDir, allowed); }
catch (IOException ex) when (ex.Message.Contains("Unsafe zip entry rejected"))
{
// Treat the archive as untrusted/corrupt; do not attempt to sanitize and re-extract.
Log.Error($"Refusing unsafe archive: {ex.Message}");
throw;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unsafe zip entry escapes destination: {name}
- provider returned a disallowed file type '.{ext}'
- zipPath required
- Screenshot folder '{folderOverride}' resolves outside the Un
- CredWrite failed (Win32 {GetLastWin32Error})
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/6c347503dcafbb0a.
Report an issue: GitHub.