{"record":{"id":"ef3bb811b48fd338","repo":"CoplayDev/unity-mcp","slug":"image-path-must-point-to-a-file-under-the-project","errorCode":null,"errorMessage":"image_path must point to a file under the project's Assets folder.","messagePattern":"image_path must point to a file under the project's Assets folder\\.","errorType":"validation","errorClass":"UnauthorizedAccessException","httpStatus":null,"severity":"error","filePath":"MCPForUnity/Editor/Services/AssetGen/Providers/LocalImage.cs","lineNumber":53,"sourceCode":"            string abs = AssetGenPaths.ToAbsolute(rel);\n            if (!File.Exists(abs)) { error = $\"Source image not found: {path}\"; return false; }\n            if (!SupportedExtensions.Contains(Path.GetExtension(abs)))\n            {\n                error = $\"Unsupported image type '{Path.GetExtension(abs)}'. Use .png, .jpg, .jpeg, .webp, or .gif.\";\n                return false;\n            }\n            absPath = abs;\n            return true;\n        }\n\n        /// <summary>\n        /// Read a local image and return a \"data:image/&lt;mime&gt;;base64,...\" URI. Throws\n        /// <see cref=\"NotSupportedException\"/> for an unsupported extension.\n        /// </summary>\n        public static string ToDataUri(string absPath)\n        {\n            if (!AssetGenPaths.TryGetAssetsRelativePath(absPath, out string rel))\n                throw new UnauthorizedAccessException(\"image_path must point to a file under the project's Assets folder.\");\n            absPath = AssetGenPaths.ToAbsolute(rel);\n            string mime = MimeFromExtension(Path.GetExtension(absPath));\n            byte[] bytes = File.ReadAllBytes(absPath);\n            return \"data:\" + mime + \";base64,\" + Convert.ToBase64String(bytes);\n        }\n\n        private static string MimeFromExtension(string ext)\n        {\n            switch ((ext ?? string.Empty).ToLowerInvariant())\n            {\n                case \".png\": return \"image/png\";\n                case \".jpg\":\n                case \".jpeg\": return \"image/jpeg\";\n                case \".webp\": return \"image/webp\";\n                case \".gif\": return \"image/gif\";\n                default:\n                    throw new NotSupportedException(\n                        $\"Unsupported image type '{ext}' for image input. Use .png, .jpg, .jpeg, .webp, or .gif.\");","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/MCPForUnity/Editor/Services/AssetGen/Providers/LocalImage.cs#L35-L71","documentation":"Thrown by LocalImage.ToDataUri when the supplied image path does not resolve inside the Unity project's Assets directory (AssetGenPaths.TryGetAssetsRelativePath returns false). It is a path-confinement guard: the library only inlines images that live under Assets, which prevents reading arbitrary files on disk and blocks traversal like 'Assets/../ProjectSettings'. The same message is produced non-throwingly by ResolveExisting, so hitting the throw means ToDataUri was called without that pre-check.","triggerScenarios":"Calling an asset-gen flow that inlines a local image (Meshy image-to-3D at MeshyAdapter.cs:55, OpenRouter image input at OpenRouterAdapter.cs:44) with an image_path that is absolute but outside the project, relative but not starting with 'Assets/', or a Packages/ / PackageCache path.","commonSituations":"Passing a path to Downloads/Desktop/a sibling project; a typo; passing a Packages folder path; passing an absolute Library/ path; copied a path from another OS with backslashes that still resolves outside Assets.","solutions":["Move or copy the image into the project's Assets/ folder and pass an 'Assets/...' relative path (or an absolute path under Assets).","If the image must stay external, host it at an http(s) URL and pass image_url instead of image_path.","Pre-validate with LocalImage.ResolveExisting before calling ToDataUri so the error surfaces as a user-facing message rather than an exception."],"exampleFix":"// before\nstring dataUri = LocalImage.ToDataUri(req.ImagePath); // throws UnauthorizedAccessException\n\n// after\nif (!LocalImage.ResolveExisting(req.ImagePath, out string abs, out string err))\n    return ErrorResponse(err);\nstring dataUri = LocalImage.ToDataUri(abs);","handlingStrategy":"validation","validationCode":"// Pre-check path confinement before inlining (handler-side, same assembly):\nif (!LocalImage.ResolveExisting(imagePath, out string abs, out string err))\n    return ErrorResponse(err);\n// abs is now a verified in-Assets file; safe to call ToDataUri(abs).","typeGuard":"// Confines an image path to the project Assets folder before use.\nstatic bool IsPathUnderAssets(string path)\n    => MCPForUnity.Editor.Helpers.AssetGenPaths.TryGetAssetsRelativePath(path, out _);","tryCatchPattern":"try { string uri = LocalImage.ToDataUri(absPath); }\ncatch (System.UnauthorizedAccessException ex)\n{ /* path escaped Assets — move file under Assets/ or use image_url */ }","preventionTips":["Always route local images through LocalImage.ResolveExisting before ToDataUri; it returns the same errors non-throwingly.","Keep input images under Assets/ and pass 'Assets/...' relative paths.","For external images, host them and pass image_url instead of image_path."],"tags":["path-validation","asset-gen","security","image-input","path-confinement"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}