Skip to content

Implementation of sandbox "Write-Protected" Governance Files#23139

Merged
DavidAPierce merged 15 commits intomainfrom
sandboxing_write_governance
Mar 24, 2026
Merged

Implementation of sandbox "Write-Protected" Governance Files#23139
DavidAPierce merged 15 commits intomainfrom
sandboxing_write_governance

Conversation

@DavidAPierce
Copy link
Copy Markdown
Contributor

Summary

Added a governance files variable to the sandbox manager that should be used as a source of truth in operating system sandboxing to set read only status on gitignore and geminiignore files.

Details

  • Centralized Governance Definition: Added a GOVERNANCE_FILES constant in SandboxManager that explicitly lists .gitignore and .geminiignore as protected files.
  • Linux Protection (Bubblewrap): Updated LinuxSandboxManager to apply --ro-bind-try for all governance files. This creates a read-only override within the sandboxed workspace, even if the
    workspace itself is mounted as read-write.
  • macOS Protection (Seatbelt): Updated seatbeltArgsBuilder to inject explicit (deny file-write* ...) rules for governance files into the Seatbelt profile.

Related Issues

Fixes https://github.com/google-gemini/maintainers-gemini-cli/issues/1559

How to Validate

   - Updated unit tests for both Linux and macOS to ensure the protection flags and rules are correctly generated.
   - Manually verified on Linux using bwrap that attempting to rm a protected file results in a "Read-only file system" error

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
    • Linux
      • npm run
      • npx
      • Docker

@DavidAPierce DavidAPierce requested a review from a team as a code owner March 19, 2026 18:56
@gemini-cli
Copy link
Copy Markdown
Contributor

gemini-cli bot commented Mar 19, 2026

Hi @DavidAPierce, thank you so much for your contribution to Gemini CLI! We really appreciate the time and effort you've put into this.

We're making some updates to our contribution process to improve how we track and review changes. Please take a moment to review our recent discussion post: Improving Our Contribution Process & Introducing New Guidelines.

Key Update: Starting January 26, 2026, the Gemini CLI project will require all pull requests to be associated with an existing issue. Any pull requests not linked to an issue by that date will be automatically closed.

Thank you for your understanding and for being a part of our community!

@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 significantly enhances the integrity and security of sandbox environments by implementing robust write-protection for critical governance files, such as .gitignore and .geminiignore. This ensures that these foundational configuration files remain immutable within sandboxed workspaces, preventing unintended modifications across different operating systems. Concurrently, the changes also improve the agent's ability to incorporate various forms of memory into its prompts, making it more adaptable to different contextual requirements.

Highlights

  • Centralized Governance File Definition: A new GOVERNANCE_FILES constant was introduced in SandboxManager to explicitly list .gitignore and .geminiignore as files requiring write-protection within sandbox environments.
  • Linux Sandbox Protection: The LinuxSandboxManager was updated to apply --ro-bind-try for all defined governance files. This ensures these files are mounted as read-only within the sandboxed workspace, even if the workspace itself is writable.
  • macOS Sandbox Protection: The seatbeltArgsBuilder for macOS was enhanced to inject explicit (deny file-write* ...) rules for governance files into the Seatbelt profile, preventing write operations on these files.
  • Memory Injection Enhancements: The LocalAgentExecutor was updated to support injecting system instruction memory into the system prompt and environment/session memory into the first message, with new unit tests covering these scenarios.
  • Unit Test Coverage: New unit tests were added and existing ones updated for both Linux and macOS sandboxing to validate that the protection flags and rules for governance files are correctly generated and applied.
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.

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.

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

The pull request successfully implements write-protection for governance files (.gitignore, .geminiignore) within the sandbox environments for both Linux (Bubblewrap) and macOS (Seatbelt). The introduction of the GOVERNANCE_FILES constant centralizes the definition of these protected files, enhancing maintainability. The changes are well-tested with updated unit tests for both platforms, ensuring the protection flags and rules are correctly applied. The overall implementation is robust and directly addresses the stated objective of securing these critical files.

Note: Security Review did not run due to the size of the PR.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 19, 2026

Size Change: +3.11 kB (+0.01%)

Total Size: 26.2 MB

Filename Size Change
./bundle/chunk-CIYYVEZT.js 0 B -14.6 MB (removed) 🏆
./bundle/chunk-H6KSQEI5.js 0 B -3.64 MB (removed) 🏆
./bundle/core-ADVP36YN.js 0 B -43.4 kB (removed) 🏆
./bundle/devtoolsService-3CSJNL6C.js 0 B -27.7 kB (removed) 🏆
./bundle/interactiveCli-4APIZ7VT.js 0 B -1.62 MB (removed) 🏆
./bundle/oauth2-provider-4ATMSRKV.js 0 B -9.16 kB (removed) 🏆
./bundle/chunk-JL73EI7Q.js 3.64 MB +3.64 MB (new file) 🆕
./bundle/chunk-MTMVVPQW.js 14.6 MB +14.6 MB (new file) 🆕
./bundle/core-LSX6HSEG.js 43.4 kB +43.4 kB (new file) 🆕
./bundle/devtoolsService-B47WVZ3R.js 27.7 kB +27.7 kB (new file) 🆕
./bundle/interactiveCli-S7KNHHR6.js 1.62 MB +1.62 MB (new file) 🆕
./bundle/oauth2-provider-LJVEAE4H.js 9.16 kB +9.16 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size
./bundle/chunk-34MYV7JD.js 2.45 kB
./bundle/chunk-5AUYMPVF.js 858 B
./bundle/chunk-664ZODQF.js 124 kB
./bundle/chunk-7CGXLV7I.js 1.95 MB
./bundle/chunk-DAHVX5MI.js 206 kB
./bundle/chunk-IUUIT4SU.js 56.5 kB
./bundle/chunk-RJTRUG2J.js 39.8 kB
./bundle/devtools-36NN55EP.js 696 kB
./bundle/dist-T73EYRDX.js 356 B
./bundle/gemini.js 521 kB
./bundle/getMachineId-bsd-TXG52NKR.js 1.55 kB
./bundle/getMachineId-darwin-7OE4DDZ6.js 1.55 kB
./bundle/getMachineId-linux-SHIFKOOX.js 1.34 kB
./bundle/getMachineId-unsupported-5U5DOEYY.js 1.06 kB
./bundle/getMachineId-win-6KLLGOI4.js 1.72 kB
./bundle/memoryDiscovery-VGDJNR3K.js 922 B
./bundle/multipart-parser-KPBZEGQU.js 11.7 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/client/main.js 221 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js 227 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/index.js 11.5 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/types.js 132 B
./bundle/sandbox-macos-permissive-open.sb 890 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB
./bundle/sandbox-macos-strict-open.sb 4.82 kB
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB
./bundle/src-QVCVGIUX.js 47 kB
./bundle/tree-sitter-7U6MW5PS.js 274 kB
./bundle/tree-sitter-bash-34ZGLXVX.js 1.84 MB

compressed-size-action

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

@galz10 galz10 left a comment

Choose a reason for hiding this comment

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

Thanks for putting this together! The approach of centralizing governance files and enforcing them at the OS level (via bwrap and sandbox-exec) is the right way to handle this.

However, there are a few critical edge cases (specifically around non-existent files and symlinks) that need to be addressed before we can merge this safely, along with some minor improvements.

🔴 Critical Concerns (Action Required)

  • Linux bwrap vulnerability with non-existent files:
    In LinuxSandboxManager.ts, using --ro-bind-try presents a security gap. If .gitignore or .geminiignore does not exist on the host before bwrap is invoked, bwrap will silently skip mounting them. Because the parent workspace directory is mounted read-write, a sandboxed process can then successfully create and write to these governance files, completely bypassing the protection.

    • Suggested fix: Ensure the files exist on the host before configuring the bwrap mounts (e.g., "touch" the file without altering existing content). Once you guarantee they exist, you should change --ro-bind-try to --ro-bind so the sandbox explicitly fails if the protection cannot be applied.
  • Symlink Bypass (Linux & macOS):
    If a repository intentionally symlinks .gitignore to another file within the workspace (e.g., .gitignore -> config/shared.gitignore), the sandbox will protect the symlink itself, but the AI could write directly to config/shared.gitignore because the workspace is mounted read-write.

    • Suggested fix: Resolve the real path of the governance file (if it exists) before passing it to bwrap or Seatbelt (e.g., using fs.realpathSync), and apply the read-only/deny protection to both the symlink path and the resolved target path.
  • macOS Seatbelt Validation:
    As noted in the PR checklist, macOS Seatbelt validation was not performed. Please manually verify on macOS to ensure the deny rule properly prevents writes as expected (especially given the comment regarding rule evaluation order mentioned in the nits below).

