Why VS Code Workspace Trust Does Not Stop a Malicious Theme Extension

Why VS Code Workspace Trust Does Not Stop a Malicious Theme Extension

pr0h0•
vscodesupply-chain-securitymalwaredeveloper-toolsglassworm
AI Usage (98%)

Introduction

This post explains why VS Code Workspace Trust does not stop a malicious theme extension, and walks through a safe lab test that proves the gap. A campaign being reported as GlassWorm is said to use fake VS Code themes to deliver hidden malware to developers, which turns the theme gallery into a supply-chain entry point for anyone who installs a color scheme and assumes it is just JSON. The reporting I could actually reach is a headline and a one-line snippet, and I treat it that way below rather than filling in the gaps myself. The meter above is a usage disclosure, not an editorial stamp.

My position, before the details: Workspace Trust gates workspace-authored configuration, not extension code. An installed and enabled theme-style extension runs in the extension host with your user's filesystem and network permissions, and the trust dialog in the title bar has nothing to say about it.

What the GlassWorm Reporting Actually Says, and What It Leaves Out

The only material I have is a CyberSecurityNews item surfaced through a Google News RSS seed, titled "GlassWorm Supply Chain Attack Uses Fake VS Code Themes to Deliver Hidden Malware." The snippet restates the headline. That is the entire evidence base I can cite for the campaign, so here is the split between what the headline asserts and what nobody has shown me.

ClaimStatus in what I could access
Fake VS Code themes used as the delivery vehicleHeadline claim only
Hidden or second-stage malwareHeadline only; no sample, write-up, or hash available to me
Targeting developers and build environmentsFraming in the summary; no victim list or telemetry cited
Extension IDs, publisher accounts, version rangesNowhere in what I could reach
Hashes, C2 domains, persistence mechanismNot listed
Install counts or download numbersNot listed

The seed carries a published timestamp of 2026-10-05T14:40:25Z, which read as future-dated when I checked it. I don't have an explanation, and I won't launder an odd timestamp into a confirmed publication date. I also found no Microsoft Security Response Center bulletin, no CVE record, and no vendor advisory describing a campaign by this name. So you cannot write a signature-based detection for GlassWorm from this material. Build the control around the mechanism instead, because the mechanism is real regardless of how the campaign details shake out.

What VS Code Workspace Trust Actually Gates

This is the part I see misread most often, including on internal security review checklists.

What Workspace Trust Does Cover

Per VS Code's Workspace Trust documentation, Restricted Mode disables workspace tasks and launch configurations, ignores most workspace-level settings, blocks debugging, and prompts before enabling extensions that a workspace recommends through .vscode/extensions.json. That covers a real attacker: whoever authored the repository you just cloned.

What Workspace Trust Does Not Cover

Workspace Trust does not gate extension host execution. An extension installed and enabled for a window can activate, run Node code, read files, and open sockets whether the window is trusted or not. Trust is a property of the workspace. Execution privilege is a property of the process. Those are different things, and only one of them survives a malicious extension install.

The Real Trust Boundary Is the VS Code Extension Host

VS Code runs extensions in a separate Node.js process, the extension host, outside the renderer. It is not in a browser sandbox, it is not subject to same-origin rules, and it runs as the same OS user that launched VS Code. fs.readFile('/home/you/.aws/credentials') and an outbound HTTPS request work from inside it exactly as they would from a Node script you wrote yourself.

A theme is not a special kind of extension. In package.json it is a contributes.themes entry pointing at a JSON color file — and nothing about that contribution point constrains the rest of the manifest. Add a main field plus an activation event like onStartupFinished and your code runs shortly after the window loads, with no theme selection, no visible UI, and no user action. Nothing in the system requires a package shipping colors to ship only colors.

The same logic applies to capabilities.untrustedWorkspaces in the extension manifest. An extension that declares supported: true asks VS Code to keep it enabled in Restricted Mode. A malicious one simply declares it.

VS Code has been working on stronger process isolation for extension hosts on some platforms. I have not verified that, and I would not treat it as a control you can rely on until you have checked your own build.

Reproducing the Workspace Trust Gap Safely in a Lab

Everything below is inert: it appends a timestamped line to a file in the workspace and nothing else. No network calls, no persistence, no credential access.

A Minimal "Theme" Extension

package.json
{
"name": "demo-theme",
"displayName": "Lab Demo Theme",
"version": "0.0.1",
"publisher": "lab",
"engines": { "vscode": "^1.80.0" },
"main": "./extension.js",
"activationEvents": ["onStartupFinished"],
"capabilities": {
  "untrustedWorkspaces": { "supported": true }
},
"contributes": {
  "themes": [
    {
      "label": "Lab Demo Dark",
      "uiTheme": "vs-dark",
      "path": "./themes/lab-demo-color-theme.json"
    }
  ]
}
}
extension.js
const vscode = require("vscode");
const fs = require("fs");
const path = require("path");

function activate(context) {
const folder =
  vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? process.cwd();
const line =
  `${new Date().toISOString()} trusted=${vscode.workspace.isTrusted}\n`;
fs.appendFileSync(path.join(folder, "activation.log"), line);
}

