Change:
<!-- Do Not Delete This! featurerequesttemplate --<!-- Please read our Rules of Conduct: --<!-- Please search existing issues to avoid creating duplicates. --Currently signing out of your GitHub account only removes the token from the secret store but the token continues to be valid, which means that if it was leaked the attacker can continue to make use of it. To mitigate this, VS Code should revoke the token on sign out, see this API:
I wish this API didn't require the client secret...

Discussion:
'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js'), './node/authServer': path.resolve(__dirname, 'src/browser/authServer'), './node/crypto': path.resolve(__dirname, 'src/browser/crypto'), <del> './node/fetch': path.resolve(__dirname, 'src/browser/fetch') </del> <ins> './node/fetch': path.resolve(__dirname, 'src/browser/fetch'), './node/buffer': path.resolve(__dirname, 'src/browser/buffer'), </ins> } } });

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ export function base64Encode(text: string): string { return btoa(text); } </ins>

this.output.error(message); } <ins> public warn(message: string): void { this.output.warn(message); } </ins> }

supportsGitHubEnterpriseServer: false, supportsHostedGitHubEnterprise: true, supportsRemoteExtensionHost: true, <del> supportsWebWorkerExtensionHost: true, </del> <ins> // Web worker can't open a port to listen for the redirect supportsWebWorkerExtensionHost: false, </ins> // exchanging a code for a token requires a client secret supportsNoClientSecret: false, supportsSupportedClients: true,

sessions.splice(sessionIndex, 1); await this.storeSessions(sessions); <ins> await this._githubServer.logout(session); </ins> this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); } else {

import { fetching } from './node/fetch'; import { ExtensionHost, GitHubTarget, getFlows } from './flows'; import { NETWORK_ERROR, USER_CANCELLATION_ERROR } from './common/errors'; <ins> import { Config } from './config'; import { base64Encode } from './node/buffer'; </ins> // This is the error message that we throw if the login was cancelled for any reason. Extensions // calling `getSession` can handle this error to know that the user cancelled the login.

export interface IGitHubServer { login(scopes: string): Promise<string>; <ins> logout(session: vscode.AuthenticationSession): Promise<void>; </ins> getUserInfo(token: string): Promise<{ id: string; accountName: string }>; sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise<void>; friendlyName: string;

} // TODO@joaomoreno TODO@TylerLeonhardt <ins> private _isNoCorsEnvironment: boolean | undefined; </ins> private async isNoCorsEnvironment(): Promise<boolean> { <ins> if (this._isNoCorsEnvironment !== undefined) { return this._isNoCorsEnvironment; } </ins> const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/dummy`)); <del> return (uri.scheme === 'https' && /^((insiders.)?vscode|github)./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority)); </del> <ins> this._isNoCorsEnvironment = (uri.scheme === 'https' && /^((insiders.)?vscode|github)./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority)); return this._isNoCorsEnvironment; </ins> } public async login(scopes: string): Promise<string> {

throw new Error(userCancelled ? CANCELLATION_ERROR : 'No auth flow succeeded.'); } <ins> public async logout(session: vscode.AuthenticationSession): Promise<void> { this._logger.trace(`Deleting session (${session.id}) from server...`); if (!Config.gitHubClientSecret) { this._logger.warn('No client secret configured for GitHub authentication. The token has been deleted with best effort on this system, but we are unable to delete the token on server without the client secret.'); return; } // Only attempt to delete OAuth tokens. They are always prefixed with `gho_`. // https://docs.github.com/en/rest/apps/oauth-applications#about-oauth-apps-and-oauth-authorizations-of-github-apps if (!session.accessToken.startsWith('gho_')) { this._logger.warn('The token being deleted is not an OAuth token. It has been deleted locally, but we cannot delete it on server.'); return; } if (!isSupportedTarget(this._type, this._ghesUri)) { this._logger.trace('GitHub.com and GitHub hosted GitHub Enterprise are the only options that support deleting tokens on the server. Skipping.'); return; } const authHeader = 'Basic ' + base64Encode(`${Config.gitHubClientId}:${Config.gitHubClientSecret}`); const uri = this.getServerUri(`/applications/${Config.gitHubClientId}/token`); try { // Defined here: https://docs.github.com/en/rest/apps/oauth-applications?apiVersion=2022-11-28#delete-an-app-token const result = await fetching(uri.toString(true), { method: 'DELETE', headers: { Accept: 'application/vnd.github+json', Authorization: authHeader, 'X-GitHub-Api-Version': '2022-11-28', 'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})` }, body: JSON.stringify({ access_token: session.accessToken }), }); if (result.status === 204) { this._logger.trace(`Successfully deleted token from session (${session.id}) from server.`); return; } try { const body = await result.text(); throw new Error(body); } catch (e) { throw new Error(`${result.status} ${result.statusText}`); } } catch (e) { this._logger.warn('Failed to delete token from server.' + e.message ?? e); } } </ins> private getServerUri(path: string = '') { const apiUri = this.baseUri; // github.com and Hosted GitHub Enterprise instances

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ export function base64Encode(text: string): string { return Buffer.from(text, 'binary').toString('base64'); } </ins>