🟡 Suggestions & Nits (Low Priority)

  • Type Safety (sandboxManager.ts):
    Make the GOVERNANCE_FILES array immutable using TypeScript's as const to prevent accidental mutation at runtime:

    export const GOVERNANCE_FILES = ['.gitignore', '.geminiignore'] as const;
  • Windows Implementation Tracking:
    Since this PR covers Linux and macOS, please add a TODO comment in the code (e.g., in sandboxManager.ts) or create a follow-up child bug/issue to implement equivalent protection for Windows (e.g., via Restricted Tokens or ACLs) to ensure consistent security across all platforms.

  • Protecting the .git directory:
    While this PR specifically addresses *ignore files, we should ideally protect the entire .git/ directory from being modified by the AI (preventing forced commits, history rewrites, or altering .git/info/exclude). Consider adding .git (or .git/) to the GOVERNANCE_FILES list. Both Seatbelt's subpath deny and bwrap's directory bind mounts support protecting directories.

  • Inaccurate Comment (seatbeltArgsBuilder.ts):
    The comment on line 64 states:
    // These are added before the workspace allow rule to ensure they take precedence.
    This is factually incorrect. profile is initialized with BASE_SEATBELT_PROFILE (which already contains the workspace allow rule at the very end), and the deny rules are appended to it. Therefore, they are added after the allow rule. If Seatbelt evaluates deny rules correctly regardless of order, simply remove or correct the comment. If the order does matter, the profile construction logic will need to be refactored to inject the deny rules prior to the base profile.

@DavidAPierce
Copy link
Copy Markdown
Contributor Author

Updated PR with the following:

Summary

This PR enhances the sandbox protection for repository governance files (.gitignore, .geminiignore, and .git) on Linux and macOS. It addresses critical vulnerabilities related to non-existent files and symlink escapes in the sandbox environment.

Details

  • Linux (bwrap):
    • Ensures governance files exist on the host (using touch) before invoking bwrap to prevent the sandboxed process from creating them in a read-write workspace.
    • Switched from --ro-bind-try to --ro-bind to ensure the sandbox fails if protection cannot be applied.
    • Resolves real paths of governance files to prevent symlink bypasses, protecting both the symlink path and its target.
    • Added .git to the list of protected files (treated as a directory).
  • macOS (Seatbelt):
    • Resolves real paths of governance files and applies deny rules to both original and real paths.
    • Uses subpath protection for directories like .git and literal for files.
    • Corrected an inaccurate comment regarding rule precedence in seatbeltArgsBuilder.ts.
  • General:
    • Made GOVERNANCE_FILES immutable (as const).
    • Added a TODO for equivalent Windows protection in sandboxManager.ts.

Related Issues

Related to #21807 (Native Windows sandboxing was recently added, but this PR focuses on cross-platform governance protection).

How to Validate

Run the sandbox unit tests:

npm test -w @google/gemini-cli-core -- src/sandbox/

Verify that the tests for LinuxSandboxManager and seatbeltArgsBuilder correctly check for:

  • .git protection.
  • Symlink resolution and protection of both paths.
  • Proper rule types (literal vs subpath).
  • Existence checks and touch logic on Linux.

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
    • Linux
      • npm run
      • npx
      • Docker

@DavidAPierce
Copy link
Copy Markdown
Contributor Author

Now that the windows implementation of sandboxing is merged, this TODO is stale. I will update this pr to address it.

Copy link
Copy Markdown
Collaborator

@galz10 galz10 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

Scope: Specific Commit: 8d652b7

This commit introduces logic to write-protect governance files (like .git, .gitignore, .geminiignore) in the Windows Sandbox environment. It achieves this by ensuring these files exist with Medium integrity on the host before executing a command in the Low integrity sandbox, effectively making them read-only to sandboxed processes. Overall, the approach is clever and aligns well with Windows security principles, but there is a critical flaw regarding how file paths are resolved.

Metadata Review

