{"record":{"id":"65fce6e12e3c8446","repo":"google-gemini/gemini-cli","slug":"access-to-forbidden-path-is-denied-path","errorCode":null,"errorMessage":"Access to forbidden path is denied: {path}","messagePattern":"Access to forbidden path is denied: (.+?)","errorType":"exception","errorClass":"UnauthorizedAccessException","httpStatus":null,"severity":"error","filePath":"packages/core/src/sandbox/windows/GeminiSandbox.cs","lineNumber":521,"sourceCode":"            RevertToSelf();\n        }\n    }\n\n    private static string GetNormalizedPath(string path) {\n        string fullPath = Path.GetFullPath(path);\n        StringBuilder longPath = new StringBuilder(1024);\n        uint result = GetLongPathName(fullPath, longPath, (uint)longPath.Capacity);\n        if (result > 0 && result < longPath.Capacity) {\n            return longPath.ToString();\n        }\n        return fullPath;\n    }\n\n    private static void CheckForbidden(string path, HashSet<string> forbiddenPaths) {\n        string fullPath = GetNormalizedPath(path);\n        foreach (string forbidden in forbiddenPaths) {\n            if (fullPath.Equals(forbidden, StringComparison.OrdinalIgnoreCase) || fullPath.StartsWith(forbidden + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) {\n                throw new UnauthorizedAccessException(\"Access to forbidden path is denied: \" + path);\n            }\n        }\n    }\n\n    private static string QuoteArgument(string arg) {\n        if (string.IsNullOrEmpty(arg)) return \"\\\"\\\"\";\n\n        bool needsQuotes = false;\n        foreach (char c in arg) {\n            if (char.IsWhiteSpace(c) || c == '\\\"') {\n                needsQuotes = true;\n                break;\n            }\n        }\n\n        if (!needsQuotes) return arg;\n\n        StringBuilder sb = new StringBuilder();","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/packages/core/src/sandbox/windows/GeminiSandbox.cs#L503-L539","documentation":"This UnauthorizedAccessException is thrown by GeminiSandbox.CheckForbidden() when a sandboxed internal file command (__read or __write) targets a path that matches or sits underneath an entry in the forbiddenPaths set. Forbidden paths are loaded from a forbidden-manifest file and normalized to long 8.3 form before comparison. It is a security guard preventing the low-integrity sandboxed process from touching protected locations.","triggerScenarios":"The sandbox receives an internal __read or __write command (args at Main:310-355) whose target path, after Path.GetFullPath + GetLongPathName normalization, equals a forbidden entry case-insensitively or starts with '<forbidden>\\'. For example, forbidden list contains 'C:\\Windows\\System32' and the command targets 'C:\\Windows\\System32\\drivers\\etc\\hosts'.","commonSituations":"An agent or tool inside the sandbox attempts to read a credential file, system directory, or the repo's .git directory that was explicitly forbidden via the --forbidden-manifest. A path-traversal or relative path ('..\\..\\forbidden\\file') resolves into a forbidden subtree after normalization. The manifest was over-broad, forbidding a parent directory that the workload legitimately needs. A symlink or junction resolves under a forbidden path.","solutions":["Identify which forbidden-manifest entry the target path matches and remove/adjust that entry if access is legitimately required.","Rewrite the sandboxed command to target a path outside the forbidden subtree (e.g., a workspace scratch dir).","If using __read/__write, verify the path is within the allowed working directory and not under a system or credential path.","Audit the --forbidden-manifest passed to GeminiSandbox.exe to ensure entries are scoped narrowly (leaf dirs, not over-broad parents).","Normalize the target path the same way (GetFullPath + GetLongPathName) before issuing the command to predict the match."],"exampleFix":"// before: forbidden-manifest forbids C:\\Repo\\.git, agent tries to read it\nGeminiSandbox.exe __read C:\\Repo\\.git\\config\n// after: read config from an allowed copy outside the forbidden path\nGeminiSandbox.exe __read C:\\Workspace\\.gitconfig-copy","handlingStrategy":"validation","validationCode":"using System.IO;\nusing System.Text;\nusing System.Runtime.InteropServices;\n\n// Replicate GeminiSandbox normalization to pre-check a path before calling __read/__write:\nstatic bool IsForbidden(string path, string[] forbidden) {\n    string full = Path.GetFullPath(path);\n    var sb = new StringBuilder(1024);\n    // P/Invoke GetLongPathName as in GeminiSandbox.cs:507-515\n    foreach (string f in forbidden) {\n        if (full.Equals(f, System.StringComparison.OrdinalIgnoreCase) ||\n            full.StartsWith(f + Path.DirectorySeparatorChar, System.StringComparison.OrdinalIgnoreCase))\n            return true;\n    }\n    return false;\n}","typeGuard":null,"tryCatchPattern":"try {\n    // __read or __write against 'path'\n} catch (UnauthorizedAccessException ex) when (ex.Message.Contains(\"forbidden path\")) {\n    Console.Error.WriteLine($\"Blocked by sandbox policy: {path}. Use an allowed workspace path.\");\n    // route the operation to an allowed directory instead\n}","preventionTips":["Scope --forbidden-manifest entries to specific sensitive directories, not overly broad parents.","Pre-check target paths against the forbidden set using the same normalization before issuing __read/__write.","Keep sandboxed file operations inside a dedicated workspace scratch directory.","Beware symlinks/junctions that resolve under a forbidden subtree; resolve them before checking."],"tags":["security","sandbox","windows","filesystem","access-control","csharp"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}