Introduction
Workspace Trust is the control in Visual Studio Code that is supposed to keep a folder you have just opened from changing how the editor behaves until you explicitly trust it. CVE-2026-121665 shows that this boundary was only enforced for settings VS Code had a schema for. An untrusted workspace could still steer restricted behavior, such as terminal profiles and experiment treatment values, through keys the trust filter never looked at.
Microsoft rates the issue Important, and the CVE record carries a CVSS 3.1 base score of 8.8 (High). The fix shipped in Visual Studio Code 1.145.3 on Patch Tuesday, November 10, 2026. At publication, Microsoft reported the flaw as not publicly disclosed and not exploited, with exploitation rated Less Likely. The Threat Radar entry lists no public exploit code in Metasploit or Exploit DB, and we did not find the CVE in the CISA Known Exploited Vulnerabilities Catalog during our review.
Technical information
Root cause: a trust filter that only knew registered settings
The weakness is classified as CWE-501, Trust Boundary Violation. MITRE describes this class as mixing trusted and untrusted data in the same structure, so that unvalidated data ends up treated as trustworthy, with protection mechanism bypass as a possible consequence. In VS Code, the structure in question is the merged configuration model.
Workspace Trust is implemented as a settings filter. When a folder is open in Restricted Mode, VS Code drops workspace values for any setting registered as restricted, using the skipRestricted parse option. But this check only works if the setting has a registered schema. GitHub advisory GHSA-c367-h9jc-pjr7, titled Workspace Trust Bypass via Unregistered Experiment Override Settings, and the fixing commit describe two ways workspace values got past it.
Gap 1: unregistered keys were kept
Some services read settings that are never registered. One example is experiments.override.<name>, which supplies an experiment's treatment value. That value becomes the default of the setting the experiment controls, and defaults are not subject to Workspace Trust. A workspace could therefore indirectly change the effective value of a restricted setting, even to a type that setting does not declare. Because the key itself had no schema, the skipRestricted filter had nothing to evaluate and kept it.
Gap 2: nested dotted keys escaped their parent's restriction
VS Code accepts settings both as nested objects and as flat dotted keys. If a registered object setting such as terminal.integrated.profiles.windows is restricted, a flat key one level deeper, such as terminal.integrated.profiles.windows.Pwn.path, has no schema of its own. When the parser reached a leaf value with no schema, it decided whether to keep it using an undefined schema, so no restriction applied:
if (!types.isObject(value) || Array.isArray(value)) { - return { included: this.shouldInclude(path, propertySchema, excludedPropertySchema, options), value, restricted: [], hasExcludedProperties: false }; + return this.filterUnregisteredLeaf(path, value, excludedPropertySchema, configurationProperties, excludedConfigurationProperties, options); }
The removed line shows the vulnerable behavior: the leaf was judged against its own (missing) schema rather than against the restricted object setting it actually belongs to.
The commit's live verification in Code OSS confirms the practical effect. The unfixed build applied a workspace-supplied terminal profile and experiment overrides while in Restricted Mode, and the fixed build did not.
Severity scoring
Microsoft's vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L/E:U/RL:O/RC:C, with a base score of 8.8 and a temporal score of 7.7. The attack is network reachable, needs no privileges, and requires user interaction. Scope is changed, with Low confidentiality, High integrity and Low availability impact. The temporal metrics reflect unproven exploit maturity, an official fix, and confirmed report status.
Attack flow
Based on the MSRC FAQ and the GitHub advisory, the scenario looks like this:
- The attacker prepares a workspace whose
.vscode/settings.jsonor.code-workspacefile contains keys the trust filter did not catch, such asexperiments.override.*entries or a flat dotted key below a restricted object setting liketerminal.integrated.profiles.windows. - The victim opens the workspace. Because it is untrusted, VS Code runs it in Restricted Mode and the user does not grant trust.
- Per Microsoft's description, the victim clicks an attacker-controlled notification.
- The untrusted workspace has applied settings that Workspace Trust would otherwise block, and commands may run in the user's context without a trust grant.
Two caveats apply. First, MSRC's FAQ (crafted workspace plus a notification click) and the GitHub advisory (unregistered experiment override settings) describe the path slightly differently. Both describe an untrusted workspace getting around Workspace Trust through settings, and both point to the 1.145.3 fix. Second, no public proof of concept or complete exploitation sequence has been published, and the patch authors note they did not run the original MSRC reproduction steps, so we do not speculate further about how the notification step connects to command execution.
Patch information
Microsoft fixed CVE-2026-121665 in Visual Studio Code 1.145.3, which MSRC marks as the Official Fix. The code change is commit 7a1a8e46 (internal "Merged PR 795"), tracked in issue #347418 under the 1.145.3 Patch Tuesday milestone and shipped from the public release/1.145 branch. It touches three files.
1. Nested keys follow their nearest registered parent (configurationModels.ts)
Leaves without a schema now go through a new filterUnregisteredLeaf() helper, which calls findRegisteredAncestor(). That function walks the dotted path from right to left, trying shorter prefixes until it finds a registered setting:
private findRegisteredAncestor(path: string, configurationProperties: IStringDictionary, excludedConfigurationProperties: IStringDictionary) { let index = path.lastIndexOf('.'); while (index > 0) { const ancestorPath = path.substring(0, index); const schema = Object.hasOwn(configurationProperties, ancestorPath) ? configurationProperties[ancestorPath] : undefined; const excludedSchema = Object.hasOwn(excludedConfigurationProperties, ancestorPath) ? excludedConfigurationProperties[ancestorPath] : undefined; if (schema || excludedSchema) { return { path: ancestorPath, schema, excludedSchema }; } index = path.lastIndexOf('.', index - 1); } return undefined; }
If a parent is found, the leaf inherits the parent's include and scope decision, and the parent's path is added to the restricted list when the parent is restricted. The Object.hasOwn checks ensure the lookup only considers the registry's own keys, never anything inherited through the prototype chain. Objects are still walked recursively, so a registered child under an unregistered key is still checked against its own schema. The new tests show every spelling of the restricted terminal profile key (flat, half nested, fully nested, and inside a [typescript] language override) is removed under skipRestricted, while unrestricted parents such as terminal.integrated.env.osx and trusted workspaces behave as before.
2. Untrusted workspaces skip unregistered settings (configuration.ts)
This is the core fix for the experiment override path. At all six places where workspace or folder configuration is loaded, reparsed or cached, the commit adds skipUnregistered next to skipRestricted, both tied to the same trust check:
- await this._workspaceConfiguration.load(this._workspaceIdentifier, { scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted() }); + await this._workspaceConfiguration.load(this._workspaceIdentifier, { scopes: WORKSPACE_SCOPES, skipRestricted: this.isUntrusted(), skipUnregistered: this.isUntrusted() });
While a workspace is untrusted, any key in .vscode/settings.json or a .code-workspace file without a registered schema is ignored, including every experiments.override.* key. The commit message states that standalone tasks.json, launch.json and mcp.json files are not affected by this change. A new test confirms that a setting registered late by an extension is ignored while untrusted and starts working as soon as its schema is registered.
3. Trust changes notify listeners (configurationService.ts)
Since the set of visible keys now depends on trust state, the change event fired on a trust transition must cover more than restricted settings. Previously it listed only restricted keys, so a service reading an experiment override would never learn the key had appeared or disappeared. The patch adds getChangedKeys(), which diffs the configuration model before and after the trust change in both directions:
private getChangedKeys(from: ConfigurationModel | undefined, to: ConfigurationModel): string[] { const fromKeys = from?.keys ?? []; const fromKeySet = new Set(fromKeys); const toKeySet = new Set(to.keys); return [...to.keys.filter(key => !fromKeySet.has(key)), ...fromKeys.filter(key => !toKeySet.has(key))]; }
These differences are collected for each folder and for the workspace configuration, merged with the restricted setting keys, deduplicated, and emitted in a single triggerConfigurationChange event. Tests confirm that experiments.override.test reads as undefined while untrusted, picks up the workspace value once trust is granted, returns to undefined when trust is revoked, and fires affectsConfiguration('experiments.override.test') on each transition.
Verification and scope
The commit adds 209 lines and removes 13, mostly regression tests. Per the commit message, 100 node tests and 418 Electron tests passed, and 5 of the new tests fail without the change. The authors state they did not test Windows, Linux, restart scenarios, or the original MSRC reproduction steps.
The same 1.145.3 release also fixes other Workspace Trust bugs, for example GHSA-fp22-hgv9-mq6q / CVE-2026-121662, so updating covers all of them. Install VS Code 1.145.3 or later from the normal update channel or the download link in the MSRC entry. Microsoft's advisory does not list a separate workaround.
Affected systems and versions
- Product: Microsoft Visual Studio Code
- Affected: version 1.0.0 up to, but not including, 1.145.3
- Fixed: 1.145.3 (MSRC Official Fix, released November 10, 2026)
- Vulnerable configuration: a workspace opened untrusted, in Restricted Mode, whose
.vscode/settings.jsonor.code-workspacefile supplies unregistered keys (such asexperiments.override.*) or flat dotted keys nested below a restricted registered object setting (such asterminal.integrated.profiles.windows) - Not changed by the fix: per the commit message, standalone
tasks.json,launch.jsonandmcp.jsonfiles are not affected by the new unregistered key filtering
References
- NVD: CVE-2026-121665
- CVE Record: CVE-2026-121665
- Microsoft Security Update Guide: CVE-2026-121665
- GitHub Security Advisory GHSA-c367-h9jc-pjr7
- Fix commit 7a1a8e46 (API)
- Fix commit 7a1a8e46 (GitHub)
- microsoft/vscode issue #347418
- Related advisory GHSA-fp22-hgv9-mq6q (CVE-2026-121662)
- Threat Radar entry for CVE-2026-121665
- CISA Known Exploited Vulnerabilities Catalog
- microsoft/vscode Security Policy
- Visual Studio Code
- CWE-501: Trust Boundary Violation