The PR title cleanly captures the intent of the change. The description provides an excellent, comprehensive breakdown of the implementation details across different operating systems (Linux, macOS, Windows) and includes clear validation steps. The context is great.

Concerns (Action Required)

  • packages/core/src/services/windowsSandboxManager.ts: WindowsSandboxManager uses req.cwd to construct the file path for GOVERNANCE_FILES (const filePath = path.join(req.cwd, file);). Because req.cwd is the working directory of the specific command being executed (which could be any nested subdirectory within the workspace), this implementation will incorrectly create dummy .git, .gitignore, and .geminiignore files in whatever directory the sandboxed command is run, rather than protecting the actual files at the workspace root.
    Suggestion:
    1. Update the WindowsSandboxManager constructor to accept a workspace: string parameter (similar to how LinuxSandboxManager and MacOsSandboxManager are instantiated).
    2. Update createSandboxManager in sandboxManagerFactory.ts to pass the workspace parameter when instantiating WindowsSandboxManager.
    3. Replace req.cwd with this.workspace when joining the governance file paths.

Nits (Optional Improvements)

  • packages/core/src/services/windowsSandboxManager.ts: The directory check (const isDirectory = file === '.git';) is hardcoded. While this works for the current contents of GOVERNANCE_FILES, it's brittle if future directories are added to the governance list.
    Suggestion: Consider checking the actual filesystem if the file already exists (similar to the fallback logic handled in seatbeltArgsBuilder.ts for macOS), or update the GOVERNANCE_FILES definition to be more explicit about which entries are directories.

  • packages/core/src/services/windowsSandboxManager.ts: In the touch() implementation, if a governance file happens to be a broken symlink (points to a non-existent file in a non-existent directory), fs.existsSync(filePath) returns false. The code then ensures the symlink's parent directory exists and calls fs.openSync(filePath, 'a'). This will follow the symlink and attempt to create the file at the target, which will throw an ENOENT error if the target's parent directory does not exist.
    Suggestion: This is a rare edge case, but using fs.lstatSync inside a try/catch to check if it's a broken symlink before trying to create it might be safer to prevent unexpected sandbox crashes.

@DavidAPierce
Copy link
Copy Markdown
Contributor Author

Merged with head and addressed previous comment.

Here is a breakdown of what was done:

  1. Action Required - req.cwd vs Workspace path in WindowsSandboxManager:

    • Status: Already resolved.
    • Explanation: During the previous merge, I updated WindowsSandboxManager to accept options: GlobalSandboxOptions in its constructor, bringing it in line with the Linux and macOS managers. I also updated the
      loop over GOVERNANCE_FILES to use this.options.workspace instead of req.cwd.
  2. Nit - Hardcoded directory check (const isDirectory = file === '.git';):

    • Status: Addressed.
    • Resolution: I updated the GOVERNANCE_FILES array definition in sandboxManager.ts to be a list of objects containing explicit path and isDirectory properties (e.g., { path: '.git', isDirectory: true }). I then
      updated the iterations across WindowsSandboxManager.ts, LinuxSandboxManager.ts, and MacOsSandboxManager.ts to consume these explicit properties. This removes the brittle hardcoded .git check and natively
      supports any future folder-based governance additions.
  3. Nit - touch() handling of broken symlinks:

    • Status: Addressed.
    • Resolution: I updated the touch implementation in all three managers. It now uses fs.lstatSync(filePath) inside a try/catch block. This ensures that if the file already exists—even as a broken symlink—the
      function will return early. This prevents fs.openSync from following a broken symlink and crashing with an ENOENT error.

@DavidAPierce DavidAPierce requested a review from galz10 March 23, 2026 19:12
@DavidAPierce DavidAPierce enabled auto-merge March 24, 2026 03:47
@DavidAPierce DavidAPierce added this pull request to the merge queue Mar 24, 2026
Merged via the queue into main with commit 37c8de3 Mar 24, 2026
28 checks passed
@DavidAPierce DavidAPierce deleted the sandboxing_write_governance branch March 24, 2026 04:18
ProthamD pushed a commit to ProthamD/gemini-cli that referenced this pull request Mar 29, 2026
…gemini#23139)

Co-authored-by: Abhi <43648792+abhipatel12@users.noreply.github.com>
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.

4 participants