Skip to content

feat(core): implement native Windows sandboxing#21807

Merged
mattKorwel merged 15 commits intomainfrom
mk-windows-sandboxing
Mar 19, 2026
Merged

feat(core): implement native Windows sandboxing#21807
mattKorwel merged 15 commits intomainfrom
mk-windows-sandboxing

Conversation

@mattKorwel
Copy link
Copy Markdown
Collaborator

Summary

Implement native Windows sandboxing using Restricted Tokens and Mandatory Integrity Control (MIC).

Details

This PR adds a complete native Windows sandboxing implementation for Gemini CLI, providing robust isolation for both external shell tools and native Node.js tools. It avoids the need for Docker or Podman on Windows while providing stronger security than simple filesystem checks.

Key Features:

  • Advanced Restricted Tokens: Uses a native C# worker (GeminiSandbox.exe) to create a Windows Restricted Token with Double SID Evaluation (S-1-5-12). This model ensures the process has zero-trust access by default, blocking read access to any file not explicitly whitelisted.
  • Network Isolation: Strips the Network SID (S-1-5-2) from the token to block outbound traffic by default. This can be toggled via networkAccess in the configuration.
  • Filesystem Isolation (Read/Write):
    • Write protection is enforced via Mandatory Integrity Control (MIC) at Low Mandatory Level.
    • Read protection is enforced via the Restricted Token.
    • The WindowsSandboxManager uses icacls to grant "Low" integrity access to the CWD and any user-defined allowedPaths.
  • Native Tool Sandboxing: Introduces SandboxedFileSystemService. Native Gemini tools like read_file and write_file now delegate their work to the restricted worker, ensuring they operate within the same security boundary as shell tools.
  • Configuration Support: Added networkAccess and allowedPaths to SandboxConfig.

The C# helper source is included and automatically compiled on the first run using the standard Windows csc.exe compiler.

Related Issues

Partially addresses sandboxing requirements on Windows.

How to Validate

  1. On Windows, enable sandboxing in .gemini/config.yaml:
sandbox:
  enabled: true
  networkAccess: false
  1. Run a command that tries to write outside the CWD:
    @shell echo test > C:\Windows\test.txt -> Should fail with Access Denied.
  2. Run a command that tries to read a private file:
    @shell type %USERPROFILE%\.ssh\id_rsa -> Should fail with Access Denied.
  3. Run a command that tries to access the network:
    @shell curl http://google.com -> Should fail.
  4. Use native tools like read_file on a file in the workspace -> Should succeed.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
      • Podman
      • Native
    • Linux
      • npm run
      • npx
      • Docker
      • Podman

@google-cla
Copy link
Copy Markdown

google-cla bot commented Mar 10, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@mattKorwel
Copy link
Copy Markdown
Collaborator Author

Note on native Windows Sandbox compilation

I've implemented the Windows native sandbox using a C# helper (GeminiSandbox.cs) that leverages Restricted Tokens for robust isolation.

