{"record":{"id":"f70532455d652760","repo":"CoplayDev/unity-mcp","slug":"provider-returned-a-disallowed-file-type-ext","errorCode":null,"errorMessage":"provider returned a disallowed file type '.{ext}'","messagePattern":"provider returned a disallowed file type '\\.(.+?)'","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"MCPForUnity/Editor/Services/AssetGen/AssetGenJobManager.cs","lineNumber":484,"sourceCode":"\n        private static HashSet<string> AllowedExtensionsFor(string kind)\n        {\n            switch ((kind ?? string.Empty).ToLowerInvariant())\n            {\n                case \"audio\": return AudioAllowedExtensions;\n                case \"image\": return ImageAllowedExtensions;\n                case \"model\":\n                case \"marketplace\": return ModelAllowedExtensions;\n                default: return NoAllowedExtensions; // fail closed for unexpected kinds\n            }\n        }\n\n        private static string WriteFile(Runner r, byte[] bytes)\n        {\n            string chosen = !string.IsNullOrEmpty(r.OverrideExt) ? r.OverrideExt : r.Ext;\n            string ext = string.IsNullOrEmpty(chosen) ? \"bin\" : chosen.TrimStart('.').ToLowerInvariant();\n            if (!IsAllowedResultExtension(r.Job.Kind, ext))\n                throw new Exception($\"provider returned a disallowed file type '.{ext}'\");\n            string requestedRoot = !string.IsNullOrEmpty(r.OutputFolder) ? r.OutputFolder\n                                                                         : (AssetGenPrefs.OutputRoot + \"/\" + r.Subfolder);\n            if (!AssetGenPaths.TryGetAssetsFolder(requestedRoot, out string root))\n                root = AssetGenPrefs.DefaultOutputRoot + \"/\" + r.Subfolder;\n            string absRoot = AssetGenPaths.ToAbsolute(root);\n            Directory.CreateDirectory(absRoot);\n            string baseName = SanitizeName(r.Name);\n            string fileName = baseName + \".\" + ext;\n            string abs = Path.Combine(absRoot, fileName);\n            int n = 1;\n            while (File.Exists(abs)) { fileName = baseName + \"_\" + n++ + \".\" + ext; abs = Path.Combine(absRoot, fileName); }\n            File.WriteAllBytes(abs, bytes);\n            return (root.TrimEnd('/') + \"/\" + fileName).Replace('\\\\', '/');\n        }\n\n        private static string NameFrom(string explicitName, string prompt, string jobId)\n        {\n            if (!string.IsNullOrWhiteSpace(explicitName)) return explicitName;","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/MCPForUnity/Editor/Services/AssetGen/AssetGenJobManager.cs#L466-L502","documentation":"WriteFile checks the provider-returned file extension against a per-kind allowlist (image/model/marketplace), defaulting to NoAllowedExtensions (fail-closed) for any unknown kind. A disallowed extension throws a generic Exception. This is a security gate preventing unexpected or executable content from landing under Assets/.","triggerScenarios":"A provider returns a file type not in the kind's allowlist (e.g. a model job returning .exe or .zip when only model formats are allowed); an unknown job kind with no allowlist entry; OverrideExt or Ext on the Runner mis-set to a disallowed value.","commonSituations":"A provider API change returning a new format not yet allowlisted; a misconfigured ext override in tests; a new job kind registered without an allowlist entry, hitting the fail-closed default.","solutions":["Confirm the provider legitimately returns that type and, if safe, add it to the relevant AllowedExtensions list for the kind.","Verify OverrideExt/Ext on the Runner is set to an allowed extension for the job kind.","For any new job kind, register an explicit allowlist rather than relying on the fail-closed default in production."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before writing, confirm the resolved extension is allowlisted for the job kind.\nstring ext = (r.OverrideExt ?? r.Ext ?? \"bin\").TrimStart('.').ToLowerInvariant();\nif (!AssetGenJob.IsAllowedResultExtension(r.Job.Kind, ext))\n    throw new InvalidOperationException($\"Provider ext '.{ext}' is not allowed for kind '{r.Job.Kind}'.\");","typeGuard":null,"tryCatchPattern":"try { AssetGenJob.WriteFile(runner, bytes); }\ncatch (Exception ex) when (ex.Message.Contains(\"disallowed file type\"))\n{\n    // Either the provider misbehaved or the allowlist needs updating for a safe new format.\n    Log.Warn(ex.Message);\n    throw;\n}","preventionTips":["Register an explicit allowlist for every job kind rather than relying on fail-closed default.","Verify OverrideExt/Ext on the Runner matches an allowed extension.","Only add a new extension to the allowlist after confirming it is a safe, inert asset type."],"tags":["assetgen","security","file-type","validation","allowlist"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}