From 352e109933ca1bd76f273f75dc6b1b968c68e477 Mon Sep 17 00:00:00 2001 From: Sachin Hulyalkar Date: Mon, 27 Jul 2026 19:44:49 +0000 Subject: [PATCH] fix: restrict copilot debug endpoint overrides to user scope; register backported iframe parentOrigin and mermaid render hardening Add a quilt patch that reads the Copilot advanced debug CAPI/proxy URL override settings only from user (global) scope so a workspace .vscode/settings.json cannot redirect requests. Register the existing web worker extension host iframe parentOrigin hardening (web-server/webview.diff) and document the notebook mermaid render finding, which has no equivalent vulnerable sink in this branch's shipped source, in backported-patches.json. --- patches/backported-patches.json | 40 +++++ ...fix-copilot-debug-override-user-scope.diff | 164 ++++++++++++++++++ patches/sagemaker.series | 1 + patches/web-embedded-with-terminal.series | 1 + patches/web-embedded.series | 1 + patches/web-server.series | 1 + 6 files changed, 208 insertions(+) create mode 100644 patches/common/fix-copilot-debug-override-user-scope.diff diff --git a/patches/backported-patches.json b/patches/backported-patches.json index 9456b19..e8afbd4 100644 --- a/patches/backported-patches.json +++ b/patches/backported-patches.json @@ -48,5 +48,45 @@ "patch_path": "N/A", "link": "https://github.com/advisories/GHSA-c82g-9gj4-hxp2", "note": "GitHub Copilot path traversal - Copilot not present in Code Editor" + }, + { + "finding_id": "CVE-2026-47282", + "affected_versions": "< 1.128.1", + "patch_path": "patches/common/fix-copilot-debug-override-user-scope.diff", + "link": "https://github.com/microsoft/vscode/commit/f05bcd1aa29cff28a62fe137b4a16fec9393f31b" + }, + { + "finding_id": "GHSA-wr9x-42j2-jvh3", + "affected_versions": "< 1.128.1", + "patch_path": "patches/common/fix-copilot-debug-override-user-scope.diff", + "link": "https://github.com/microsoft/vscode/commit/f05bcd1aa29cff28a62fe137b4a16fec9393f31b" + }, + { + "finding_id": "CVE-2026-57102", + "affected_versions": "< 1.128.1", + "patch_path": "patches/web-server/webview.diff", + "link": "https://github.com/microsoft/vscode/commit/236fa7d8ea86f0f4261df38e1a17b3c7e7002bd0", + "note": "Attacker-controlled parentOrigin RCE in the web worker extension host iframe. Already mitigated by the existing web-server/webview.diff: the iframe script forces parentOrigin = window.origin and only reads the searchParams parentOrigin after the hostname-validation branch, so the non-validated (same-origin) path never trusts an attacker-supplied parentOrigin. Equivalent to upstream's explicit rejection; not double-applied." + }, + { + "finding_id": "GHSA-v282-cxqj-xgj4", + "affected_versions": "< 1.128.1", + "patch_path": "patches/web-server/webview.diff", + "link": "https://github.com/microsoft/vscode/commit/236fa7d8ea86f0f4261df38e1a17b3c7e7002bd0", + "note": "Attacker-controlled parentOrigin RCE in the web worker extension host iframe. Already mitigated by the existing web-server/webview.diff: the iframe script forces parentOrigin = window.origin and only reads the searchParams parentOrigin after the hostname-validation branch, so the non-validated (same-origin) path never trusts an attacker-supplied parentOrigin. Equivalent to upstream's explicit rejection; not double-applied." + }, + { + "finding_id": "CVE-2026-57101", + "affected_versions": "< 1.128.1", + "patch_path": "N/A", + "link": "https://github.com/microsoft/vscode/commit/bc2d56c6f8da22a4bd31f741fcb034208770f158", + "note": "Notebook Restricted-Mode bypass via mermaid render. Upstream vulnerable file extensions/mermaid-markdown-features/preview-src/notebook/index.ts (the 'temp.innerHTML = result' sink and renderMermaidBlocksInElement) does not exist in this branch's shipped source. The only mermaid extension here, mermaid-chat-features, renders diagrams via escapeHtmlText() into a
 and uses textContent (chat-webview-src/mermaidWebview.ts) inside a sandboxed webview with strict nonce CSP - no innerHTML of untrusted content. The markdown-language-features notebook renderer already sanitizes untrusted HTML with DOMPurify. No equivalent vulnerable innerHTML/insertAdjacentHTML sink for untrusted render output exists."