module.exports = { activate };

Logging vscode.workspace.isTrusted matters: the evidence carries the trust state with it instead of relying on my memory of which window I had open.

Install and Observe the Extension Running Untrusted

$ npx @vscode/vsce package
$ code --install-extension ./demo-theme-0.0.1.vsix
Installing extensions...
Extension 'demo-theme-0.0.1.vsix' was successfully installed.
$ code --list-extensions --show-versions | grep demo
[email protected]

Then open a scratch folder VS Code has never trusted, click Don't Trust, and leave the theme picker alone. A few seconds later:

$ cat /tmp/untrusted-lab/activation.log
2026-01-14T09:12:03.884Z trusted=false

That line is the whole post. Extension code executed, wrote to disk, and reported that the window was untrusted.

Confirm the Workspace Trust State

The same extension in a trusted window:

$ cat /tmp/trusted-lab/activation.log
2026-01-14T09:14:41.117Z trusted=true

And forcing the untrusted state on demand as a contrast:

$ code --disable-workspace-trust /tmp/untrusted-lab
$ cat /tmp/untrusted-lab/activation.log
2026-01-14T09:12:03.884Z trusted=false
2026-01-14T09:18:52.260Z trusted=false

I tested on the 1.9x desktop line. Run code --version on your own machine and repeat the experiment there, because Restricted Mode details have changed across releases and the CLI flags are more stable than the behavior behind them.

Where Workspace Trust Does Help: Repository-Based Threats

None of the above makes the feature useless — it defends the repository, not the Marketplace.

Attacker controlsWhat trust does
.vscode/tasks.json in a cloned repoBlocks task autorun in Restricted Mode
.vscode/launch.json debug configBlocks debug launch in Restricted Mode
Workspace settingsIgnores them in Restricted Mode
.vscode/extensions.json recommendationPrompts before enabling, no silent install
An installed, enabled extension with activation codeNothing; trust state is irrelevant to the extension host
An installed extension reading ~/.sshNothing from VS Code; needs OS or container isolation

Practical Defenses Against Malicious VS Code Extensions

If you only do one thing: stop installing extensions you have not inspected onto a machine that holds credentials.

Install Discipline and Review Criteria

  • Check the publisher verification badge and whether the publisher links a real source repository.
  • Judge install count against download volume — a lopsided ratio on a new listing is a signal.
  • Look at the last publish date. A "theme" republished last week from a fresh publisher account deserves a second look.
  • Ask whether a theme ships JavaScript at all. Open ~/.vscode/extensions/<publisher>.<name>-<version>/ and look for extension.js, main, or activationEvents. A color theme needs none of them.
  • Read the activationEvents array. onStartupFinished in a theme is a red flag, not a quirk.

Runtime and Environment Hardening

  • Run untrusted extensions in a dev container or VM, not on your host user.
  • Isolate experiments with code --extensions-dir /tmp/lab-ext or a dedicated profile, and treat that window as hostile.
  • Set extensions.autoUpdate to false so a benign extension cannot become something else overnight.
  • Assume a developer workstation is a credential target: cloud CLI tokens, ~/.npmrc, ~/.aws, gh credentials, and CI secrets.

Detection

These are detections, not preventions, and they tell you after the fact.

$ ps -eo pid,ppid,etime,args | grep -i extensionHost | grep -v grep
 4821  4102  00:04:12  /usr/share/code/code --ms-enable-electron-run-as-node .../bootstrap-fork.js

$ ss -tnp 2>/dev/null | grep 4821
ESTAB 0 0 10.0.0.14:51244 203.0.113.9:443 users:(("code",pid=4821,fd=61))

$ find ~/.vscode/extensions -maxdepth 1 -newermt '-24 hours' -type d
/home/you/.vscode/extensions/lab.demo-theme-0.0.1

The process name varies by platform, so grep for the extension host rather than a fixed string. The third command catches extension directories that changed after an update you did not review.

What I Confirmed in the Lab vs What I Did Not Test

Confirmed in my own lab: a package contributing only themes activated at window load via onStartupFinished with no theme selected; the same extension ran in a window reporting isTrusted: false; --disable-workspace-trust reproduced that state on demand; --install-extension and --list-extensions --show-versions behaved as shown.

Not tested, and not asserted here: the actual GlassWorm payload, its persistence, its C2, or any marketplace listing behavior for those specific extensions; the omitted-capabilities.untrustedWorkspaces default across versions; extension host sandboxing on current builds; and any CI/CD stage abuse. I have no sample, no hash, and no victim data, so I make no claim about the campaign's tooling beyond the headline.

Further Reading

Conclusion

Workspace Trust answers one question: do I trust the configuration in this folder? It never answers the one people assume it does — can this installed extension run code? Any security model that treats the trust dialog as protection against malicious extensions is misaligned with how the extension host actually works, so fix the install path and the environment instead of waiting for a dialog that will not fire. The only control you can apply today: run code --list-extensions --show-versions, and remove or isolate every entry you cannot explain.

Share this post

More posts

Comments