Fix #8077: Settings command overwrites entire JSON file, leaking environment variables (#8154)

Co-authored-by: Shreya Keshive <skeshive@gmail.com>
This commit is contained in:
fuyou
2025-09-13 14:31:15 +08:00
committed by GitHub
parent a96cc2f148
commit 2135dbb6a4
8 changed files with 379 additions and 26 deletions
+44 -17
View File
@@ -29,6 +29,7 @@ import {
} from './settingsSchema.js';
import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
import { customDeepMerge } from '../utils/deepMerge.js';
import { updateSettingsFilePreservingFormat } from '../utils/commentJson.js';
function getMergeStrategyForPath(path: string[]): MergeStrategy | undefined {
let current: SettingDefinition | undefined = undefined;
@@ -172,7 +173,9 @@ export interface SettingsError {
export interface SettingsFile {
settings: Settings;
originalSettings: Settings;
path: string;
rawJson?: string;
}
function setNestedProperty(
@@ -406,6 +409,7 @@ export class LoadedSettings {
setValue(scope: SettingScope, key: string, value: unknown): void {
const settingsFile = this.forScope(scope);
setNestedProperty(settingsFile.settings, key, value);
setNestedProperty(settingsFile.originalSettings, key, value);
this._merged = this.computeMergedSettings();
saveSettings(settingsFile);
}
@@ -539,7 +543,10 @@ export function loadSettings(
workspaceDir,
).getWorkspaceSettingsPath();
const loadAndMigrate = (filePath: string, scope: SettingScope): Settings => {
const loadAndMigrate = (
filePath: string,
scope: SettingScope,
): { settings: Settings; rawJson?: string } => {
try {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf-8');
@@ -554,7 +561,7 @@ export function loadSettings(
message: 'Settings file is not a valid JSON object.',
path: filePath,
});
return {};
return { settings: {} };
}
let settingsObject = rawSettings as Record<string, unknown>;
@@ -582,7 +589,7 @@ export function loadSettings(
settingsObject = migratedSettings;
}
}
return settingsObject as Settings;
return { settings: settingsObject as Settings, rawJson: content };
}
} catch (error: unknown) {
settingsErrors.push({
@@ -590,23 +597,40 @@ export function loadSettings(
path: filePath,
});
}
return {};
return { settings: {} };
};
systemSettings = loadAndMigrate(systemSettingsPath, SettingScope.System);
systemDefaultSettings = loadAndMigrate(
const systemResult = loadAndMigrate(systemSettingsPath, SettingScope.System);
const systemDefaultsResult = loadAndMigrate(
systemDefaultsPath,
SettingScope.SystemDefaults,
);
userSettings = loadAndMigrate(USER_SETTINGS_PATH, SettingScope.User);
const userResult = loadAndMigrate(USER_SETTINGS_PATH, SettingScope.User);
let workspaceResult: { settings: Settings; rawJson?: string } = {
settings: {} as Settings,
rawJson: undefined,
};
if (realWorkspaceDir !== realHomeDir) {
workspaceSettings = loadAndMigrate(
workspaceResult = loadAndMigrate(
workspaceSettingsPath,
SettingScope.Workspace,
);
}
const systemOriginalSettings = structuredClone(systemResult.settings);
const systemDefaultsOriginalSettings = structuredClone(
systemDefaultsResult.settings,
);
const userOriginalSettings = structuredClone(userResult.settings);
const workspaceOriginalSettings = structuredClone(workspaceResult.settings);
// Environment variables for runtime use
systemSettings = resolveEnvVarsInObject(systemResult.settings);
systemDefaultSettings = resolveEnvVarsInObject(systemDefaultsResult.settings);
userSettings = resolveEnvVarsInObject(userResult.settings);
workspaceSettings = resolveEnvVarsInObject(workspaceResult.settings);
// Support legacy theme names
if (userSettings.ui?.theme === 'VS') {
userSettings.ui.theme = DefaultLight.name;
@@ -642,11 +666,6 @@ export function loadSettings(
// the settings to avoid a cycle
loadEnvironment(tempMergedSettings);
// Now that the environment is loaded, resolve variables in the settings.
systemSettings = resolveEnvVarsInObject(systemSettings);
userSettings = resolveEnvVarsInObject(userSettings);
workspaceSettings = resolveEnvVarsInObject(workspaceSettings);
// Create LoadedSettings first
if (settingsErrors.length > 0) {
@@ -662,18 +681,26 @@ export function loadSettings(
{
path: systemSettingsPath,
settings: systemSettings,
originalSettings: systemOriginalSettings,
rawJson: systemResult.rawJson,
},
{
path: systemDefaultsPath,
settings: systemDefaultSettings,
originalSettings: systemDefaultsOriginalSettings,
rawJson: systemDefaultsResult.rawJson,
},
{
path: USER_SETTINGS_PATH,
settings: userSettings,
originalSettings: userOriginalSettings,
rawJson: userResult.rawJson,
},
{
path: workspaceSettingsPath,
settings: workspaceSettings,
originalSettings: workspaceOriginalSettings,
rawJson: workspaceResult.rawJson,
},
isTrusted,
migratedInMemorScopes,
@@ -688,17 +715,17 @@ export function saveSettings(settingsFile: SettingsFile): void {
fs.mkdirSync(dirPath, { recursive: true });
}
let settingsToSave = settingsFile.settings;
let settingsToSave = settingsFile.originalSettings;
if (!MIGRATE_V2_OVERWRITE) {
settingsToSave = migrateSettingsToV1(
settingsToSave as Record<string, unknown>,
) as Settings;
}
fs.writeFileSync(
// Use the format-preserving update function
updateSettingsFilePreservingFormat(
settingsFile.path,
JSON.stringify(settingsToSave, null, 2),
'utf-8',
settingsToSave as Record<string, unknown>,
);
} catch (error) {
console.error('Error saving user settings file:', error);