+  },
+  {
+    "finding_id": "GHSA-9mw4-h26x-gfxw",
+    "affected_versions": "< 1.128.1",
+    "patch_path": "N/A",
+    "link": "https://github.com/microsoft/vscode/commit/bc2d56c6f8da22a4bd31f741fcb034208770f158",
+    "note": "Notebook Restricted-Mode bypass via mermaid render. Upstream vulnerable file extensions/mermaid-markdown-features/preview-src/notebook/index.ts (the 'temp.innerHTML = result' sink and renderMermaidBlocksInElement) does not exist in this branch's shipped source. The only mermaid extension here, mermaid-chat-features, renders diagrams via escapeHtmlText() into a 
 and uses textContent (chat-webview-src/mermaidWebview.ts) inside a sandboxed webview with strict nonce CSP - no innerHTML of untrusted content. The markdown-language-features notebook renderer already sanitizes untrusted HTML with DOMPurify. No equivalent vulnerable innerHTML/insertAdjacentHTML sink for untrusted render output exists."
   }
 ]
diff --git a/patches/common/fix-copilot-debug-override-user-scope.diff b/patches/common/fix-copilot-debug-override-user-scope.diff
new file mode 100644
index 0000000..66e6d1f
--- /dev/null
+++ b/patches/common/fix-copilot-debug-override-user-scope.diff
@@ -0,0 +1,164 @@
+Restrict Copilot debug endpoint override settings to user (global) scope.
+Reads the advanced debug CAPI/proxy URL override settings only from the user
+(global) configuration, ignoring workspace and folder values so a malicious
+workspace .vscode/settings.json cannot redirect requests to an attacker host.
+Remove when Code-OSS is updated to >= 1.128.1.
+
+@backported: https://github.com/microsoft/vscode/commit/f05bcd1aa29cff28a62fe137b4a16fec9393f31b
+@finding-id: CVE-2026-47282 GHSA-wr9x-42j2-jvh3
+Index: b/extensions/copilot/src/extension/completions-core/vscode-node/extension/src/config.ts
+===================================================================
+--- a/extensions/copilot/src/extension/completions-core/vscode-node/extension/src/config.ts
++++ b/extensions/copilot/src/extension/completions-core/vscode-node/extension/src/config.ts
+@@ -15,7 +15,8 @@ import {
+ 	getOptionalConfigDefaultForKey,
+ 	ICompletionsConfigProvider,
+ 	ICompletionsEditorAndPluginInfo,
+-	packageJson
++	packageJson,
++	userScopeOnlyKeys
+ } from '../../lib/src/config';
+ import { CopilotConfigPrefix } from '../../lib/src/constants';
+ import { Logger } from '../../lib/src/logger';
+@@ -44,13 +45,38 @@ export class VSCodeConfigProvider extend
+ 	}
+ 
+ 	override getConfig(key: ConfigKeyType): T {
++		if (userScopeOnlyKeys.has(key)) {
++			return this._getUserScopeValue(key) ?? getConfigDefaultForKey(key);
++		}
+ 		return getConfigKeyRecursively(this.config, key) ?? getConfigDefaultForKey(key);
+ 	}
+ 
+ 	override getOptionalConfig(key: ConfigKeyType): T | undefined {
++		if (userScopeOnlyKeys.has(key)) {
++			return this._getUserScopeValue(key) ?? getOptionalConfigDefaultForKey(key);
++		}
+ 		return getConfigKeyRecursively(this.config, key) ?? getOptionalConfigDefaultForKey(key);
+ 	}
+ 
++	private _getUserScopeValue(key: ConfigKeyType): T | undefined {
++		// Try the flat key path directly
++		const inspected = this.config.inspect(key);
++		if (inspected?.globalValue !== undefined) {
++			return inspected.globalValue;
++		}
++		// Handle object-style settings (e.g., "advanced": { "debug.overrideCapiUrl": "..." })
++		const firstDot = key.indexOf('.');
++		if (firstDot > 0) {
++			const parentKey = key.substring(0, firstDot);
++			const subKey = key.substring(firstDot + 1);
++			const parentInspect = this.config.inspect>(parentKey);
++			if (parentInspect?.globalValue && typeof parentInspect.globalValue === 'object') {
++				return getConfigKeyRecursively(parentInspect.globalValue as Record, subKey);
++			}
++		}
++		return undefined;
++	}
++
+ 	// Dumps config settings defined in the extension json
+ 	override dumpForTelemetry(): { [key: string]: string } {
+ 		return {};
+Index: b/extensions/copilot/src/extension/completions-core/vscode-node/lib/src/config.ts
+===================================================================
+--- a/extensions/copilot/src/extension/completions-core/vscode-node/lib/src/config.ts
++++ b/extensions/copilot/src/extension/completions-core/vscode-node/lib/src/config.ts
+@@ -75,6 +75,21 @@ export const ConfigKey = {
+ 	UseSplitContextPrompt: 'internal.useSplitContextPrompt',
+ };
+ 
++/**
++ * Settings that must only be read from user (global) scope, ignoring workspace/folder values.
++ * Prevents workspace-level override attacks via malicious .vscode/settings.json.
++ */
++export const userScopeOnlyKeys = new Set([
++	ConfigKey.DebugOverrideCapiUrl,
++	ConfigKey.DebugOverrideCapiUrlLegacy,
++	ConfigKey.DebugTestOverrideCapiUrl,
++	ConfigKey.DebugTestOverrideCapiUrlLegacy,
++	ConfigKey.DebugOverrideProxyUrl,
++	ConfigKey.DebugOverrideProxyUrlLegacy,
++	ConfigKey.DebugTestOverrideProxyUrl,
++	ConfigKey.DebugTestOverrideProxyUrlLegacy,
++]);
++
+ export type ConfigKeyType = string;
+ 
+ // How to determine where to terminate the completion to the current block.
+Index: b/extensions/copilot/src/platform/configuration/common/configurationService.ts
+===================================================================
+--- a/extensions/copilot/src/platform/configuration/common/configurationService.ts
++++ b/extensions/copilot/src/platform/configuration/common/configurationService.ts
+@@ -375,6 +375,12 @@ export const enum ConfigType {
+ export interface ConfigOptions {
+ 	readonly oldKey?: string;
+ 	readonly valueIgnoredForExternals?: boolean;
++	/**
++	 * When true, only reads from user (global) scope, ignoring workspace and folder values.
++	 * Use for security-sensitive settings (e.g., API endpoint overrides) that must not be
++	 * controllable via a workspace's .vscode/settings.json.
++	 */
++	readonly userScopeOnly?: boolean;
+ }
+ 
+ export interface Config extends BaseConfig {
+@@ -573,8 +579,8 @@ export namespace ConfigKey {
+ 	*/
+ 	export namespace Shared {
+ 		/** Allows for overriding the base domain we use for making requests to the CAPI. This helps CAPI devs develop against a local instance. */
+-		export const DebugOverrideProxyUrl = defineSetting('advanced.debug.overrideProxyUrl', ConfigType.Simple, undefined);
+-		export const DebugOverrideCAPIUrl = defineSetting('advanced.debug.overrideCapiUrl', ConfigType.Simple, undefined);
++		export const DebugOverrideProxyUrl = defineSetting('advanced.debug.overrideProxyUrl', ConfigType.Simple, undefined, undefined, { userScopeOnly: true });
++		export const DebugOverrideCAPIUrl = defineSetting('advanced.debug.overrideCapiUrl', ConfigType.Simple, undefined, undefined, { userScopeOnly: true });
+ 		export const DebugUseNodeFetchFetcher = defineSetting('advanced.debug.useNodeFetchFetcher', ConfigType.Simple, true);
+ 		export const DebugUseNodeFetcher = defineSetting('advanced.debug.useNodeFetcher', ConfigType.Simple, false);
+ 		export const DebugUseElectronFetcher = defineSetting('advanced.debug.useElectronFetcher', ConfigType.Simple, true);
+Index: b/extensions/copilot/src/platform/configuration/vscode/configurationServiceImpl.ts
+===================================================================
+--- a/extensions/copilot/src/platform/configuration/vscode/configurationServiceImpl.ts
++++ b/extensions/copilot/src/platform/configuration/vscode/configurationServiceImpl.ts
+@@ -57,7 +57,9 @@ export class ConfigurationServiceImpl ex
+ 		const config = scope === undefined ? this.config : vscode.workspace.getConfiguration(CopilotConfigPrefix, scope);
+ 
+ 		let configuredValue: T | undefined;
+-		if (key.advancedSubKey) {
++		if (key.options?.userScopeOnly) {
++			configuredValue = this._getUserScopeValue(config, key);
++		} else if (key.advancedSubKey) {
+ 			// This is a `github.copilot.advanced.*` setting
+ 
+ 			// First, let's try to read it using the flat style
+@@ -104,6 +106,33 @@ export class ConfigurationServiceImpl ex
+ 		return value.content;
+ 	}
+ 
++	private _getUserScopeValue(config: WorkspaceConfiguration, key: Config): T | undefined {
++		if (key.advancedSubKey) {
++			// Flat style: e.g., "github.copilot.advanced.debug.overrideCapiUrl": "..."
++			const flatInspect = config.inspect(key.id);
++			if (flatInspect?.globalValue !== undefined) {
++				return flatInspect.globalValue;
++			}
++			// Object style: e.g., "github.copilot.advanced": { "debug.overrideCapiUrl": "..." }
++			const advancedInspect = config.inspect>('advanced');
++			return advancedInspect?.globalValue?.[key.advancedSubKey];
++		}
++
++		const inspected = config.inspect(key.id);
++		if (inspected?.globalValue !== undefined) {
++			return inspected.globalValue;
++		}
++
++		if (key.oldId) {
++			const oldInspected = config.inspect(key.oldId);
++			if (oldInspected?.globalValue !== undefined) {
++				return oldInspected.globalValue;
++			}
++		}
++
++		return undefined;
++	}
++
+ 	inspectConfig(key: BaseConfig, scope?: vscode.ConfigurationScope): InspectConfigResult | undefined {
+ 		if (key.options?.valueIgnoredForExternals && !this._isInternal) {
+ 			return { defaultValue: this.getDefaultValue(key) };
diff --git a/patches/sagemaker.series b/patches/sagemaker.series
index 52f1e02..ccfc8bf 100644
--- a/patches/sagemaker.series
+++ b/patches/sagemaker.series
@@ -71,3 +71,4 @@ common/fix-remote-hosts-loopback.diff
 common/finding-override-undici.diff
 common/finding-override-form-data.diff
 common/finding-override-tar.diff
+common/fix-copilot-debug-override-user-scope.diff
diff --git a/patches/web-embedded-with-terminal.series b/patches/web-embedded-with-terminal.series
index 2e0e0e5..4ea4488 100644
--- a/patches/web-embedded-with-terminal.series
+++ b/patches/web-embedded-with-terminal.series
@@ -67,3 +67,4 @@ common/fix-snippets-path-traversal.diff
 common/fix-remote-hosts-loopback.diff
 common/finding-override-undici.diff
 common/finding-override-form-data.diff
+common/fix-copilot-debug-override-user-scope.diff
diff --git a/patches/web-embedded.series b/patches/web-embedded.series
index be679f6..721ea56 100644
--- a/patches/web-embedded.series
+++ b/patches/web-embedded.series
@@ -70,3 +70,4 @@ common/fix-snippets-path-traversal.diff
 common/fix-remote-hosts-loopback.diff
 common/finding-override-undici.diff
 common/finding-override-form-data.diff
+common/fix-copilot-debug-override-user-scope.diff
diff --git a/patches/web-server.series b/patches/web-server.series
index 00fd5ef..34bdd22 100644
--- a/patches/web-server.series
+++ b/patches/web-server.series
@@ -49,3 +49,4 @@ common/fix-snippets-path-traversal.diff
 common/fix-remote-hosts-loopback.diff
 common/finding-override-undici.diff
 common/finding-override-form-data.diff
+common/fix-copilot-debug-override-user-scope.diff