Repository navigation
fix(cli): stop an untrusted workspace wiping its own settings.json - #29466
lets-order-some-fries wants to merge 1 commit into
Conversation
An untrusted folder gets an emptied copy of its workspace settings, and
createEmptyWorkspace spreads the original, so the copy keeps the real path
and leaves readOnly unset. setValue then persists it, because isPersistable
is !readOnly, and saveSettings writes originalSettings - which is {} plus
whatever was just set - to that real path. updateSettingsFilePreservingFormat
merges via applyKeyDiff, which is sync-by-omission: every key on disk and
absent from the update is deleted.
So `gemini mcp add newsrv echo` in a folder the user has not trusted - the
default state - reduces .gemini/settings.json to a single mcpServers entry,
destroying unrelated settings AND the servers already configured, while
reporting success. Reproduces on published 0.60.0 and on main.
Marks the emptied copy readOnly, which is the same mechanism loadSettings
already uses to keep the workspace unwritable when it is the home directory.
saveSettings has exactly one caller and it is gated on isPersistable, so this
closes the write path.
setValue now also warns when a workspace write did not reach disk. Callers
such as mcp add report success immediately afterwards, so silence reads as
"saved". Only User and Workspace are ever written, so this cannot fire for
the intentionally read-only system scopes.
Tests use real files in a temp dir rather than fs mocks, since what lands on
disk is the whole point; 3 of the 6 fail without the fix.
Fixes google-gemini#29465
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 critical issue where the CLI would silently destroy a project's settings.json file when running commands like 'gemini mcp add' in an untrusted directory. By marking the in-memory representation of an untrusted workspace as read-only, the system now correctly prevents destructive write operations. Additionally, the CLI now provides explicit feedback to the user when a write operation is blocked, preventing the false impression of success. 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 the 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 counterproductive. 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
|
|
📊 PR Size: size/M
|
There was a problem hiding this comment.
Code Review
This pull request ensures that workspace settings are not silently discarded when a workspace is untrusted or unwritable. It marks the emptied workspace settings object as read-only to prevent it from being persisted back to disk, and emits a warning feedback event when a write is attempted under these conditions. A new test suite has been added to verify these settings write behaviors. There are no review comments, and I have no feedback to provide.
|
Hi there! Thank you for your interest in contributing to Gemini CLI. To ensure we maintain high code quality and focus on our prioritized roadmap, we only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'. This PR will be closed in 7 days if it remains without that designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding. |
|
This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding. |
Summary
gemini mcp addrun in a folder the user has not trusted silently destroys thatproject's
.gemini/settings.json, keeping only the key it just wrote, and reportssuccess. Untrusted is the default state for any folder not explicitly trusted, so this is
a normal path. Reproduces on the published 0.60.0 and on
main@d5b3e3acc.Fixes #29465.
Details
Four steps, all in
packages/cli/src:config/settings.ts— an untrusted workspace is replaced by an emptied copy:createEmptyWorkspacespreads the original, so the copy keeps the realpathandleaves
readOnlyunset.setValue()resolves throughforScope(Workspace), gets that copy, and persists it —isPersistableis!settingsFile.readOnly, soundefinedmeans writable.saveSettings()writesoriginalSettings—{}plus the one new key — to the real path.utils/commentJson.ts—applyKeyDiffis sync-by-omission: every key on disk andabsent from the update is deleted. Correct when
originalSettingsmirrors the file;destructive when the in-memory copy was deliberately emptied.
The fix
createEmptyWorkspacemarks the copyreadOnly: true. That is the same mechanismloadSettingsalready uses to keep the workspace unwritable when it is the homedirectory (
readOnly: storage.isWorkspaceHomeDir()), so this is the existing idiom ratherthan a new concept.
saveSettingshas exactly one caller and it is gated onisPersistable, so this closes the write path completely.setValuealso now warns when a workspace write did not reach disk. Callers such asmcp addlog success immediately aftersetValuereturns, so staying silent reads as"saved". Only
UserandWorkspaceare ever written (nothing in the tree callssetValue(SettingScope.System…)), so this cannot fire for the intentionally read-onlysystem scopes.
Deliberately out of scope
What should happen instead of the write is left to you, and tracked in #29465: refuse
outright with "this folder is not trusted", or write through to
_workspaceFileso anexplicit
mcp addstill lands. Both are behaviour decisions on a trust boundary. This PRonly stops the destruction and stops the silence.
One consequence of that: on an untrusted folder
mcp addnow prints the warning andthen still prints its own success line. Suppressing the second needs a result threaded
back to each of the four callers (
mcp add,mcp remove,gemma setup,hooks migrate),which is the same design decision. The warning is printed first so the user sees it.
Related Issues
Fixes #29465
How to Validate
New file, 6 cases, real files in a temp dir rather than
fsmocks — what lands on disk isthe whole point. 3 fail without the source change; the others are controls asserting
the trusted path is untouched.
End to end:
On
mainthe firstmcp addleaves only{"mcpServers":{"newsrv":…}}—ui,contextand the pre-existing
existingserver all gone.Observed on this branch (macOS, node v22.13.0): md5 identical across the untrusted run,
warning printed, and the trusted control merges correctly.
Suites on this branch:
src/config/+src/commands/mcp/→ 41 files, 956 passed.Full
npm run test -w packages/cli→ 466 of 467 files pass; the one failure issrc/gemini.test.tsx, which fails the same 9 tests on cleanmainin this checkout(an untrusted-workspace environment issue — I re-ran it on
mainto confirm the lists areidentical, since two of those nine are trust-related and this change touches trust).
npm run typecheck -w packages/cliandeslint packages/cli/src/config/both clean.Pre-Merge Checklist
behaviour changes; the fix restores what the docs already imply
were never meant to persist; today they persist by destroying the file.
No platform-specific paths involved; the tests use
os.tmpdir()andpath.join, so theyrun identically on the Windows shard.
🤖 Generated with Claude Code