Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions packages/cli/src/config/extensions/extensionSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,35 @@ describe('extensionSettings', () => {
expect(actualContent).toBe('VAR1="a value with spaces"\n');
});

it('should not set sensitive settings if the value is empty during initial setup', async () => {
const config: ExtensionConfig = {
name: 'test-ext',
version: '1.0.0',
settings: [
{
name: 's1',
description: 'd1',
envVar: 'SENSITIVE_VAR',
sensitive: true,
},
],
};
mockRequestSetting.mockResolvedValue('');

await maybePromptForSettings(
config,
'12345',
mockRequestSetting,
undefined,
undefined,
);

const userKeychain = new KeychainTokenStorage(
`Gemini CLI Extensions test-ext 12345`,
);
expect(await userKeychain.getSecret('SENSITIVE_VAR')).toBeNull();
});

it('should not attempt to clear secrets if keychain is unavailable', async () => {
// Arrange
const mockIsAvailable = vi.fn().mockResolvedValue(false);
Expand Down Expand Up @@ -738,5 +767,42 @@ describe('extensionSettings', () => {
const lines = actualContent.split('\n').filter((line) => line.length > 0);
expect(lines).toHaveLength(3); // Should only have the three variables
});

it('should delete a sensitive setting if the new value is empty', async () => {
mockRequestSetting.mockResolvedValue('');

await updateSetting(
config,
'12345',
'VAR2',
mockRequestSetting,
ExtensionSettingScope.USER,
tempWorkspaceDir,
);

const userKeychain = new KeychainTokenStorage(
`Gemini CLI Extensions test-ext 12345`,
);
expect(await userKeychain.getSecret('VAR2')).toBeNull();
});

it('should not throw if deleting a non-existent sensitive setting with empty value', async () => {
mockRequestSetting.mockResolvedValue('');
// Ensure it doesn't exist first
const userKeychain = new KeychainTokenStorage(
`Gemini CLI Extensions test-ext 12345`,
);
await userKeychain.deleteSecret('VAR2');

await updateSetting(
config,
'12345',
'VAR2',
mockRequestSetting,
ExtensionSettingScope.USER,
tempWorkspaceDir,
);
// Should complete without error
});
});
});
12 changes: 10 additions & 2 deletions packages/cli/src/config/extensions/extensionSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export async function maybePromptForSettings(
const nonSensitiveSettings: Record<string, string> = {};
for (const setting of settings) {
const value = allSettings[setting.envVar];
if (value === undefined) {
if (value === undefined || value === '') {
continue;
}
if (setting.sensitive) {
Expand Down Expand Up @@ -230,7 +230,15 @@ export async function updateSetting(
);

if (settingToUpdate.sensitive) {
await keychain.setSecret(settingToUpdate.envVar, newValue);
if (newValue) {
await keychain.setSecret(settingToUpdate.envVar, newValue);
} else {
try {
await keychain.deleteSecret(settingToUpdate.envVar);
} catch {
// Ignore if secret does not exist
}
Comment on lines +238 to +240

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The empty catch block is too broad and can swallow important errors, such as when the keychain is unavailable. It should only ignore the specific error for a non-existent secret and re-throw any other errors. Additionally, for debugging purposes, all caught exceptions should be logged, even if they are re-thrown or intentionally ignored.

      } catch (e) {
        // It's okay if the secret doesn't exist, but we should re-throw other errors.
        if (e instanceof Error && e.message.startsWith('No secret found for key:')) {
          // Log the expected ignored error for debugging purposes.
          console.debug(`Keychain access: No secret found for key. Ignoring as expected. Error: ${e.message}`);
        } else {
          // Log unexpected errors before re-throwing to ensure they are captured.
          console.error(`Keychain access: Unexpected error. Re-throwing. Error: ${e.message}`, e);
          throw e;
        }
      }
References
  1. When catching exceptions, log the detailed error for debugging instead of providing only a generic error message.

}
return;
}

Expand Down
Loading