fix(core): resolve Plan Mode deadlock during plan file creation due to sandbox restrictions#24047
fix(core): resolve Plan Mode deadlock during plan file creation due to sandbox restrictions#24047DavidAPierce merged 27 commits intomainfrom
Conversation
This PR resolves a deadlock in Plan Mode where the agent could not create its plan file if the parent directory (e.g., .gemini/plans) did not exist on the host machine. This was caused by sandbox restrictions preventing both directory creation and binding of non-existent paths. Key changes: - updated EnterPlanModeTool to pre-create the plans directory on the host. - Implemented virtual command translation (__read/__write) for Linux and macOS sandboxes. - Enhanced SandboxedFileSystemService to recognize platform-specific ENOENT error strings (Linux/Windows). - Updated LinuxSandboxManager to allow binding the parent directory of explicitly allowed but non-existent paths. - Ensured operation-specific policies are passed to the sandbox during file operations. Fixes #23958
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a deadlock in Plan Mode where the agent was unable to create plan files due to sandbox restrictions when the target directory did not exist. By implementing proactive host-side directory creation and translating virtual file system commands into native shell operations, the changes ensure that the sandbox environment is correctly prepared for file operations. Additionally, the update improves error propagation to handle missing file scenarios gracefully across different platforms. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces virtual commands (__read and __write) to the Linux and macOS sandbox managers to standardize file system access and updates the SandboxedFileSystemService to utilize these commands with appropriate policies. It also ensures that the plans directory exists on the host before entering plan mode and improves error handling by mapping file-not-found errors to the ENOENT code. The review feedback identifies critical security vulnerabilities regarding path traversal in the SandboxedFileSystemService due to a lack of path sanitization. Additionally, the reviewer pointed out a logic error in the Linux sandbox manager where parent directories for non-existent files must be mounted as read-write to allow for file creation, necessitating changes to both the implementation and the associated tests.
🧠 Model Steering GuidanceThis PR modifies files that affect the model's behavior (prompts, tools, or instructions).
This is an automated guidance message triggered by steering logic signatures. |
|
Size Change: +3.17 kB (+0.01%) Total Size: 26.5 MB
ℹ️ View Unchanged
|
- Ensure paths passed to SandboxedFileSystemService are sanitized and validated to be inside the workspace. - Bind missing parent directories as writable when the command is '__write' in LinuxSandboxManager. - Add a behavioral evaluation for entering plan mode and creating a plan file from scratch.
…across platforms - Added support for virtual commands (__read, __write) in WindowsSandboxManager using cmd.exe and PowerShell. - Integrated includeDirectories into Windows, Linux, and macOS sandbox managers to grant read access. - Improved directory pre-creation logic in Windows and Linux managers for sandboxed writes. - Fixed initialization order in Config constructor to prevent TypeScript errors.
# Conflicts: # packages/core/src/sandbox/windows/WindowsSandboxManager.ts
… into plan_mode_loop_fix
Code ReviewScope: Pull Request #24047 The PR effectively solves the Plan Mode deadlock by creating the However, there are critical issues with how Windows handles the virtual command arguments that could cause syntax errors or unintended command execution. Additionally, an unrelated change to URL validation has been included. Metadata ReviewThe PR Title perfectly aligns with the Conventional Commits format ( Concerns (Action Required)
Nits (Minor Suggestions)
|
| const resolved = await tryRealpath(allowedPath); | ||
| if (!fs.existsSync(resolved)) { | ||
| // If the path doesn't exist, we still want to allow access to its parent | ||
| // if it's explicitly allowed, to enable creating it. |
There was a problem hiding this comment.
When a path does not exist, the manager falls back to granting "Low Mandatory Level" to its parent directory using icacls. This modifies the Windows DACL/MIC persistently, making the entire parent directory writable to all Low Integrity processes on the system going forward. If this parent directory happens to be the workspace root, the entire workspace permanently becomes writable in the sandbox.
I think instead of granting low integrity if the path does not exist we should throw an error or warning.
| let args = req.args; | ||
|
|
||
| // Translate virtual commands for sandboxed file system access | ||
| if (command === '__read') { |
There was a problem hiding this comment.
Passing arguments as trailing array elements to PowerShell.exe -Command does not behave like typical POSIX shell argument passing. PowerShell concatenates all arguments following -Command into a single string before parsing and executing them.
This introduces two major issues:
1. Space Splitting: If targetPath contains spaces (e.g., my file.txt), PowerShell evaluates & { ... } my file.txt and splits the path into two arguments (my and file.txt), causing the command to fail because $args[0] only receives my.
2. Command Injection: A malicious path like foo"; Write-Host "hacked (as seen in the new test case) will be concatenated directly into the command string and executed as arbitrary PowerShell code. The test should safely handle special characters in __write path asserts this vulnerable behavior under the false assumption that passing it as an array to spawn prevents PowerShell from interpolating it.
The safest way to pass paths to a PowerShell script block without injection or splitting issues is to pass the target path via an environment variable, or use -EncodedCommand.
Using an environment variable is very robust and avoids escaping logic:
if (command === '__write') {
const targetPath = args[0] || '';
command = 'PowerShell.exe';
args = [
'-NoProfile',
'-NonInteractive',
'-Command',
'& { $Input | Out-File -FilePath $env:GEMINI_TARGET_PATH -Encoding utf8 }',
];
// You will then need to merge `GEMINI_TARGET_PATH: targetPath` into the returned `env` object at the end of `prepareCommand`.
}
There was a problem hiding this comment.
Updated WindowsSandboxManager.ts and its test file to safely pass arguments using environment variables, successfully addressing the command injection and space-splitting vulnerabilities on Windows.
Here is a summary of the changes:
Security Fix in WindowsSandboxManager.ts
- Environment Variable Injection: Instead of passing $args[0] into the PowerShell script block (which was vulnerable to space-splitting and command injection because PowerShell evaluates the array values as part of the
executed string), the manager now sets GEMINI_TARGET_PATH on the final env object before launching the sandbox helper. - Safe Evaluation: The __read and __write command translations now reference the path safely within the script block using $env:GEMINI_TARGET_PATH.
- For __read: & { Get-Content -LiteralPath $env:GEMINI_TARGET_PATH -Raw }
- For __write: & { $Input | Out-File -FilePath $env:GEMINI_TARGET_PATH -Encoding utf8 }
Test Coverage Updates in WindowsSandboxManager.test.ts
- Updated Expectations: Modified the __read and __write translation tests to look for the updated & { ... $env:GEMINI_TARGET_PATH ... } syntax.
- Environment Verification: Added assertions to verify that result.env['GEMINI_TARGET_PATH'] is correctly assigned the target file path (including cases with spaces and malicious strings like foo"; echo bar; ".txt),
confirming that injection via command interpolation is no longer possible.
| `Sandbox Error: read_file failed for '${filePath}'. Exit code ${code}. ${error ? 'Details: ' + error : ''}`, | ||
| ); | ||
| if (isEnoent) { | ||
| // @ts-expect-error - Adding code property to Error object |
There was a problem hiding this comment.
Instead of suppressing the error, you can use Object.assign or cast it
There was a problem hiding this comment.
Good suggestion. Done.
|
One thing I'm curious about is if we turn off tool sandboxing mid-session, what happens? Are the sandbox restrictions still lifted? |
No Require to restart. |
…o sandbox restrictions (google-gemini#24047)
…o sandbox restrictions (google-gemini#24047)
…o sandbox restrictions (google-gemini#24047)
Summary
This PR resolves a deadlock in Plan Mode where the agent could not create its plan file if the parent directory (e.g.,
.gemini/plans) did not exist on the host machine. This was caused by sandbox restrictions preventing both directory creation and the binding of non-existent paths.Details
The fix implements a proactive, path-specific permission strategy and host-side initialization:
EnterPlanModeTool: Now pre-creates the plans directory on the host before entering the sandbox.__readand__writein Linux (Bubblewrap) and macOS (Seatbelt) sandbox managers. This allowsSandboxedFileSystemServiceto perform these operations using standard system tools (cat,sh) even whenrun_shell_commandis blocked.LinuxSandboxManagerto allow binding the parent directory of an explicitly allowed but non-existent path.SandboxedFileSystemServiceto correctly identify and propagate platform-specificENOENTerror strings (e.g., Windows "Could not find a part of the path"), allowingwrite_fileto handle "new file" scenarios correctly.Related Issues
Fixes #23958
How to Validate
npm test -w @google/gemini-cli-core -- src/services/sandboxedFileSystemService.test.ts src/sandbox/linux/LinuxSandboxManager.test.ts src/sandbox/macos/MacOsSandboxManager.test.ts src/tools/enter-plan-mode.test.ts.gemini/plansdirectory does not exist.Pre-Merge Checklist