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
3 changes: 2 additions & 1 deletion extensions/git/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"tabInputMultiDiff",
"tabInputTextMerge",
"textEditorDiffInformation",
"timeline"
"timeline",
"workspaceTrust"
],
"categories": [
"Other"
Expand Down
23 changes: 10 additions & 13 deletions extensions/git/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder, ThemeIcon } from 'vscode';
import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder, ThemeIcon, ResourceTrustRequestOptions } from 'vscode';
import TelemetryReporter from '@vscode/extension-telemetry';
import { IRepositoryResolver, Repository, RepositoryState } from './repository';
import { memoize, sequentialize, debounce } from './decorators';
Expand Down Expand Up @@ -588,18 +588,15 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
return;
}

if (!workspace.isTrusted) {
// Check if the folder is a bare repo: if it has a file named HEAD && `rev-parse --show -cdup` is empty
try {
fs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK);
const result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']);
if (result.stderr.trim() === '' && result.stdout.trim() === '') {
this.logger.trace(`[Model][openRepository] Bare repository: ${repoPath}`);
return;
}
} catch {
// If this throw, we should be good to open the repo (e.g. HEAD doesn't exist)
}
// Repository trust check
const result = await workspace.requestResourceTrust({
message: l10n.t('You are opening a repository from a location that is not trusted. Do you trust the authors of the files in the repository you are opening?'),
uri: Uri.file(repoPath),
} satisfies ResourceTrustRequestOptions);

if (!result) {
this.logger.trace(`[Model][openRepository] Repository folder is not trusted: ${repoPath}`);
return;
}
Comment thread
lszomoru marked this conversation as resolved.

try {
Expand Down
1 change: 1 addition & 0 deletions extensions/git/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts",
"../../src/vscode-dts/vscode.proposed.textEditorDiffInformation.d.ts",
"../../src/vscode-dts/vscode.proposed.timeline.d.ts",
"../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts",
"../types/lib.textEncoder.d.ts"
]
}
9 changes: 9 additions & 0 deletions src/vs/platform/workspace/common/workspaceTrust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export interface WorkspaceTrustRequestButton {
readonly type: 'ContinueWithTrust' | 'ContinueWithoutTrust' | 'Manage' | 'Cancel';
}

export interface ResourceTrustRequestOptions {
readonly uri: URI;
readonly message?: string;
}

