chore: fix lint errors

This commit is contained in:
galz10
2026-02-24 11:50:29 -08:00
parent 508774fa20
commit d0dc83aa64
2 changed files with 43 additions and 13 deletions
@@ -11,6 +11,19 @@ import * as crypto from 'node:crypto';
import type { SecretStorage } from './types.js';
import { GEMINI_DIR, homedir } from '../../utils/paths.js';
/**
* Type guard for NodeJS.ErrnoException
*/
function isErrnoException(error: unknown): error is NodeJS.ErrnoException {
return (
error instanceof Error &&
('code' in error ||
'errno' in error ||
'path' in error ||
'syscall' in error)
);
}
/**
* Encrypted file-based storage for secrets, used as a fallback
* when a system keychain (like keytar) is unavailable.
@@ -23,7 +36,10 @@ export class FileSecretStorage implements SecretStorage {
const configDir = path.join(homedir(), GEMINI_DIR);
// Sanitize service name for filename
const sanitizedService = serviceName.replace(/[^a-zA-Z0-9-_.]/g, '_');
this.secretFilePath = path.join(configDir, `secrets-${sanitizedService}.json`);
this.secretFilePath = path.join(
configDir,
`secrets-${sanitizedService}.json`,
);
this.encryptionKey = this.deriveEncryptionKey();
}
@@ -76,18 +92,30 @@ export class FileSecretStorage implements SecretStorage {
try {
const data = await fs.readFile(this.secretFilePath, 'utf-8');
const decrypted = this.decrypt(data);
return JSON.parse(decrypted) as Record<string, string>;
const parsed = JSON.parse(decrypted) as unknown;
if (parsed === null || typeof parsed !== 'object') {
throw new Error('Secret file content is not a valid object');
}
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(parsed)) {
if (typeof value === 'string') {
result[key] = value;
}
}
return result;
} catch (error: unknown) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const err = error as NodeJS.ErrnoException & { message?: string };
if (err.code === 'ENOENT') {
if (isErrnoException(error) && error.code === 'ENOENT') {
return {};
}
const message = error instanceof Error ? error.message : '';
if (
err.message?.includes('Invalid encrypted data format') ||
err.message?.includes(
'Unsupported state or unable to authenticate data',
)
message.includes('Invalid encrypted data format') ||
message.includes('Unsupported state or unable to authenticate data')
) {
throw new Error('Secret file corrupted');
}
@@ -133,11 +161,10 @@ export class FileSecretStorage implements SecretStorage {
try {
await fs.unlink(this.secretFilePath);
} catch (error: unknown) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const err = error as NodeJS.ErrnoException;
if (err.code !== 'ENOENT') {
throw error;
if (isErrnoException(error) && error.code === 'ENOENT') {
return;
}
throw error;
}
}
}
@@ -11,3 +11,6 @@ export * from './keychain-token-storage.js';
export * from './hybrid-token-storage.js';
export * from './file-secret-storage.js';
export * from './hybrid-secret-storage.js';
export const DEFAULT_SERVICE_NAME = 'gemini-cli-mcp';
export const FORCE_ENCRYPTED_FILE_ENV_VAR = 'GEMINI_FORCE_ENCRYPTED_FILE';