Move keychain fallback to keychain service (#22332)

This commit is contained in:
christine betts
2026-03-13 16:57:08 -04:00
committed by GitHub
parent 1836a3a452
commit 8300be1101
10 changed files with 314 additions and 716 deletions
+37 -12
View File
@@ -14,6 +14,9 @@ import {
KEYCHAIN_TEST_PREFIX,
} from './keychainTypes.js';
import { isRecord } from '../utils/markdownUtils.js';
import { FileKeychain } from './fileKeychain.js';
export const FORCE_FILE_STORAGE_ENV_VAR = 'GEMINI_FORCE_FILE_STORAGE';
/**
* Service for interacting with OS-level secure storage (e.g. keytar).
@@ -31,6 +34,14 @@ export class KeychainService {
return (await this.getKeychain()) !== null;
}
/**
* Returns true if the service is using the encrypted file fallback backend.
*/
async isUsingFileFallback(): Promise<boolean> {
const keychain = await this.getKeychain();
return keychain instanceof FileKeychain;
}
/**
* Retrieves a secret for the given account.
* @throws Error if the keychain is unavailable.
@@ -85,26 +96,40 @@ export class KeychainService {
// High-level orchestration of the loading and testing cycle.
private async initializeKeychain(): Promise<Keychain | null> {
let resultKeychain: Keychain | null = null;
const forceFileStorage = process.env[FORCE_FILE_STORAGE_ENV_VAR] === 'true';
try {
const keychainModule = await this.loadKeychainModule();
if (keychainModule) {
if (await this.isKeychainFunctional(keychainModule)) {
resultKeychain = keychainModule;
} else {
debugLogger.log('Keychain functional verification failed');
if (!forceFileStorage) {
try {
const keychainModule = await this.loadKeychainModule();
if (keychainModule) {
if (await this.isKeychainFunctional(keychainModule)) {
resultKeychain = keychainModule;
} else {
debugLogger.log('Keychain functional verification failed');
}
}
} catch (error) {
// Avoid logging full error objects to prevent PII exposure.
const message = error instanceof Error ? error.message : String(error);
debugLogger.log(
'Keychain initialization encountered an error:',
message,
);
}
} catch (error) {
// Avoid logging full error objects to prevent PII exposure.
const message = error instanceof Error ? error.message : String(error);
debugLogger.log('Keychain initialization encountered an error:', message);
}
coreEvents.emitTelemetryKeychainAvailability(
new KeychainAvailabilityEvent(resultKeychain !== null),
new KeychainAvailabilityEvent(
resultKeychain !== null && !forceFileStorage,
),
);
// Fallback to FileKeychain if native keychain is unavailable or file storage is forced
if (!resultKeychain) {
resultKeychain = new FileKeychain();
debugLogger.log('Using FileKeychain fallback for secure storage.');
}
return resultKeychain;
}