diff --git a/src/web-server/services/claude-extension-settings-service.ts b/src/web-server/services/claude-extension-settings-service.ts index aad9fe24..97d97474 100644 --- a/src/web-server/services/claude-extension-settings-service.ts +++ b/src/web-server/services/claude-extension-settings-service.ts @@ -231,8 +231,12 @@ function writeJsonDocument( const tempPath = `${filePath}.tmp.${uniqueFileNonce()}`; try { - fs.writeFileSync(tempPath, JSON.stringify(data, null, 2) + '\n', 'utf8'); + fs.writeFileSync(tempPath, JSON.stringify(data, null, 2) + '\n', { + encoding: 'utf8', + mode: 0o600, + }); fs.renameSync(tempPath, filePath); + fs.chmodSync(filePath, 0o600); } catch (error) { if (fs.existsSync(tempPath)) { fs.rmSync(tempPath, { force: true }); diff --git a/tests/unit/web-server/claude-extension-routes.test.ts b/tests/unit/web-server/claude-extension-routes.test.ts index edb4a1ee..27fc2d16 100644 --- a/tests/unit/web-server/claude-extension-routes.test.ts +++ b/tests/unit/web-server/claude-extension-routes.test.ts @@ -313,6 +313,12 @@ describe('web-server claude-extension-routes', () => { it('creates a binding and applies managed settings to shared + IDE targets', async () => { const ideSettingsPath = path.join(tempHome, 'ide', 'vscode', 'settings.json'); + const sharedSettingsPath = path.join(tempHome, '.claude', 'settings.json'); + fs.mkdirSync(path.dirname(ideSettingsPath), { recursive: true }); + fs.mkdirSync(path.dirname(sharedSettingsPath), { recursive: true }); + fs.writeFileSync(sharedSettingsPath, JSON.stringify({ env: {} }, null, 2) + '\n', { mode: 0o600 }); + fs.writeFileSync(ideSettingsPath, JSON.stringify({}, null, 2) + '\n', { mode: 0o600 }); + const createResponse = await fetch(`${baseUrl}/api/claude-extension/bindings`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -352,7 +358,6 @@ describe('web-server claude-extension-routes', () => { expect(applied.sharedSettings.state).toBe('applied'); expect(applied.ideSettings.state).toBe('applied'); - const sharedSettingsPath = path.join(tempHome, '.claude', 'settings.json'); const sharedSettings = JSON.parse(fs.readFileSync(sharedSettingsPath, 'utf8')) as { env?: Record; }; @@ -370,6 +375,9 @@ describe('web-server claude-extension-routes', () => { ) ).toBe(true); expect(ideSettings['claudeCode.disableLoginPrompt']).toBe(true); + + expect(fs.statSync(sharedSettingsPath).mode & 0o777).toBe(0o600); + expect(fs.statSync(ideSettingsPath).mode & 0o777).toBe(0o600); }); it('resets only managed keys and preserves unrelated shared + IDE settings', async () => {