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

  1. Treat the archive as corrupt or malicious and do not extract it; report it to the provider if it came from a marketplace.
  2. Re-package the archive locally with clean relative entry paths.
  3. 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

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


AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13). Data as JSON: /api/errors/6c347503dcafbb0a. Report an issue: GitHub.