{"record":{"id":"377544a8a9d98753","repo":"CoplayDev/unity-mcp","slug":"screenshot-folder-folderoverride-resolves-outs","errorCode":null,"errorMessage":"Screenshot folder '{folderOverride}' resolves outside the Unity project root ('{fullFolder}'). Use a project-relative path (e.g. 'Assets/Screenshots' or 'Captures').","messagePattern":"Screenshot folder '(.+?)' resolves outside the Unity project root \\('(.+?)'\\)\\. Use a project-relative path \\(e\\.g\\. 'Assets/Screenshots' or 'Captures'\\)\\.","errorType":"validation","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs","lineNumber":654,"sourceCode":"            string projectRoot = GetProjectRootPath().TrimEnd('/');\n            string requested = string.IsNullOrWhiteSpace(folderOverride) ? DefaultFolder : folderOverride.Trim();\n            requested = requested.Replace('\\\\', '/').TrimEnd('/');\n\n            string combined = Path.IsPathRooted(requested)\n                ? requested\n                : Path.Combine(projectRoot, requested);\n\n            string fullFolder = Path.GetFullPath(combined).Replace('\\\\', '/').TrimEnd('/');\n            string normalizedRoot = projectRoot;\n\n            // Reject paths that escape the project root (case-insensitive on Windows, exact elsewhere).\n            var rootComparison = Application.platform == RuntimePlatform.WindowsEditor\n                ? StringComparison.OrdinalIgnoreCase\n                : StringComparison.Ordinal;\n            if (!fullFolder.Equals(normalizedRoot, rootComparison) &&\n                !fullFolder.StartsWith(normalizedRoot + \"/\", rootComparison))\n            {\n                throw new InvalidOperationException(\n                    $\"Screenshot folder '{folderOverride}' resolves outside the Unity project root ('{fullFolder}'). \" +\n                    $\"Use a project-relative path (e.g. 'Assets/Screenshots' or 'Captures').\");\n            }\n\n            return fullFolder;\n        }\n\n        /// <summary>\n        /// Converts an absolute filesystem path inside the project to a project-relative path\n        /// (forward slashes, no leading separator). Returns the input unchanged when it does\n        /// not live under the project root.\n        /// </summary>\n        public static string ToProjectRelativePath(string normalizedFullPath)\n        {\n            if (string.IsNullOrEmpty(normalizedFullPath)) return normalizedFullPath;\n            string projectRoot = GetProjectRootPath();\n            string normalized = normalizedFullPath.Replace('\\\\', '/');\n            if (normalized.StartsWith(projectRoot, StringComparison.OrdinalIgnoreCase))","sourceCodeStart":636,"sourceCodeEnd":672,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs#L636-L672","documentation":"Thrown by ResolveFolderAbsolute when the resolved screenshot folder path escapes the Unity project root. This is a deliberate security guard against path traversal. The method computes the full path and checks it starts with the project root; anything outside (absolute paths elsewhere, or relative paths using '..' that traverse up) is rejected.","triggerScenarios":"Passing a folderOverride with '../' sequences that resolve outside the project, an absolute path to a different directory (e.g. '/tmp' or 'C:\\Users'), or a symlink that points outside the project root.","commonSituations":"AI passes an absolute temp path thinking it's convenient; user wants screenshots in a shared external folder; relative path with too many '..' levels; path normalization differences between platforms (symlinks, junction points).","solutions":["Use a project-relative path like 'Assets/Screenshots' or 'Captures'.","If you need screenshots outside the project, capture to a project folder then copy/move the file externally afterward.","Avoid absolute paths and '..' traversal in folderOverride.","Ensure the folder name does not contain backslashes on Windows that confuse path resolution (use forward slashes)."],"exampleFix":"// before\nparams = {\"folderOverride\": \"/tmp/screenshots\"}\n// after\nparams = {\"folderOverride\": \"Assets/Screenshots\"}","handlingStrategy":"validation","validationCode":"import os\n\ndef validate_folder_in_project(folder: str, project_root: str) -> bool:\n    \"\"\"Returns True if the resolved folder is within the project root.\"\"\"\n    if os.path.isabs(folder):\n        combined = folder\n    else:\n        combined = os.path.join(project_root, folder)\n    full = os.path.normpath(combined)\n    root = os.path.normpath(project_root)\n    return full == root or full.startswith(root + os.sep)","typeGuard":null,"tryCatchPattern":"try\n{\n    string folder = ResolveFolderAbsolute(folderOverride);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"resolves outside the Unity project root\"))\n{\n    return new ErrorResponse(\"Use a project-relative path like 'Assets/Screenshots'.\");\n}","preventionTips":["Always use project-relative paths for screenshot folders.","Never use absolute paths or '..' traversal in folderOverride.","Use forward slashes in paths for cross-platform consistency.","If screenshots need to go elsewhere, capture in-project then move the file."],"tags":["screenshot","path-traversal","security","validation","filesystem"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}