Architectural Choice: Local Compilation vs. Distributed Binary
Rather than distributing a pre-compiled .exe (which is often flagged by AV and is a security 'black box'), this PR implements local compilation on the user's machine:

  1. The helper is compiled during the prepare (npm install) phase.
  2. It uses csc.exe (the C# compiler), which is part of the .NET Framework and built into every modern Windows installation.
  3. If the binary is missing or
    pm install was skipped, WindowsSandboxManager will attempt to compile it on the first run.

This approach ensures the sandbox is auditable, targets the host architecture (x64/ARM64), and avoids the common issues with NPM-distributed Windows binaries.

@mattKorwel
Copy link
Copy Markdown
Collaborator Author

Windows Native Sandbox: Progressive Elevation & AI Awareness

I've updated the implementation to include a Progressive Elevation strategy to handle the limitations of native Windows security tokens:

  1. Strict Mode (Default):

    • Uses a Restricted SID (Everyone) for zero-trust isolation.
    • Forcibly blocks all outbound network traffic and unauthorized file reads.
    • Shell Fallback: Since this strict mode blocks some system DLL loads for complex programs, the CLI now automatically falls back to using cmd.exe for tool calls instead of powershell.exe.
  2. Elevated Mode (Opt-in via 'sandboxNetworkAccess: true'):

    • Drops the Restricted SID to allow network access and full powershell.exe compatibility.
    • Maintains Privilege Stripping (removing SeDebugPrivilege, etc.) and Job Objects for resource management.
  3. AI Error Awareness:

    • The CLI now detects specific Windows OS error codes (like

@mattKorwel
Copy link
Copy Markdown
Collaborator Author

Clarification on Sandbox Boundaries: Native vs. Docker

Based on further testing, I'm documenting the explicit security boundaries of the \windows-native\ implementation to help users choose the right provider:

The 'Level 3' Limitation:
It is exceptionally difficult to use native Win32 APIs (without requiring Administrator rights or kernel drivers) to create a sandbox that simultaneously allows Network/PowerShell execution while strictly blocking global file reads.

  • Using a strict Restricted SID blocks network RPCs and crashes PowerShell (DLL load errors).
  • Dropping the Restricted SID fixes PowerShell/Network but restores standard user read access (meaning the sandbox can read files like ~/.ssh/id_rsa\ if explicitly instructed to).

Summary of Sandbox Providers for Windows:

  1. \windows-native\ (Strict Mode): No Network, No Global Reads, \cmd.exe\ only. Best for pure local isolation.
  2. \windows-native\ (Elevated Mode): Network Allowed, Global Reads Allowed, \powershell.exe\ allowed. Prevents privilege escalation and guarantees process teardown via Job Objects. Best for general developer tasks without the overhead of Docker.
  3. **\docker\ / \podman**: The officially recommended path for users requiring strict 'Level 3' isolation (Network Allowed + Local Reads Only). Containers use kernel silos to provide a layered filesystem, cleanly solving the DLL load issues that plague native user-space tokens.

@mattKorwel mattKorwel force-pushed the mk-windows-sandboxing branch from 13877f6 to 7814427 Compare March 13, 2026 17:17
@mattKorwel mattKorwel changed the base branch from galzahavi/add/sandboxed-tool to main March 13, 2026 18:07
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 13, 2026

Size Change: +10.2 kB (+0.04%)

Total Size: 26.3 MB

Filename Size Change
./bundle/chunk-PQ4GD4LP.js 0 B -3.63 MB (removed) 🏆
./bundle/chunk-V5FOZC6K.js 0 B -13.5 MB (removed) 🏆
./bundle/core-572B2XHP.js 0 B -41.6 kB (removed) 🏆
./bundle/devtoolsService-RJRDUYRS.js 0 B -27.7 kB (removed) 🏆
./bundle/interactiveCli-S7DAPG2D.js 0 B -1.61 MB (removed) 🏆
./bundle/oauth2-provider-DPII3W2R.js 0 B -9.19 kB (removed) 🏆
./bundle/chunk-KYY65T6V.js 13.5 MB +13.5 MB (new file) 🆕
./bundle/chunk-PALUCL5M.js 3.63 MB +3.63 MB (new file) 🆕
./bundle/core-CLZKQLG7.js 41.7 kB +41.7 kB (new file) 🆕
./bundle/devtoolsService-LQR6KYLP.js 27.7 kB +27.7 kB (new file) 🆕
./bundle/interactiveCli-ALUZCA5N.js 1.61 MB +1.61 MB (new file) 🆕
./bundle/oauth2-provider-MX75QUCM.js 9.19 kB +9.19 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
./bundle/chunk-34MYV7JD.js 2.45 kB 0 B
./bundle/chunk-37ZTTFQF.js 966 kB 0 B
./bundle/chunk-5AUYMPVF.js 858 B 0 B
./bundle/chunk-664ZODQF.js 124 kB 0 B
./bundle/chunk-DAHVX5MI.js 206 kB 0 B
./bundle/chunk-IUUIT4SU.js 56.5 kB 0 B
./bundle/chunk-RJTRUG2J.js 39.8 kB 0 B
./bundle/chunk-TCYGHW2X.js 1.95 MB 0 B
./bundle/devtools-36NN55EP.js 696 kB 0 B
./bundle/dist-T73EYRDX.js 356 B 0 B
./bundle/gemini.js 698 kB +808 B (+0.12%)
./bundle/getMachineId-bsd-TXG52NKR.js 1.55 kB 0 B
./bundle/getMachineId-darwin-7OE4DDZ6.js 1.55 kB 0 B
./bundle/getMachineId-linux-SHIFKOOX.js 1.34 kB 0 B
./bundle/getMachineId-unsupported-5U5DOEYY.js 1.06 kB 0 B
./bundle/getMachineId-win-6KLLGOI4.js 1.72 kB 0 B
./bundle/memoryDiscovery-C47CWCMG.js 922 B 0 B
./bundle/multipart-parser-KPBZEGQU.js 11.7 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/client/main.js 221 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js 227 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/index.js 11.5 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/types.js 132 B 0 B
./bundle/sandbox-macos-permissive-open.sb 890 B 0 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB 0 B
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB 0 B
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB 0 B
./bundle/sandbox-macos-strict-open.sb 4.82 kB 0 B
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB 0 B
./bundle/src-QVCVGIUX.js 47 kB 0 B
./bundle/tree-sitter-7U6MW5PS.js 274 kB 0 B
./bundle/tree-sitter-bash-34ZGLXVX.js 1.84 MB 0 B
./bundle/undici-4X2YZID5.js 360 B 0 B

compressed-size-action

@mattKorwel mattKorwel marked this pull request as ready for review March 13, 2026 19:38
@mattKorwel mattKorwel requested a review from a team as a code owner March 13, 2026 19:38
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 delivers a comprehensive native Windows sandboxing solution for the Gemini CLI. It significantly enhances the security posture by providing robust isolation for both external shell commands and internal Node.js file operations, thereby removing the dependency on containerization tools like Docker or Podman for secure execution on Windows. The implementation offers fine-grained control over network and file system access, ensuring that operations are confined to explicitly permitted resources.

Highlights

  • Native Windows Sandboxing: Implemented native Windows sandboxing using Restricted Tokens and Mandatory Integrity Control (MIC) to provide robust isolation for external shell tools and native Node.js tools, eliminating the need for Docker or Podman on Windows.
  • Advanced Restricted Tokens: Utilized a native C# worker (GeminiSandbox.exe) to create a Windows Restricted Token with Double SID Evaluation (S-1-5-12), ensuring zero-trust access by default and blocking read access to non-whitelisted files.
  • Network and Filesystem Isolation: Stripped the Network SID (S-1-5-2) for network isolation (configurable via networkAccess), enforced write protection via MIC at Low Mandatory Level, and read protection via the Restricted Token. icacls is used to grant 'Low' integrity access to the current working directory and user-defined allowedPaths.
  • Native Tool Sandboxing: Introduced SandboxedFileSystemService to delegate native Gemini tools like read_file and write_file to the restricted worker, ensuring they operate within the same security boundary as shell tools.
  • Configuration and Compilation: Added networkAccess and allowedPaths to SandboxConfig. The C# helper source is included and automatically compiled on the first run using the standard Windows csc.exe compiler.
Changelog
  • eslint.config.js
    • Extended ESLint configuration to include scripts located within packages.
  • packages/cli/src/config/sandboxConfig.test.ts
    • Updated sandbox configuration tests to include new allowedPaths and networkAccess properties with default values.
  • packages/core/scripts/compile-windows-sandbox.js
    • Added a new script responsible for compiling the native Windows sandbox helper (GeminiSandbox.exe) from its C# source.
  • packages/core/src/config/config.ts
    • Imported necessary sandbox-related services and OS utilities.
    • Modified the SandboxConfig interface to make allowedPaths and networkAccess mandatory and added windows-native as a valid sandbox command.
    • Updated ConfigSchema to reflect the changes in SandboxConfig and removed the requirement for a sandbox command when sandboxing is enabled.
    • Introduced a private _sandboxManager property and initialized it with either WindowsSandboxManager or NoopSandboxManager based on the platform and sandbox configuration.
    • Conditionally instantiated fileSystemService as SandboxedFileSystemService if sandboxing is enabled.
    • Integrated sandboxManager and sandboxConfig into shellExecutionConfig.
    • Added a getter for sandboxManager.
  • packages/core/src/services/sandboxManager.ts
    • Extended the SandboxRequest interface to include allowedPaths and networkAccess in its configuration.
  • packages/core/src/services/sandboxedFileSystemService.test.ts
    • Added unit tests for the SandboxedFileSystemService, verifying its ability to perform read and write operations through a mock sandbox manager.
  • packages/core/src/services/sandboxedFileSystemService.ts
    • Implemented SandboxedFileSystemService, which acts as a proxy for file system operations, delegating them to the configured SandboxManager.
  • packages/core/src/services/scripts/GeminiSandbox.cs
    • Added the complete C# source code for GeminiSandbox.exe, which handles Windows API calls for creating restricted tokens, setting integrity levels, and executing commands within the sandbox.
  • packages/core/src/services/shellExecutionService.ts
    • Imported SandboxManager and SandboxConfig types.
    • Added sandboxManager and sandboxConfig properties to ShellExecutionConfig.
    • Modified executeShellCommand to utilize the provided sandboxManager or a NoopSandboxManager.
    • Introduced logic to detect and enforce a 'strict sandbox' mode on Windows, which forces the use of cmd.exe as the shell.
    • Updated internal shell execution methods (_executeShellCommand, _executePtyShellCommand) to adapt the shell configuration when in strict sandbox mode.
  • packages/core/src/services/windowsSandboxManager.test.ts
    • Added unit tests for WindowsSandboxManager, covering command preparation, network access configuration, and environment variable sanitization.
  • packages/core/src/services/windowsSandboxManager.ts
    • Implemented WindowsSandboxManager, a platform-specific sandbox manager for Windows that leverages restricted tokens, job objects, and icacls for access control.
    • Included logic to compile the GeminiSandbox.exe C# helper if it's not already present.
    • Provided functionality to sanitize environment variables and grant low integrity access to specified paths.
Activity
  • Added/updated tests to ensure the new sandboxing features function as expected.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@mattKorwel mattKorwel self-assigned this Mar 13, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces native Windows sandboxing capabilities by adding a C# helper (GeminiSandbox.exe) for restricted token sandboxing, along with a script to compile it. The changes integrate WindowsSandboxManager and SandboxedFileSystemService to handle sandboxed shell execution and file system operations, updating configuration schemas to support a windows-native sandbox command. Review comments highlight a critical security vulnerability where the SandboxedFileSystemService might not be activated consistently with the WindowsSandboxManager, potentially bypassing file system sandboxing. Another high-severity security concern is raised regarding the grantLowIntegrityAccess method, which degrades system security by making user workspace directories writable by any low-integrity process. Additionally, memory leaks were identified in the GeminiSandbox.cs Main method due to unmanaged SID and struct pointers not being freed.

Comment on lines +142 to +305
static int Main(string[] args) {
if (args.Length < 3) {
Console.WriteLine("Usage: GeminiSandbox.exe <network:0|1> <cwd> <command> [args...]");
Console.WriteLine("Internal commands: __read <path>, __write <path>");
return 1;
}

bool networkAccess = args[0] == "1";
string cwd = args[1];
string command = args[2];

// 1. Setup Token
IntPtr hCurrentProcess = GetCurrentProcess();
IntPtr hToken;
if (!OpenProcessToken(hCurrentProcess, TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_DEFAULT, out hToken)) {
Console.Error.WriteLine("Failed to open process token");
return 1;
}

IntPtr hRestrictedToken;
IntPtr pSidsToDisable = IntPtr.Zero;
uint sidCount = 0;

IntPtr pSidsToRestrict = IntPtr.Zero;
uint restrictCount = 0;

// "networkAccess == false" implies Strict Sandbox Level 1.
// In Strict mode, we strip the Network SID and apply the Restricted Code SID.
// This blocks network access and restricts file reads, but requires cmd.exe.
if (!networkAccess) {
IntPtr networkSid;
if (ConvertStringSidToSid("S-1-5-2", out networkSid)) {
sidCount = 1;
int saaSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
pSidsToDisable = Marshal.AllocHGlobal(saaSize);
SID_AND_ATTRIBUTES saa = new SID_AND_ATTRIBUTES();
saa.Sid = networkSid;
saa.Attributes = 0;
Marshal.StructureToPtr(saa, pSidsToDisable, false);
}

IntPtr restrictedSid;
// S-1-5-12 is Restricted Code SID
if (ConvertStringSidToSid("S-1-5-12", out restrictedSid)) {
restrictCount = 1;
int saaSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
pSidsToRestrict = Marshal.AllocHGlobal(saaSize);
SID_AND_ATTRIBUTES saa = new SID_AND_ATTRIBUTES();
saa.Sid = restrictedSid;
saa.Attributes = 0;
Marshal.StructureToPtr(saa, pSidsToRestrict, false);
}
}
// If networkAccess == true, we are in Elevated mode (Level 2).
// We only strip privileges (DISABLE_MAX_PRIVILEGE), allowing network and powershell.

if (!CreateRestrictedToken(hToken, DISABLE_MAX_PRIVILEGE, sidCount, pSidsToDisable, 0, IntPtr.Zero, restrictCount, pSidsToRestrict, out hRestrictedToken)) {
Console.Error.WriteLine("Failed to create restricted token");
return 1;
}

// 2. Set Integrity Level to Low
IntPtr lowIntegritySid;
if (ConvertStringSidToSid("S-1-16-4096", out lowIntegritySid)) {
TOKEN_MANDATORY_LABEL tml = new TOKEN_MANDATORY_LABEL();
tml.Label.Sid = lowIntegritySid;
tml.Label.Attributes = SE_GROUP_INTEGRITY;
int tmlSize = Marshal.SizeOf(tml);
IntPtr pTml = Marshal.AllocHGlobal(tmlSize);
Marshal.StructureToPtr(tml, pTml, false);
SetTokenInformation(hRestrictedToken, TokenIntegrityLevel, pTml, (uint)tmlSize);
Marshal.FreeHGlobal(pTml);
}

// 3. Handle Internal Commands or External Process
if (command == "__read") {
string path = args[3];
return RunInImpersonation(hRestrictedToken, () => {
try {
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using (StreamReader sr = new StreamReader(fs)) {
char[] buffer = new char[4096];
int bytesRead;
while ((bytesRead = sr.Read(buffer, 0, buffer.Length)) > 0) {
Console.Write(buffer, 0, bytesRead);
}
}
return 0;
} catch (Exception e) {
Console.Error.WriteLine(e.Message);
return 1;
}
});
} else if (command == "__write") {
string path = args[3];
return RunInImpersonation(hRestrictedToken, () => {
try {
using (StreamReader reader = new StreamReader(Console.OpenStandardInput()))
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
using (StreamWriter writer = new StreamWriter(fs)) {
char[] buffer = new char[4096];
int bytesRead;
while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0) {
writer.Write(buffer, 0, bytesRead);
}
}
return 0;
} catch (Exception e) {
Console.Error.WriteLine(e.Message);
return 1;
}
});
}

// 4. Setup Job Object for external process
IntPtr hJob = CreateJobObject(IntPtr.Zero, null);
if (hJob != IntPtr.Zero) {
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limitInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
limitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
int limitSize = Marshal.SizeOf(limitInfo);
IntPtr pLimit = Marshal.AllocHGlobal(limitSize);
Marshal.StructureToPtr(limitInfo, pLimit, false);
SetInformationJobObject(hJob, JobObjectInfoClass.ExtendedLimitInformation, pLimit, (uint)limitSize);
Marshal.FreeHGlobal(pLimit);
}

// 5. Launch Process
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = GetStdHandle(-10);
si.hStdOutput = GetStdHandle(-11);
si.hStdError = GetStdHandle(-12);

List<string> quotedArgs = new List<string>();
for (int i = 2; i < args.Length; i++) {
quotedArgs.Add(QuoteArgument(args[i]));
}
string commandLine = string.Join(" ", quotedArgs.ToArray());

PROCESS_INFORMATION pi;
if (!CreateProcessAsUser(hRestrictedToken, null, commandLine, IntPtr.Zero, IntPtr.Zero, true, CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, IntPtr.Zero, cwd, ref si, out pi)) {
Console.Error.WriteLine("Failed to create process. Error: " + Marshal.GetLastWin32Error());
return 1;
}

if (hJob != IntPtr.Zero) {
AssignProcessToJobObject(hJob, pi.hProcess);
}

ResumeThread(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);

uint exitCode = 0;
GetExitCodeProcess(pi.hProcess, out exitCode);

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hRestrictedToken);
CloseHandle(hToken);
if (hJob != IntPtr.Zero) CloseHandle(hJob);

return (int)exitCode;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The Main method has several memory leaks from unmanaged resources not being freed.

  1. SID pointers: The IntPtr handles returned by ConvertStringSidToSid (for networkSid, restrictedSid, lowIntegritySid) are not being freed. According to documentation, these must be freed using LocalFree.
  2. Struct pointers: The memory allocated with Marshal.AllocHGlobal for pSidsToDisable and pSidsToRestrict is not being freed with Marshal.FreeHGlobal.

While this is a short-lived process, it's crucial to correctly manage unmanaged resources. Please ensure all allocated memory is freed. A try...finally block is a good pattern to guarantee cleanup.

You'll need to add the P/Invoke declaration for LocalFree:

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree(IntPtr hMem);

Then, ensure you call LocalFree on the SID pointers and Marshal.FreeHGlobal on the allocated struct pointers in a finally block.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@gemini-cli gemini-cli bot added the status/need-issue Pull requests that need to have an associated issue. label Mar 13, 2026
@mattKorwel
Copy link
Copy Markdown
Collaborator Author

PR Review: feat(core): implement native Windows sandboxing (#21807)

Summary

This PR introduces native Windows sandboxing using Restricted Tokens and Mandatory Integrity Control (MIC). It's a significant security enhancement for Windows users, providing a native alternative to Docker/Podman isolation. The implementation of `SandboxedFileSystemService` to bring native tools into the same security boundary is excellent.

However, there is a critical implementation gap: while the infrastructure for sandboxing shell commands is present, it is not currently integrated into `ShellExecutionService`. Consequently, shell commands are not yet running inside the `GeminiSandbox.exe` wrapper.

Findings

🔴 Critical

  • Shell Integration Missing: In `packages/core/src/services/shellExecutionService.ts`, `sandboxManager.prepareCommand` is called only to retrieve the sanitized environment. The `program` (the helper exe) and `args` returned by the manager are ignored. As a result, shell commands are spawned normally (via `cmd.exe` or `powershell.exe`) without the restricted token wrapper. To fix this, `ShellExecutionService` should use the `program` and `args` from the `SandboxedCommand` to spawn the process.

🟡 Improvements

  • C# Resource Management: In `GeminiSandbox.cs`, several handles (`hToken`, `hRestrictedToken`) and allocated memory (`pSidsToDisable`, `pSidsToRestrict`) are not explicitly closed or freed, especially in the early return paths for `__read` and `__write`. While the OS cleans up on exit, explicit management is standard practice.
  • `icacls` Latency: `WindowsSandboxManager` calls `icacls` on every command preparation. This adds a blocking shell call to every execution. Caching the paths that have already been granted "Low" integrity access within the session would improve performance.
  • Documentation: The new `SandboxConfig` fields (`allowedPaths`, `networkAccess`) are not documented in `docs/`. Please update the relevant reference files.

🟢 Value-Add

  • Tracker Tool Improvements: The improved `returnDisplay` for tracker tools is a great UX enhancement, though technically out of scope for the sandboxing feature.
  • Sandboxed File System: The `SandboxedFileSystemService` is a very clean design that correctly extends the security boundary to native Node.js tools.

Conclusion

Status: Request Changes

The sandboxing mechanism itself (the C# helper and the `SandboxManager` logic) looks solid, but it needs to be properly wired into `ShellExecutionService` to achieve its goal for shell tools.

if (ConvertStringSidToSid("S-1-5-2", out networkSid)) {
sidCount = 1;
int saaSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
pSidsToDisable = Marshal.AllocHGlobal(saaSize);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pointer is never freed, and will result in a memory leak.

return "\"" + escaped + "\"";
}

private static int RunInImpersonation(IntPtr hToken, Func<int> action) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When GeminiSandbox.exe processes internal __read or __write commands, it returns early via return RunInImpersonation(...). However, it fails to clean up the handles (hToken, hRestrictedToken) and any allocated memory (pSidsToDisable, pSidsToRestrict) before exiting. While Windows will reclaim these resources on process exit, relying on the OS cleanup for a security boundary tool is bad practice.

@galz10 galz10 self-requested a review March 16, 2026 17:30
@mattKorwel mattKorwel requested a review from a team as a code owner March 18, 2026 18:51
@mattKorwel
Copy link
Copy Markdown
Collaborator Author

🚀 PR Update: Addressing Review Comments & Async Assessment Summary

I've completed a round of manual fixes and an automated async review. Here's the current status of PR 21807:

✅ Resolved in latest push:

  • Shell Integration: Properly wired the sandbox wrapper into ShellExecutionService. Shell commands are now correctly wrapped in GeminiSandbox.exe.
  • Security Hardening: Added protections to prevent icacls from modifying system directories (C:\Windows, etc.) and implemented a cache for integrity grants to boost performance.
  • Resource Management: Fixed memory leaks and handle cleanup in the C# helper.
  • Configuration Consistency: Ensured SandboxedFileSystemService is activated whenever the Windows sandbox is configured.
  • Schema & Docs: Updated settingsSchema.ts and added comprehensive documentation in docs/cli/sandbox.md.

⏳ Remaining Items (Identified by Async Review):

  • License Header: Add the Apache-2.0 license header to GeminiSandbox.cs.
  • TypeScript Clean-up: Remove any types in sandboxedFileSystemService.test.ts.
  • Refactoring: Transition WindowsSandboxManager.ts from spawnSync to the preferred spawnAsync.
  • Logging: Replace empty catch blocks in WindowsSandboxManager.ts with debugLogger calls.
  • Robustness: Improve the discovery logic for csc.exe beyond the current hardcoded paths.

Tests for ShellExecutionService and WindowsSandboxManager are passing locally. I'll address these final compliance items next.

@mattKorwel
Copy link
Copy Markdown
Collaborator Author

✅ Final Update: All Async Review Findings Addressed

I've pushed the final set of changes addressing the remaining feedback:

  • Compliance: Added Apache-2.0 license header to GeminiSandbox.cs.
  • TypeScript: Removed all any usages in sandboxedFileSystemService.test.ts to satisfy strict typing rules and ESLint.
  • Robustness: Improved csc.exe discovery logic and added debugLogger to WindowsSandboxManager.ts catch blocks.
  • Verification: All relevant tests (shellExecutionService, windowsSandboxManager, sandboxedFileSystemService, and sandboxConfig) are passing.

This PR is now ready for final approval.

@wissamblue69-dotcom
Copy link
Copy Markdown

Summary

Implement native Windows sandboxing using Restricted Tokens and Mandatory Integrity Control (MIC).

Details

This PR adds a complete native Windows sandboxing implementation for Gemini CLI, providing robust isolation for both external shell tools and native Node.js tools. It avoids the need for Docker or Podman on Windows while providing stronger security than simple filesystem checks.

Key Features:

  • Advanced Restricted Tokens: Uses a native C# worker (GeminiSandbox.exe) to create a Windows Restricted Token with Double SID Evaluation (S-1-5-12). This model ensures the process has zero-trust access by default, blocking read access to any file not explicitly whitelisted.
  • Network Isolation: Strips the Network SID (S-1-5-2) from the token to block outbound traffic by default. This can be toggled via networkAccess in the configuration.
  • Filesystem Isolation (Read/Write):
    • Write protection is enforced via Mandatory Integrity Control (MIC) at Low Mandatory Level.
    • Read protection is enforced via the Restricted Token.
    • The WindowsSandboxManager uses icacls to grant "Low" integrity access to the CWD and any user-defined allowedPaths.
  • Native Tool Sandboxing: Introduces SandboxedFileSystemService. Native Gemini tools like read_file and write_file now delegate their work to the restricted worker, ensuring they operate within the same security boundary as shell tools.
  • Configuration Support: Added networkAccess and allowedPaths to SandboxConfig.

The C# helper source is included and automatically compiled on the first run using the standard Windows csc.exe compiler.

Related Issues

Partially addresses sandboxing requirements on Windows.

How to Validate

  1. On Windows, enable sandboxing in .gemini/config.yaml:
sandbox:
  enabled: true
  networkAccess: false
  1. Run a command that tries to write outside the CWD:
    @shell echo test > C:\Windows\test.txt -> Should fail with Access Denied.
  2. Run a command that tries to read a private file:
    @shell type %USERPROFILE%\.ssh\id_rsa -> Should fail with Access Denied.
  3. Run a command that tries to access the network:
    @shell curl http://google.com -> Should fail.
  4. Use native tools like read_file on a file in the workspace -> Should succeed.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
      • Podman
      • Native
    • Linux
      • npm run
      • npx
      • Docker
      • Podman

@mattKorwel
Copy link
Copy Markdown
Collaborator Author

🏁 Final Polish: Architectural & Build Hardening

I've pushed the final set of improvements addressing the deeper architectural feedback:

  • Factory Pattern: Consolidated all sandbox creation into a dedicated sandboxManagerFactory.ts, resolving circular dependencies and a double-initialization bug in Config.ts.
  • Async Refactoring: Transitioned WindowsSandboxManager from spawnSync to the mandatory spawnAsync utility.
  • Build Consistency: Updated the build scripts to properly include the C# source and compiled helper in the distribution bundle.
  • Strict Compliance: Resolved all TypeScript type mismatches, removed unsafe assertions, and added explicit justification for streaming spawn usage.
  • Verification: Confirmed a clean bill of health with local build, typecheck, and the full test suite for both Core and CLI.

The PR is now robust, compliant, and ready for final review.

@mattKorwel mattKorwel added this pull request to the merge queue Mar 19, 2026
Merged via the queue into main with commit c9a3369 Mar 19, 2026
26 of 27 checks passed
@mattKorwel mattKorwel deleted the mk-windows-sandboxing branch March 19, 2026 22:42
ProthamD pushed a commit to ProthamD/gemini-cli that referenced this pull request Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status/need-issue Pull requests that need to have an associated issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants