Skip to content

Bug: Unhandled JSON.parse crash in migrateFromFileStorage() can break authentication #24087

Description

@G26karthik

What happened?

In packages/core/src/code_assist/oauth-credential-storage.ts, the migrateFromFileStorage() method reads the old credential file (~/.gemini/oauth_creds.json) and calls JSON.parse(credsJson) at line 131 without any try/catch.

The file-read error handling only catches ENOENT (file not found). If the credential file exists but contains corrupted/invalid JSON (e.g., partial write from a crash, disk error, manual edit), an unhandled SyntaxError propagates up and crashes the entire authentication flow.

// Current code (line 108-141):
private static async migrateFromFileStorage(): Promise<Credentials | null> {
    const oldFilePath = path.join(homedir(), GEMINI_DIR, OAUTH_FILE);

    let credsJson: string;
    try {
      credsJson = await fs.readFile(oldFilePath, 'utf-8');
    } catch (error: unknown) {
      if (/* ENOENT check */) {
        return null;
      }
      throw error;
    }

    // ⚠️ No try/catch — corrupted JSON crashes the app
    const credentials: Credentials = JSON.parse(credsJson);

    await this.saveCredentials(credentials);

    // ⚠️ Old file is deleted even if saveCredentials() throws above
    await fs.rm(oldFilePath, { force: true }).catch(() => {});

    return credentials;
}

Additionally, fs.rm(oldFilePath) at line 137 runs regardless of whether saveCredentials() succeeded. If saveCredentials() throws, the old file is already deleted — permanently losing the user's credentials.

What did you expect to happen?

  1. If JSON.parse fails on a corrupted file, the error should be caught, a warning logged, and null returned (treat as "no credentials to migrate") — not crash the app.
  2. The old credential file should only be deleted after saveCredentials() is confirmed to have succeeded.

Suggested Fix

private static async migrateFromFileStorage(): Promise<Credentials | null> {
    const oldFilePath = path.join(homedir(), GEMINI_DIR, OAUTH_FILE);

    let credsJson: string;
    try {
      credsJson = await fs.readFile(oldFilePath, 'utf-8');
    } catch (error: unknown) {
      if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') {
        return null;
      }
      throw error;
    }

    let credentials: Credentials;
    try {
      credentials = JSON.parse(credsJson) as Credentials;
    } catch {
      coreEvents.emitFeedback('warning', `Corrupted OAuth credential file at ${oldFilePath}, skipping migration`);
      return null;
    }

    await this.saveCredentials(credentials);

    // Only delete old file after saveCredentials() succeeded (we're past await above)
    await fs.rm(oldFilePath, { force: true }).catch(() => {});

    return credentials;
}

Client information

Client Information

Source code review — packages/core/src/code_assist/oauth-credential-storage.ts, lines 108-141.

Anything else we need to know?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area/coreIssues related to User Interface, OS Support, Core Functionalityeffort/small1 day or less: trivial logic, UI adjustments, docskind/bugpriority/p2Important but can be addressed in a future release.status/bot-triagedstatus/need-information

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions