From b248ec6dfb3952449a6325205fd441091fc33d70 Mon Sep 17 00:00:00 2001 From: kevinjwang1 Date: Tue, 11 Nov 2025 18:37:01 +0000 Subject: [PATCH] Add implementation for setting to disable Github extensions (#12838) --- docs/get-started/configuration.md | 5 ++ packages/cli/src/config/extension-manager.ts | 16 ++++++ packages/cli/src/config/extension.test.ts | 52 ++++++++++++++++++++ packages/cli/src/config/settingsSchema.ts | 9 ++++ schemas/settings.schema.json | 7 +++ 5 files changed, 89 insertions(+) diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index 77ca480aa7..c5ac46b78b 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -490,6 +490,11 @@ their corresponding top-level category object in your `settings.json` file. - **Default:** `false` - **Requires restart:** Yes +- **`security.blockGitExtensions`** (boolean): + - **Description:** Blocks installing and loading extensions from Git. + - **Default:** `false` + - **Requires restart:** Yes + - **`security.folderTrust.enabled`** (boolean): - **Description:** Setting to track whether Folder trust is enabled. - **Default:** `false` diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index 610716cdc3..e6467d9b96 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -128,6 +128,15 @@ export class ExtensionManager extends ExtensionLoader { installMetadata: ExtensionInstallMetadata, previousExtensionConfig?: ExtensionConfig, ): Promise { + if ( + (installMetadata.type === 'git' || + installMetadata.type === 'github-release') && + this.settings.security?.blockGitExtensions + ) { + throw new Error( + 'Installing extensions from remote sources is disallowed by your current settings.', + ); + } const isUpdate = !!previousExtensionConfig; let newExtensionConfig: ExtensionConfig | null = null; let localSourcePath: string | undefined; @@ -449,6 +458,13 @@ export class ExtensionManager extends ExtensionLoader { const installMetadata = loadInstallMetadata(extensionDir); let effectiveExtensionPath = extensionDir; + if ( + (installMetadata?.type === 'git' || + installMetadata?.type === 'github-release') && + this.settings.security?.blockGitExtensions + ) { + return null; + } if (installMetadata?.type === 'link') { effectiveExtensionPath = installMetadata.source; diff --git a/packages/cli/src/config/extension.test.ts b/packages/cli/src/config/extension.test.ts index 3220e763d5..fd2d2adcf2 100644 --- a/packages/cli/src/config/extension.test.ts +++ b/packages/cli/src/config/extension.test.ts @@ -594,6 +594,34 @@ describe('extension tests', () => { consoleSpy.mockRestore(); }); + it('should not load github extensions if blockGitExtensions is set', async () => { + createExtension({ + extensionsDir: userExtensionsDir, + name: 'my-ext', + version: '1.0.0', + installMetadata: { + type: 'git', + source: 'http://somehost.com/foo/bar', + }, + }); + + const blockGitExtensionsSetting = { + security: { + blockGitExtensions: true, + }, + }; + extensionManager = new ExtensionManager({ + workspaceDir: tempWorkspaceDir, + requestConsent: mockRequestConsent, + requestSetting: mockPromptForSettings, + settings: blockGitExtensionsSetting, + }); + const extensions = await extensionManager.loadExtensions(); + const extension = extensions.find((e) => e.name === 'my-ext'); + + expect(extension).toBeUndefined(); + }); + describe('id generation', () => { it('should generate id from source for non-github git urls', async () => { createExtension({ @@ -876,6 +904,30 @@ describe('extension tests', () => { fs.rmSync(targetExtDir, { recursive: true, force: true }); }); + it('should not install a github extension if blockGitExtensions is set', async () => { + const gitUrl = 'https://somehost.com/somerepo.git'; + const blockGitExtensionsSetting = { + security: { + blockGitExtensions: true, + }, + }; + extensionManager = new ExtensionManager({ + workspaceDir: tempWorkspaceDir, + requestConsent: mockRequestConsent, + requestSetting: mockPromptForSettings, + settings: blockGitExtensionsSetting, + }); + await extensionManager.loadExtensions(); + await expect( + extensionManager.installOrUpdateExtension({ + source: gitUrl, + type: 'git', + }), + ).rejects.toThrow( + 'Installing extensions from remote sources is disallowed by your current settings.', + ); + }); + it('should prompt for trust if workspace is not trusted', async () => { vi.mocked(isWorkspaceTrusted).mockReturnValue({ isTrusted: false, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 29c4211557..41331a60f8 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1102,6 +1102,15 @@ const SETTINGS_SCHEMA = { description: 'Disable YOLO mode, even if enabled by a flag.', showInDialog: true, }, + blockGitExtensions: { + type: 'boolean', + label: 'Blocks extensions from Git', + category: 'Security', + requiresRestart: true, + default: false, + description: 'Blocks installing and loading extensions from Git.', + showInDialog: true, + }, folderTrust: { type: 'object', label: 'Folder Trust', diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 7089cec51e..295f7d5215 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -997,6 +997,13 @@ "default": false, "type": "boolean" }, + "blockGitExtensions": { + "title": "Blocks extensions from Git", + "description": "Blocks installing and loading extensions from Git.", + "markdownDescription": "Blocks installing and loading extensions from Git.\n\n- Category: `Security`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, "folderTrust": { "title": "Folder Trust", "description": "Settings for folder trust.",