export interface WorkspaceTrustRequestOptions {
readonly buttons?: WorkspaceTrustRequestButton[];
readonly message?: string;
Expand Down Expand Up @@ -75,10 +80,14 @@ export interface IWorkspaceTrustRequestService {
readonly onDidInitiateOpenFilesTrustRequest: Event<void>;
readonly onDidInitiateWorkspaceTrustRequest: Event<WorkspaceTrustRequestOptions | undefined>;
readonly onDidInitiateWorkspaceTrustRequestOnStartup: Event<void>;
readonly onDidInitiateResourcesTrustRequest: Event<ResourceTrustRequestOptions>;

completeOpenFilesTrustRequest(result: WorkspaceTrustUriResponse, saveResponse?: boolean): Promise<void>;
requestOpenFilesTrust(openFiles: URI[]): Promise<WorkspaceTrustUriResponse>;

completeResourcesTrustRequest(uri: URI, result: WorkspaceTrustUriResponse): Promise<void>;
requestResourcesTrust(options: ResourceTrustRequestOptions): Promise<boolean | undefined>;

cancelWorkspaceTrustRequest(): void;
completeWorkspaceTrustRequest(trusted?: boolean): Promise<void>;
requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined>;
Expand Down
9 changes: 7 additions & 2 deletions src/vs/workbench/api/browser/mainThreadWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ import { IInstantiationService } from '../../../platform/instantiation/common/in
import { ILabelService } from '../../../platform/label/common/label.js';
import { INotificationService } from '../../../platform/notification/common/notification.js';
import { AuthInfo, Credentials, IRequestService } from '../../../platform/request/common/request.js';
import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from '../../../platform/workspace/common/workspaceTrust.js';
import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, ResourceTrustRequestOptions } from '../../../platform/workspace/common/workspaceTrust.js';
import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace, WorkspaceFolder } from '../../../platform/workspace/common/workspace.js';
import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
import { checkGlobFileExists } from '../../services/extensions/common/workspaceContains.js';
import { IFileQueryBuilderOptions, ITextQueryBuilderOptions, QueryBuilder } from '../../services/search/common/queryBuilder.js';
import { IEditorService, ISaveEditorsResult } from '../../services/editor/common/editorService.js';
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from '../../services/search/common/search.js';
import { IWorkspaceEditingService } from '../../services/workspaces/common/workspaceEditing.js';
import { ExtHostContext, ExtHostWorkspaceShape, ITextSearchComplete, IWorkspaceData, MainContext, MainThreadWorkspaceShape } from '../common/extHost.protocol.js';
import { ExtHostContext, ExtHostWorkspaceShape, ITextSearchComplete, IWorkspaceData, MainContext, MainThreadWorkspaceShape, ResourceTrustRequestOptionsDto } from '../common/extHost.protocol.js';
import { IEditSessionIdentityService } from '../../../platform/workspace/common/editSessions.js';
import { EditorResourceAccessor, SaveReason, SideBySideEditor } from '../../common/editor.js';
import { coalesce } from '../../../base/common/arrays.js';
Expand Down Expand Up @@ -241,6 +241,11 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {

// --- trust ---

$requestResourceTrust(optionsDto: ResourceTrustRequestOptionsDto): Promise<boolean | undefined> {
const options = { ...optionsDto, uri: URI.revive(optionsDto.uri) } satisfies ResourceTrustRequestOptions;
return this._workspaceTrustRequestService.requestResourcesTrust(options);
}

$requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined> {
return this._workspaceTrustRequestService.requestWorkspaceTrust(options);
}
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
get isTrusted() {
return extHostWorkspace.trusted;
},
requestResourceTrust: (options: vscode.ResourceTrustRequestOptions) => {
checkProposedApiEnabled(extension, 'workspaceTrust');
return extHostWorkspace.requestResourceTrust(options);
},
requestWorkspaceTrust: (options?: vscode.WorkspaceTrustRequestOptions) => {
checkProposedApiEnabled(extension, 'workspaceTrust');
return extHostWorkspace.requestWorkspaceTrust(options);
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,11 @@ export interface ITextSearchComplete {
message?: TextSearchCompleteMessage | TextSearchCompleteMessage[];
}

export interface ResourceTrustRequestOptionsDto {
readonly uri: UriComponents;
readonly message?: string;
}

export interface MainThreadWorkspaceShape extends IDisposable {
$startFileSearch(includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<UriComponents[] | null>;
$startTextSearch(query: search.IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null>;
Expand All @@ -1606,6 +1611,7 @@ export interface MainThreadWorkspaceShape extends IDisposable {
$lookupAuthorization(authInfo: AuthInfo): Promise<Credentials | undefined>;
$lookupKerberosAuthorization(url: string): Promise<string | undefined>;
$loadCertificates(): Promise<string[]>;
$requestResourceTrust(options: ResourceTrustRequestOptionsDto): Promise<boolean | undefined>;
$requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined>;
$registerEditSessionIdentityProvider(handle: number, scheme: string): void;
$unregisterEditSessionIdentityProvider(handle: number): void;
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/api/common/extHostWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
return this._trusted;
}

requestResourceTrust(options: vscode.ResourceTrustRequestOptions): Promise<boolean | undefined> {
return this._proxy.$requestResourceTrust(options);
}

requestWorkspaceTrust(options?: vscode.WorkspaceTrustRequestOptions): Promise<boolean | undefined> {
return this._proxy.$requestWorkspaceTrust(options);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,36 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
});
}));

// Resources trust request
this._register(this.workspaceTrustRequestService.onDidInitiateResourcesTrustRequest(async (options) => {
await this.workspaceTrustManagementService.workspaceResolved;

// Details
const markdownDetails = [
options?.message ?? localize('resourcesTrustDetails', "You are trying to open an untrusted folder. Do you trust the authors of this content?"),
localize('resourcesTrustLearnMore', "If you don't trust the authors of these files, we recommend not continuing as the files may be malicious. See [our docs](https://aka.ms/vscode-workspace-trust) to learn more.")
];

// Dialog
await this.dialogService.prompt<void>({
type: Severity.Info,
message: localize('resourcesTrustMessage', "Do you trust the authors of the files in this folder?"),
buttons: [
{
label: localize({ key: 'trustResources', comment: ['&& denotes a mnemonic'] }, "&&Trust Folder & Continue"),
run: () => this.workspaceTrustRequestService.completeResourcesTrustRequest(options.uri, WorkspaceTrustUriResponse.Open)
}
],
cancelButton: {
run: () => this.workspaceTrustRequestService.completeResourcesTrustRequest(options.uri, WorkspaceTrustUriResponse.Cancel)
},
custom: {
icon: Codicon.shield,
markdownDetails: markdownDetails.map(md => { return { markdown: new MarkdownString(md) }; })
}
});
}));

// Workspace trust request
this._register(this.workspaceTrustRequestService.onDidInitiateWorkspaceTrustRequest(async requestOptions => {
await this.workspaceTrustManagementService.workspaceResolved;
Expand Down
51 changes: 50 additions & 1 deletion src/vs/workbench/services/workspaces/common/workspaceTrust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ import { getRemoteAuthority } from '../../../../platform/remote/common/remoteHos
import { isVirtualResource } from '../../../../platform/workspace/common/virtualWorkspace.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { ISingleFolderWorkspaceIdentifier, isSavedWorkspace, isSingleFolderWorkspaceIdentifier, isTemporaryWorkspace, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, toWorkspaceIdentifier, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustInfo, IWorkspaceTrustUriInfo, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, WorkspaceTrustUriResponse, IWorkspaceTrustEnablementService } from '../../../../platform/workspace/common/workspaceTrust.js';
import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustInfo, IWorkspaceTrustUriInfo, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, WorkspaceTrustUriResponse, IWorkspaceTrustEnablementService, ResourceTrustRequestOptions } from '../../../../platform/workspace/common/workspaceTrust.js';
import { Memento } from '../../../common/memento.js';
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
import { isEqualAuthority } from '../../../../base/common/resources.js';
import { isWeb } from '../../../../base/common/platform.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { promiseWithResolvers } from '../../../../base/common/async.js';
import { ResourceMap } from '../../../../base/common/map.js';

export const WORKSPACE_TRUST_ENABLED = 'security.workspace.trust.enabled';
export const WORKSPACE_TRUST_STARTUP_PROMPT = 'security.workspace.trust.startupPrompt';
Expand Down Expand Up @@ -660,12 +661,18 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa
private _openFilesTrustRequestPromise?: Promise<WorkspaceTrustUriResponse>;
private _openFilesTrustRequestResolver?: (response: WorkspaceTrustUriResponse) => void;

private readonly _resourcesTrustRequestPromises = new ResourceMap<Promise<boolean | undefined>>();
private readonly _resourcesTrustRequestResolvers = new ResourceMap<(trusted: boolean | undefined) => void>();

private _workspaceTrustRequestPromise?: Promise<boolean | undefined>;
private _workspaceTrustRequestResolver?: (trusted: boolean | undefined) => void;

private readonly _onDidInitiateOpenFilesTrustRequest = this._register(new Emitter<void>());
readonly onDidInitiateOpenFilesTrustRequest = this._onDidInitiateOpenFilesTrustRequest.event;

private readonly _onDidInitiateResourcesTrustRequest = this._register(new Emitter<ResourceTrustRequestOptions>());
readonly onDidInitiateResourcesTrustRequest = this._onDidInitiateResourcesTrustRequest.event;

private readonly _onDidInitiateWorkspaceTrustRequest = this._register(new Emitter<WorkspaceTrustRequestOptions | undefined>());
readonly onDidInitiateWorkspaceTrustRequest = this._onDidInitiateWorkspaceTrustRequest.event;

Expand Down Expand Up @@ -761,6 +768,48 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa

//#endregion

//#region Resource(s) trust request

async completeResourcesTrustRequest(uri: URI, result: WorkspaceTrustUriResponse): Promise<void> {
const resolver = this._resourcesTrustRequestResolvers.get(uri);
if (!resolver) {
return;
}

const trusted = result === WorkspaceTrustUriResponse.Open;
await this.workspaceTrustManagementService.setUrisTrust([uri], trusted);

resolver(trusted);

this._resourcesTrustRequestResolvers.delete(uri);
this._resourcesTrustRequestPromises.delete(uri);
}

async requestResourcesTrust(options: ResourceTrustRequestOptions): Promise<boolean | undefined> {
// Check if all resources are already trusted
const resourcesTrustInfo = await this.workspaceTrustManagementService.getUriTrustInfo(options.uri);
if (resourcesTrustInfo.trusted) {
return true;
}

// Return existing promise for this URI
const existingPromise = this._resourcesTrustRequestPromises.get(options.uri);
if (existingPromise) {
return existingPromise;
}

// Create a new promise for this URI
const promise = new Promise<boolean | undefined>(resolve => {
this._resourcesTrustRequestResolvers.set(options.uri, resolve);
});
this._resourcesTrustRequestPromises.set(options.uri, promise);
this._onDidInitiateResourcesTrustRequest.fire(options);

return promise;
}

Comment thread
lszomoru marked this conversation as resolved.
//#endregion

//#region Workspace trust request

private resolveWorkspaceTrustRequest(trusted?: boolean): void {
Expand Down
13 changes: 12 additions & 1 deletion src/vs/workbench/test/common/workbenchTestServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { IProgress, IProgressStep } from '../../../platform/progress/common/prog
import { InMemoryStorageService, WillSaveStateReason } from '../../../platform/storage/common/storage.js';
import { toUserDataProfile } from '../../../platform/userDataProfile/common/userDataProfile.js';
import { ISingleFolderWorkspaceIdentifier, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, IWorkspaceFoldersWillChangeEvent, IWorkspaceIdentifier, WorkbenchState, Workspace } from '../../../platform/workspace/common/workspace.js';
import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, IWorkspaceTrustUriInfo, WorkspaceTrustRequestOptions, WorkspaceTrustUriResponse } from '../../../platform/workspace/common/workspaceTrust.js';
import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, IWorkspaceTrustUriInfo, ResourceTrustRequestOptions, WorkspaceTrustRequestOptions, WorkspaceTrustUriResponse } from '../../../platform/workspace/common/workspaceTrust.js';
import { TestWorkspace } from '../../../platform/workspace/test/common/testWorkspace.js';
import { GroupIdentifier, IRevertOptions, ISaveOptions, SaveReason } from '../../common/editor.js';
import { EditorInput } from '../../common/editor/editorInput.js';
Expand Down Expand Up @@ -451,6 +451,9 @@ export class TestWorkspaceTrustRequestService extends Disposable implements IWor
private readonly _onDidInitiateOpenFilesTrustRequest = this._register(new Emitter<void>());
readonly onDidInitiateOpenFilesTrustRequest = this._onDidInitiateOpenFilesTrustRequest.event;

private readonly _onDidInitiateResourcesTrustRequest = this._register(new Emitter<ResourceTrustRequestOptions>());
readonly onDidInitiateResourcesTrustRequest = this._onDidInitiateResourcesTrustRequest.event;

private readonly _onDidInitiateWorkspaceTrustRequest = this._register(new Emitter<WorkspaceTrustRequestOptions>());
readonly onDidInitiateWorkspaceTrustRequest = this._onDidInitiateWorkspaceTrustRequest.event;

Expand All @@ -473,6 +476,14 @@ export class TestWorkspaceTrustRequestService extends Disposable implements IWor
throw new Error('Method not implemented.');
}

async completeResourcesTrustRequest(uri: URI, result: WorkspaceTrustUriResponse): Promise<void> {
throw new Error('Method not implemented.');
}

async requestResourcesTrust(options: ResourceTrustRequestOptions): Promise<boolean | undefined> {
return this._trusted;
}

cancelWorkspaceTrustRequest(): void {
throw new Error('Method not implemented.');
}
Expand Down
20 changes: 20 additions & 0 deletions src/vscode-dts/vscode.proposed.workspaceTrust.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ declare module 'vscode' {

// https://github.com/microsoft/vscode/issues/120173

export interface ResourceTrustRequestOptions {
/**
* An resource related to the trust request.
*/
readonly uri: Uri;

/**
* Custom message describing the user action that requires resource
* trust. If omitted, a generic message will be displayed in the resource
* trust request dialog.
*/
readonly message?: string;
}

/**
* The object describing the properties of the workspace trust request
*/
Expand All @@ -20,6 +34,12 @@ declare module 'vscode' {
}

export namespace workspace {
/**
* Prompt the user to chose whether to trust the specified resource (ex: folder)
* @param options Object describing the properties of the resource trust request.
*/
export function requestResourceTrust(options: ResourceTrustRequestOptions): Thenable<boolean | undefined>;

/**
* Prompt the user to chose whether to trust the current workspace
* @param options Optional object describing the properties of the
Expand Down
Loading