-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Workspace trust: update dialog and status bar item styling and fix command state handling #16877
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| /******************************************************************************** | ||
| * Copyright (C) 2026 EclipseSource and others. | ||
| * | ||
| * This program and the accompanying materials are made available under the | ||
| * terms of the Eclipse Public License v. 2.0 which is available at | ||
| * http://www.eclipse.org/legal/epl-2.0. | ||
| * | ||
| * This Source Code may also be made available under the following Secondary | ||
| * Licenses when the conditions for such availability set forth in the Eclipse | ||
| * Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
| * with the GNU Classpath Exception which is available at | ||
| * https://www.gnu.org/software/classpath/license.html. | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 | ||
| ********************************************************************************/ | ||
|
|
||
| /* Workspace Trust Dialog Styles */ | ||
|
|
||
| .workspace-trust-content { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: calc(var(--theia-ui-padding) * 3); | ||
| padding: calc(var(--theia-ui-padding) * 2); | ||
| } | ||
|
|
||
| .workspace-trust-header { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: calc(var(--theia-ui-padding) * 2); | ||
| } | ||
|
|
||
| .workspace-trust-header i { | ||
| font-size: calc(var(--theia-ui-font-size3) * 2.5) !important; | ||
| color: var(--theia-button-background); | ||
| } | ||
|
|
||
| .workspace-trust-title { | ||
| font-size: var(--theia-ui-font-size2); | ||
| font-weight: 600; | ||
| line-height: var(--theia-content-line-height); | ||
| } | ||
|
|
||
| .workspace-trust-description, | ||
| .workspace-trust-folder { | ||
| margin-left: calc(var(--theia-ui-font-size3) * 2.5 + var(--theia-ui-padding) * 2); | ||
| } | ||
|
|
||
| .workspace-trust-dialog .dialogControl { | ||
| margin-left: calc(var(--theia-ui-font-size3) * 2.5 + var(--theia-ui-padding) * 4); | ||
| padding-bottom: calc(var(--theia-ui-padding) * 4) !important; | ||
| justify-content: flex-start !important; | ||
| } | ||
|
|
||
| .workspace-trust-description { | ||
| color: var(--theia-descriptionForeground); | ||
| line-height: var(--theia-content-line-height); | ||
| } | ||
|
|
||
| .workspace-trust-folder { | ||
| font-family: var(--theia-code-font-family); | ||
| font-size: var(--theia-code-font-size); | ||
| color: var(--theia-foreground); | ||
| background-color: var(--theia-editor-background); | ||
| padding: var(--theia-ui-padding) calc(var(--theia-ui-padding) * 1.5); | ||
| border-radius: 4px; | ||
| } | ||
|
|
||
| .workspace-trust-dialog .dialogControl .theia-button.secondary { | ||
| margin-left: 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // ***************************************************************************** | ||
| // Copyright (C) 2026 EclipseSource and others. | ||
| // | ||
| // This program and the accompanying materials are made available under the | ||
| // terms of the Eclipse Public License v. 2.0 which is available at | ||
| // http://www.eclipse.org/legal/epl-2.0. | ||
| // | ||
| // This Source Code may also be made available under the following Secondary | ||
| // Licenses when the conditions for such availability set forth in the Eclipse | ||
| // Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
| // with the GNU Classpath Exception which is available at | ||
| // https://www.gnu.org/software/classpath/license.html. | ||
| // | ||
| // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 | ||
| // ***************************************************************************** | ||
|
|
||
| import { nls } from '@theia/core'; | ||
| import { codicon } from '@theia/core/lib/browser'; | ||
| import { ReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog'; | ||
| import * as React from '@theia/core/shared/react'; | ||
|
|
||
| export class WorkspaceTrustDialog extends ReactDialog<boolean> { | ||
| protected confirmed = true; | ||
|
|
||
| constructor(protected readonly folderPath: string) { | ||
| super({ | ||
| title: '', | ||
| maxWidth: 500 | ||
| }); | ||
|
|
||
| this.node.classList.add('workspace-trust-dialog'); | ||
|
|
||
| this.appendCloseButton(nls.localizeByDefault("No, I don't trust the authors")); | ||
| this.appendAcceptButton(nls.localizeByDefault('Yes, I trust the authors')); | ||
| this.controlPanel.removeChild(this.errorMessageNode); | ||
| } | ||
|
|
||
| get value(): boolean { | ||
| return this.confirmed; | ||
| } | ||
|
|
||
| protected override handleEscape(): boolean | void { | ||
| this.confirmed = false; | ||
| this.accept(); | ||
| } | ||
|
|
||
| override close(): void { | ||
| this.confirmed = false; | ||
| this.accept(); | ||
| } | ||
|
|
||
| protected render(): React.ReactNode { | ||
| return ( | ||
| <div className="workspace-trust-content"> | ||
| <div className="workspace-trust-header"> | ||
| <i className={codicon('shield')}></i> | ||
| <div className="workspace-trust-title"> | ||
| {nls.localizeByDefault('Do you trust the authors of the files in this folder?')} | ||
| </div> | ||
| </div> | ||
| <div className="workspace-trust-description"> | ||
| {nls.localize( | ||
| 'theia/workspace/trustDialogMessage', | ||
| `The workspace trust feature is not yet fully supported in Theia. | ||
|
|
||
| If you trust the authors of this folder, code inside may be executed. Only trust folders that you trust the contents of.` | ||
| )} | ||
| </div> | ||
| {this.folderPath && ( | ||
| <div className="workspace-trust-folder">{this.folderPath}</div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/front | |
| import { WorkspaceService } from './workspace-service'; | ||
| import { WorkspaceCommands } from './workspace-commands'; | ||
| import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; | ||
| import { WorkspaceTrustDialog } from './workspace-trust-dialog'; | ||
|
|
||
| const STORAGE_TRUSTED = 'trusted'; | ||
| export const WORKSPACE_TRUST_STATUS_BAR_ID = 'workspace-trust-status'; | ||
|
|
@@ -110,6 +111,10 @@ export class WorkspaceTrustService { | |
| } | ||
|
|
||
| getWorkspaceTrust(): Promise<boolean> { | ||
| // Return current trust if already resolved, otherwise wait for initial resolution | ||
| if (this.currentTrust !== undefined) { | ||
| return Promise.resolve(this.currentTrust); | ||
| } | ||
| return this.workspaceTrust.promise; | ||
| } | ||
|
|
||
|
|
@@ -183,18 +188,9 @@ export class WorkspaceTrustService { | |
|
|
||
| this.pendingTrustDialog = new Deferred<boolean>(); | ||
| try { | ||
| const trust = nls.localizeByDefault('Yes, I trust the authors'); | ||
| const dontTrust = nls.localizeByDefault("No, I don't trust the authors"); | ||
| const folderPath = this.workspaceService.workspace?.resource?.path?.toString() ?? ''; | ||
|
|
||
| const dialog = new ConfirmDialog({ | ||
| title: nls.localizeByDefault('Do you trust the authors of the files in this folder?'), | ||
| msg: nls.localize('theia/workspace/trustDialogMessage', | ||
| 'If you trust the authors of this folder, code inside may be executed. Only trust folders that you trust the contents of.') + | ||
| (folderPath ? `\n\n"${folderPath}"` : ''), | ||
| ok: trust, | ||
| cancel: dontTrust, | ||
| }); | ||
| const dialog = new WorkspaceTrustDialog(folderPath); | ||
|
|
||
| const result = await dialog.open(); | ||
| const trusted = result === true; | ||
|
|
@@ -223,6 +219,31 @@ export class WorkspaceTrustService { | |
| } | ||
| } | ||
|
|
||
| async removeFromTrustedFolders(): Promise<void> { | ||
| const workspaceUri = this.workspaceService.workspace?.resource; | ||
| if (!workspaceUri) { | ||
| return; | ||
| } | ||
| if (this.isWorkspaceInTrustedFolders()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is a problem with this method:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As disussed offline, I created a follow up for this part: #16887 |
||
| const currentFolders = this.workspaceTrustPref[WORKSPACE_TRUST_TRUSTED_FOLDERS] || []; | ||
| const caseSensitive = !OS.backend.isWindows; | ||
| const normalizedWorkspaceUri = workspaceUri.normalizePath(); | ||
| const updatedFolders = currentFolders.filter(folder => { | ||
| try { | ||
| const folderUri = new URI(folder).normalizePath(); | ||
| return !normalizedWorkspaceUri.isEqual(folderUri, caseSensitive); | ||
| } catch { | ||
| return true; // Keep invalid URIs | ||
| } | ||
| }); | ||
| await this.preferences.set( | ||
| WORKSPACE_TRUST_TRUSTED_FOLDERS, | ||
| updatedFolders, | ||
| PreferenceScope.User | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| protected isWorkspaceInTrustedFolders(): boolean { | ||
| const workspaceUri = this.workspaceService.workspace?.resource; | ||
| if (!workspaceUri) { | ||
|
|
@@ -326,6 +347,8 @@ export class WorkspaceTrustService { | |
| this.statusBar.setElement(WORKSPACE_TRUST_STATUS_BAR_ID, { | ||
| text: '$(shield) ' + nls.localizeByDefault('Restricted Mode'), | ||
| alignment: StatusBarAlignment.LEFT, | ||
| backgroundColor: 'var(--theia-statusBarItem-prominentBackground)', | ||
| color: 'var(--theia-statusBarItem-prominentForeground)', | ||
| priority: 5000, | ||
| tooltip: nls.localize('theia/workspace/restrictedModeTooltip', | ||
| 'Running in Restricted Mode. Some features are disabled because this folder is not trusted. Click to manage trust settings.'), | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure exactly what would be best for this message. The current state of the feature, as I understand it, is basically that its efficacy is restricted to (some parts of) the plugin system. That means that if all plugins behave nicely and check workspace trust before doing things that could be dangerous, it actually works as intended. But if some plugin automatically runs e.g. some compilation task with a malicious definition without first checking trust, we wouldn't stop it, and I think VSCode would.
So the sense we want to communicate is that the answer to the question is not irrelevant, but saying 'no' may not have all the effects that the user would expect.
But maybe we should just hook up workspace trust in the task and debug systems to stop those systems from running in untrusted workspaces, and then I'll stop hemming and hawing :-).