mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-04 00:44:05 -07:00
fix: enforce folder trust for workspace settings, skills, and context (#17596)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
+124
-119
@@ -277,8 +277,11 @@ export class LoadedSettings {
|
||||
this.system = system;
|
||||
this.systemDefaults = systemDefaults;
|
||||
this.user = user;
|
||||
this.workspace = workspace;
|
||||
this._workspaceFile = workspace;
|
||||
this.isTrusted = isTrusted;
|
||||
this.workspace = isTrusted
|
||||
? workspace
|
||||
: this.createEmptyWorkspace(workspace);
|
||||
this.errors = errors;
|
||||
this._merged = this.computeMergedSettings();
|
||||
}
|
||||
@@ -286,10 +289,11 @@ export class LoadedSettings {
|
||||
readonly system: SettingsFile;
|
||||
readonly systemDefaults: SettingsFile;
|
||||
readonly user: SettingsFile;
|
||||
readonly workspace: SettingsFile;
|
||||
readonly isTrusted: boolean;
|
||||
workspace: SettingsFile;
|
||||
isTrusted: boolean;
|
||||
readonly errors: SettingsError[];
|
||||
|
||||
private _workspaceFile: SettingsFile;
|
||||
private _merged: MergedSettings;
|
||||
private _remoteAdminSettings: Partial<Settings> | undefined;
|
||||
|
||||
@@ -297,6 +301,26 @@ export class LoadedSettings {
|
||||
return this._merged;
|
||||
}
|
||||
|
||||
setTrusted(isTrusted: boolean): void {
|
||||
if (this.isTrusted === isTrusted) {
|
||||
return;
|
||||
}
|
||||
this.isTrusted = isTrusted;
|
||||
this.workspace = isTrusted
|
||||
? this._workspaceFile
|
||||
: this.createEmptyWorkspace(this._workspaceFile);
|
||||
this._merged = this.computeMergedSettings();
|
||||
coreEvents.emitSettingsChanged();
|
||||
}
|
||||
|
||||
private createEmptyWorkspace(workspace: SettingsFile): SettingsFile {
|
||||
return {
|
||||
...workspace,
|
||||
settings: {},
|
||||
originalSettings: {},
|
||||
};
|
||||
}
|
||||
|
||||
private computeMergedSettings(): MergedSettings {
|
||||
const merged = mergeSettings(
|
||||
this.system.settings,
|
||||
@@ -341,8 +365,21 @@ export class LoadedSettings {
|
||||
|
||||
setValue(scope: LoadableSettingScope, key: string, value: unknown): void {
|
||||
const settingsFile = this.forScope(scope);
|
||||
setNestedProperty(settingsFile.settings, key, value);
|
||||
setNestedProperty(settingsFile.originalSettings, key, value);
|
||||
|
||||
// Clone value to prevent reference sharing between settings and originalSettings
|
||||
const valueToSet =
|
||||
typeof value === 'object' && value !== null
|
||||
? structuredClone(value)
|
||||
: value;
|
||||
|
||||
setNestedProperty(settingsFile.settings, key, valueToSet);
|
||||
// Use a fresh clone for originalSettings to ensure total independence
|
||||
setNestedProperty(
|
||||
settingsFile.originalSettings,
|
||||
key,
|
||||
structuredClone(valueToSet),
|
||||
);
|
||||
|
||||
this._merged = this.computeMergedSettings();
|
||||
saveSettings(settingsFile);
|
||||
coreEvents.emitSettingsChanged();
|
||||
@@ -592,9 +629,10 @@ export function loadSettings(
|
||||
// For the initial trust check, we can only use user and system settings.
|
||||
const initialTrustCheckSettings = customDeepMerge(
|
||||
getMergeStrategyForPath,
|
||||
{},
|
||||
systemSettings,
|
||||
getDefaultsFromSchema(),
|
||||
systemDefaultSettings,
|
||||
userSettings,
|
||||
systemSettings,
|
||||
);
|
||||
const isTrusted =
|
||||
isWorkspaceTrusted(initialTrustCheckSettings as Settings, workspaceDir)
|
||||
@@ -672,57 +710,55 @@ export function migrateDeprecatedSettings(
|
||||
removeDeprecated = false,
|
||||
): boolean {
|
||||
let anyModified = false;
|
||||
|
||||
const migrateBoolean = (
|
||||
settings: Record<string, unknown>,
|
||||
oldKey: string,
|
||||
newKey: string,
|
||||
): boolean => {
|
||||
let modified = false;
|
||||
const oldValue = settings[oldKey];
|
||||
const newValue = settings[newKey];
|
||||
|
||||
if (typeof oldValue === 'boolean') {
|
||||
if (typeof newValue === 'boolean') {
|
||||
// Both exist, trust the new one
|
||||
if (removeDeprecated) {
|
||||
delete settings[oldKey];
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
// Only old exists, migrate to new (inverted)
|
||||
settings[newKey] = !oldValue;
|
||||
if (removeDeprecated) {
|
||||
delete settings[oldKey];
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
};
|
||||
|
||||
const processScope = (scope: LoadableSettingScope) => {
|
||||
const settings = loadedSettings.forScope(scope).settings;
|
||||
|
||||
// Migrate inverted boolean settings (disableX -> enableX)
|
||||
// These settings were renamed and their boolean logic inverted
|
||||
// Migrate general settings
|
||||
const generalSettings = settings.general as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
const uiSettings = settings.ui as Record<string, unknown> | undefined;
|
||||
const contextSettings = settings.context as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
|
||||
// Migrate general settings (disableAutoUpdate, disableUpdateNag)
|
||||
if (generalSettings) {
|
||||
const newGeneral: Record<string, unknown> = { ...generalSettings };
|
||||
const newGeneral = { ...generalSettings };
|
||||
let modified = false;
|
||||
|
||||
if (typeof newGeneral['disableAutoUpdate'] === 'boolean') {
|
||||
if (typeof newGeneral['enableAutoUpdate'] === 'boolean') {
|
||||
// Both exist, trust the new one
|
||||
if (removeDeprecated) {
|
||||
delete newGeneral['disableAutoUpdate'];
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
const oldValue = newGeneral['disableAutoUpdate'];
|
||||
newGeneral['enableAutoUpdate'] = !oldValue;
|
||||
if (removeDeprecated) {
|
||||
delete newGeneral['disableAutoUpdate'];
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof newGeneral['disableUpdateNag'] === 'boolean') {
|
||||
if (typeof newGeneral['enableAutoUpdateNotification'] === 'boolean') {
|
||||
// Both exist, trust the new one
|
||||
if (removeDeprecated) {
|
||||
delete newGeneral['disableUpdateNag'];
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
const oldValue = newGeneral['disableUpdateNag'];
|
||||
newGeneral['enableAutoUpdateNotification'] = !oldValue;
|
||||
if (removeDeprecated) {
|
||||
delete newGeneral['disableUpdateNag'];
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
modified =
|
||||
migrateBoolean(newGeneral, 'disableAutoUpdate', 'enableAutoUpdate') ||
|
||||
modified;
|
||||
modified =
|
||||
migrateBoolean(
|
||||
newGeneral,
|
||||
'disableUpdateNag',
|
||||
'enableAutoUpdateNotification',
|
||||
) || modified;
|
||||
|
||||
if (modified) {
|
||||
loadedSettings.setValue(scope, 'general', newGeneral);
|
||||
@@ -731,94 +767,63 @@ export function migrateDeprecatedSettings(
|
||||
}
|
||||
|
||||
// Migrate ui settings
|
||||
const uiSettings = settings.ui as Record<string, unknown> | undefined;
|
||||
if (uiSettings) {
|
||||
const newUi: Record<string, unknown> = { ...uiSettings };
|
||||
let modified = false;
|
||||
|
||||
// Migrate ui.accessibility.disableLoadingPhrases -> ui.accessibility.enableLoadingPhrases
|
||||
const newUi = { ...uiSettings };
|
||||
const accessibilitySettings = newUi['accessibility'] as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
if (
|
||||
accessibilitySettings &&
|
||||
typeof accessibilitySettings['disableLoadingPhrases'] === 'boolean'
|
||||
) {
|
||||
const newAccessibility: Record<string, unknown> = {
|
||||
...accessibilitySettings,
|
||||
};
|
||||
if (
|
||||
typeof accessibilitySettings['enableLoadingPhrases'] === 'boolean'
|
||||
) {
|
||||
// Both exist, trust the new one
|
||||
if (removeDeprecated) {
|
||||
delete newAccessibility['disableLoadingPhrases'];
|
||||
newUi['accessibility'] = newAccessibility;
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
const oldValue = accessibilitySettings['disableLoadingPhrases'];
|
||||
newAccessibility['enableLoadingPhrases'] = !oldValue;
|
||||
if (removeDeprecated) {
|
||||
delete newAccessibility['disableLoadingPhrases'];
|
||||
}
|
||||
newUi['accessibility'] = newAccessibility;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
loadedSettings.setValue(scope, 'ui', newUi);
|
||||
anyModified = true;
|
||||
if (accessibilitySettings) {
|
||||
const newAccessibility = { ...accessibilitySettings };
|
||||
if (
|
||||
migrateBoolean(
|
||||
newAccessibility,
|
||||
'disableLoadingPhrases',
|
||||
'enableLoadingPhrases',
|
||||
)
|
||||
) {
|
||||
newUi['accessibility'] = newAccessibility;
|
||||
loadedSettings.setValue(scope, 'ui', newUi);
|
||||
anyModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate context settings
|
||||
const contextSettings = settings.context as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
if (contextSettings) {
|
||||
const newContext: Record<string, unknown> = { ...contextSettings };
|
||||
let modified = false;
|
||||
|
||||
// Migrate context.fileFiltering.disableFuzzySearch -> context.fileFiltering.enableFuzzySearch
|
||||
const newContext = { ...contextSettings };
|
||||
const fileFilteringSettings = newContext['fileFiltering'] as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
if (
|
||||
fileFilteringSettings &&
|
||||
typeof fileFilteringSettings['disableFuzzySearch'] === 'boolean'
|
||||
) {
|
||||
const newFileFiltering: Record<string, unknown> = {
|
||||
...fileFilteringSettings,
|
||||
};
|
||||
if (typeof fileFilteringSettings['enableFuzzySearch'] === 'boolean') {
|
||||
// Both exist, trust the new one
|
||||
if (removeDeprecated) {
|
||||
delete newFileFiltering['disableFuzzySearch'];
|
||||
newContext['fileFiltering'] = newFileFiltering;
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
const oldValue = fileFilteringSettings['disableFuzzySearch'];
|
||||
newFileFiltering['enableFuzzySearch'] = !oldValue;
|
||||
if (removeDeprecated) {
|
||||
delete newFileFiltering['disableFuzzySearch'];
|
||||
}
|
||||
newContext['fileFiltering'] = newFileFiltering;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
loadedSettings.setValue(scope, 'context', newContext);
|
||||
anyModified = true;
|
||||
if (fileFilteringSettings) {
|
||||
const newFileFiltering = { ...fileFilteringSettings };
|
||||
if (
|
||||
migrateBoolean(
|
||||
newFileFiltering,
|
||||
'disableFuzzySearch',
|
||||
'enableFuzzySearch',
|
||||
)
|
||||
) {
|
||||
newContext['fileFiltering'] = newFileFiltering;
|
||||
loadedSettings.setValue(scope, 'context', newContext);
|
||||
anyModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate experimental agent settings
|
||||
anyModified ||= migrateExperimentalSettings(
|
||||
settings,
|
||||
loadedSettings,
|
||||
scope,
|
||||
removeDeprecated,
|
||||
);
|
||||
anyModified =
|
||||
migrateExperimentalSettings(
|
||||
settings,
|
||||
loadedSettings,
|
||||
scope,
|
||||
removeDeprecated,
|
||||
) || anyModified;
|
||||
};
|
||||
|
||||
processScope(SettingScope.User);
|
||||
|
||||
Reference in New Issue
Block a user