-
Notifications
You must be signed in to change notification settings - Fork 447
Feat/add workspace trust feature #4738
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
base: main
Are you sure you want to change the base?
Changes from 4 commits
a276714
dba77b2
8e97342
8e75fe5
a3af3b5
bc63495
170eb33
f136fdb
4950b33
922f391
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,142 @@ | ||
| # 工作区信任(受限模式)功能设计文档 | ||
|
|
||
| ## 概述 | ||
|
|
||
| 为 IDE 添加「工作区信任」机制,在首次打开项目时提示用户是否信任该项目目录。若用户选择「受限模式」,则 IDE 仅加载特定的安全插件,防止恶意脚本攻击。 | ||
|
|
||
| ## 背景 | ||
|
|
||
| 当前 IDE 打开项目时会自动加载并激活所有插件(extensions),包括第三方插件。如果项目目录来源不可信,其中的恶意插件脚本可能对用户的系统和数据造成威胁。VS Code 的「Workspace Trust」功能提供了类似的防护机制。 | ||
|
|
||
| ## 核心流程 | ||
|
|
||
| ``` | ||
| 用户打开项目目录 | ||
| │ | ||
| ├── 查询 globalStorage 中是否已记录该目录的信任状态 | ||
| │ │ | ||
| │ ├── 已记录 → 直接使用已有状态 | ||
| │ └── 未记录 → 弹出信任对话框 | ||
| │ │ | ||
| │ ├── 选择「信任」 → 记录为信任状态 | ||
| │ └── 选择「受限模式」 → 记录为受限状态 | ||
| │ | ||
| ├── 根据信任状态决定是否加载插件 | ||
| │ │ | ||
| │ ├── 信任 → 加载所有插件 | ||
| │ └── 受限 → 仅加载白名单插件 | ||
| │ | ||
| └── 根据信任状态更新 UI | ||
| │ | ||
| ├── 信任 → 正常 UI | ||
| └── 受限 → 状态栏显示「受限模式」标识 | ||
| ``` | ||
|
|
||
| ## 数据设计 | ||
|
|
||
| ### 存储方案 | ||
|
|
||
| 使用 `globalStorage`(全局持久化存储)记录每个目录的信任状态。 | ||
|
|
||
| 存储 key 格式:`workspace_trust_state:<workspace_path>` 存储 value:`"trusted"` | `"restricted"` | ||
|
|
||
| 例如: | ||
|
|
||
| ``` | ||
| workspace_trust_state:/Users/mahang/Workspace/ide/core = "trusted" | ||
| workspace_trust_state:/tmp/suspicious-project = "restricted" | ||
| ``` | ||
|
|
||
| ### 存储模块 | ||
|
|
||
| 使用 `STORAGE_NAMESPACE.GLOBAL_EXTENSIONS` 全局存储空间,以 key-value 形式持久化。 | ||
|
|
||
| ### 信任白名单插件 | ||
|
|
||
| 受限模式下仅允许加载以下插件(可通过配置扩展): | ||
|
|
||
| - `vscode.theme-defaults` - 默认主题 | ||
| - `vscode.typescript-language-features` - TypeScript 语言服务 | ||
| - `common-extension` - 通用扩展 | ||
|
|
||
| ## 模块设计 | ||
|
|
||
| ### 新增模块:`workspace-trust` | ||
|
|
||
| 在 `packages/workspace-trust/` 下创建新模块: | ||
|
|
||
| #### 文件结构 | ||
|
|
||
| ``` | ||
| packages/workspace-trust/ | ||
| ├── package.json | ||
| ├── src/ | ||
| │ ├── common/ | ||
| │ │ └── index.ts # 公共常量和类型定义 | ||
| │ └── browser/ | ||
| │ ├── index.ts # 模块入口,BrowserModule 定义 | ||
| │ ├── workspace-trust.service.ts # WorkspaceTrustService | ||
| │ ├── workspace-trust.contribution.ts # ClientAppContribution, 信任对话框 | ||
| │ └── workspace-trust-statusbar.contribution.ts # 状态栏「受限模式」标识 | ||
| ``` | ||
|
|
||
| #### `WorkspaceTrustService` 核心方法 | ||
|
|
||
| | 方法 | 说明 | | ||
| | --- | --- | | ||
| | `getTrustState(workspacePath: string): WorkspaceTrustState \| undefined` | 获取指定工作区路径的信任状态 | | ||
| | `setTrustState(workspacePath: string, state: WorkspaceTrustState): Promise<void>` | 设置并持久化信任状态 | | ||
| | `isRestricted(): boolean` | 当前工作区是否处于受限模式 | | ||
| | `showTrustDialog(): Promise<WorkspaceTrustState>` | 弹出信任选择对话框 | | ||
| | `ensureTrustDecided(): Promise<void>` | 确保当前工作区已有信任决定(如没有则弹窗) | | ||
| | `getAllowedExtensionIds(): string[]` | 受限模式下允许加载的插件 ID 白名单 | | ||
|
|
||
| #### `WorkspaceTrustContribution` | ||
|
|
||
| 实现 `ClientAppContribution` 接口: | ||
|
|
||
| - `initialize()`: 检查当前工作区的信任状态,如果未决定则弹出信任对话框 | ||
| - 此方法在 `ExtensionClientAppContribution.initialize()` 之前执行(通过模块依赖保证) | ||
|
|
||
| #### `WorkspaceTrustStatusbarContribution` | ||
|
|
||
| - 在受限模式下,于状态栏右侧显示「受限模式」标识 | ||
| - 点击可跳转到设置页面解除受限模式 | ||
|
|
||
| ## 集成点 | ||
|
|
||
| ### 1. 信任检查时机 | ||
|
|
||
| `WorkspaceTrustContribution.initialize()` 在 app 启动的 `initialize` 阶段执行。此时: | ||
|
|
||
| 1. 获取当前工作区路径(从 `AppConfig.workspaceDir`) | ||
| 2. 查询 globalStorage 中是否已有信任状态 | ||
| 3. 如果没有,弹出信任对话框,等待用户选择 | ||
| 4. 将用户的决定写入 globalStorage | ||
|
|
||
| ### 2. 插件过滤时机 | ||
|
|
||
| 修改 `ExtensionServiceImpl.initExtensionMetaData()`,在获取到所有插件元数据后: | ||
|
|
||
| 1. 检查 `WorkspaceTrustService.isRestricted()` | ||
| 2. 如果是受限模式,过滤 `extensionMetaDataArr`,仅保留白名单中的插件 | ||
|
|
||
| ### 3. 状态栏显示 | ||
|
|
||
| `WorkspaceTrustStatusbarContribution` 在 `onStart()` 阶段: | ||
|
|
||
| 1. 检查 `WorkspaceTrustService.isRestricted()` | ||
| 2. 如果是受限模式,在状态栏右侧添加「受限模式」元素 | ||
|
|
||
| ## 模块依赖 | ||
|
|
||
| ``` | ||
| workspace-trust → extension (依赖关系:workspace-trust 模块需要在 extension 之前初始化) | ||
| workspace-trust → workspace (获取工作区路径) | ||
| ``` | ||
|
|
||
| ## 后续扩展 | ||
|
|
||
| - 在设置页面添加「管理工作区信任」入口,允许用户修改已有工作区的信任状态 | ||
| - 支持更多类型的白名单插件配置 | ||
| - 受限模式下禁用终端、调试等可能存在安全风险的功能 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "extends": "../tsconfig.base.json", | ||
| "compilerOptions": { | ||
| "rootDir": "../../../packages/workspace-trust/src", | ||
| "outDir": "../../../packages/workspace-trust/lib" | ||
| }, | ||
| "include": ["../../../packages/workspace-trust/src"], | ||
| "exclude": ["../../../packages/workspace-trust/__mocks__"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ import { FileSearchServicePath, IFileSearchService } from '@opensumi/ide-file-se | |
| import { IFileServiceClient } from '@opensumi/ide-file-service'; | ||
| import { IDialogService, IMessageService } from '@opensumi/ide-overlay'; | ||
| import { IWorkspaceService } from '@opensumi/ide-workspace'; | ||
| import { WorkspaceTrustService } from '@opensumi/ide-workspace-trust'; | ||
|
|
||
| import { | ||
| ERestartPolicy, | ||
|
|
@@ -148,6 +149,9 @@ export class ExtensionServiceImpl extends WithEventBus implements ExtensionServi | |
| @Autowired(IFileServiceClient) | ||
| protected fileServiceClient: IFileServiceClient; | ||
|
|
||
| @Autowired(WorkspaceTrustService) | ||
| private readonly workspaceTrustService: WorkspaceTrustService; | ||
|
|
||
| constructor() { | ||
| super(); | ||
|
|
||
|
|
@@ -235,8 +239,22 @@ export class ExtensionServiceImpl extends WithEventBus implements ExtensionServi | |
| await this.updateExtHostData(); | ||
| } | ||
|
|
||
| private isWorkspaceTrustedModuleAvailable() { | ||
| try { | ||
| return !!this.workspaceTrustService; | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| public async activate(): Promise<void> { | ||
| await this.initExtensionMetaData(); | ||
| if (this.isWorkspaceTrustedModuleAvailable()) { | ||
| // Wait for workspace trust decision before filtering extensions | ||
| await this.workspaceTrustService.whenTrustDecided(); | ||
| // Apply trust filter if in restricted mode | ||
| this.extensionMetaDataArr = this.workspaceTrustService.filterExtensions(this.extensionMetaDataArr); | ||
| } | ||
|
Comment on lines
252
to
+263
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.
当 ♻️ 建议方案将 private async runEagerExtensionsContributes() {
await Promise.all([this.contributesService.initialize(), this.sumiContributesService.initialize()]);
-
- this.commandRegistry.beforeExecuteCommand(async (command, args) => {
- await this.activationEventService.fireEvent('onCommand', command);
- return args;
- });
- this.eventBus.fire(new ExtensionDidContributes());
+ }
+
+ private eagerContributesFinalized = false;
+ private finalizeEagerContributes() {
+ if (this.eagerContributesFinalized) {
+ return;
+ }
+ this.eagerContributesFinalized = true;
+ this.commandRegistry.beforeExecuteCommand(async (command, args) => {
+ await this.activationEventService.fireEvent('onCommand', command);
+ return args;
+ });
+ this.eventBus.fire(new ExtensionDidContributes());
}并在 🤖 Prompt for AI Agents |
||
| await this.initExtensionInstanceData(); | ||
| await this.runEagerExtensionsContributes(); | ||
| // update nls config by extensions | ||
|
|
||
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.
externalSearchBehavior 模式下仍执行内部过滤,和新语义不一致
Line 305 已启用“外部托管”语义,但 Line 484-507 仍会在输入时调用
filterOption。建议在 external 模式直接跳过内部过滤,避免契约偏差和额外开销。建议修复
Also applies to: 484-507
🤖 Prompt for AI Agents