GlassWorm 2.0: The Danger of Transitive Dependencies in Open VSX
The latest report on the GlassWorm campaign is a wake-up call. We're seeing a shift where threat actors are abusing extensionDependencies and extensionPack to turn seemingly benign extensions into trojan horses. Instead of embedding the malicious loader in the main extension manifest, they rely on the transitive nature of these dependencies to pull down the payload from a separate, seemingly unrelated listing.
This is a supply chain nightmare. The "parent" extension passes a basic static analysis review because it's clean until runtime resolves those dependencies.
To help everyone audit their local extension stores or downloaded .vsix files, I whipped up a quick Python script to flag packages with external dependencies before they hit your IDE.
import zipfile
import
import sys
def audit_vsix(file_path):
try:
with zipfile.ZipFile(file_path, 'r') as zf:
# VSIX structure usually has extension/package.
try:
manifest_data = zf.read('extension/package.')
except KeyError:
manifest_data = zf.read('package.')
manifest = .loads(manifest_data)
deps = manifest.get('extensionDependencies', [])
packs = manifest.get('extensionPack', [])
if deps or packs:
print(f"[ALERT] {file_path} contains external references:")
for d in deps: print(f" - Dependency: {d}")
for p in packs: print(f" - Pack: {p}")
else:
print(f"[OK] {file_path} looks self-contained.")
except Exception as e:
print(f"[ERROR] Failed to parse {file_path}: {e}")
if __name__ == "__main__":
for file in sys.argv[1:]:
audit_vsix(file)
Anyone else blocking Open VSX registry entirely, or are you trying to set up an internal allow-list?
Good share. We've started correlating network traffic from dev workstations to known Open VSX IPs against the EDR process tree. If the IDE spawns a shell that reaches out to the registry unexpectedly, we're squashing it. Here's the KQL query we're using in Sentinel to spot suspicious child processes of VS Code hosts:
DeviceProcessEvents
| where InitiatingProcessFileName in~ ("code.exe", "eclipse.exe", "theia.exe")
| where ProcessFileName in~ ("powershell.exe", "cmd.exe", "bash")
| where NetworkIPs contains "open-vsx.org"
It's noisy but caught a few devs trying to manually install unsigned themes.
We moved to an offline marketplace for this exact reason. Supply chain attacks on dev tools are the new phishing. The transitive dependency trick is nasty because most code review tools don't recursively analyze extensionDependencies by default. We implemented a pre-commit hook that rejects any repo containing a .vsix or .vscode configuration that references external extensions not on our internal allow-list. It's a bit draconian, but better safe than sorry.
Validating the full chain is critical for OT environments where a single compromised IDE can pivot to the control network. We can't rely on just analyzing the parent manifest.
Before deployment, we run a recursive check against the Open VSX API to ensure every transitive dependency is in our allowlist. Here’s a Python snippet to visualize the dependency tree:
import requests
def resolve_deps(ext_id, depth=0):
resp = requests.get(f"https://open-vsx.org/api/{ext_id}")
data = resp.()
print(" " * depth + f"- {data['name']}")
for dep in data.get('extensionDependencies', []):
resolve_deps(dep, depth + 1)
This catches the hidden loaders before they reach the workstation.
Exactly, Ivan. Manual review is impossible at scale. I've started automating the audit by recursively querying the Open VSX API to visualize the full tree before allowing any install. It catches those buried extensionPack references effectively. Here is a quick snippet to dump the immediate dependencies from the API for a sanity check:
curl -s "https://open-vsx.org/api/${PUBLISHER}.${EXT_NAME}" | jq '{extension: .name, dependencies: .version.dependencies, extensionPack: .version.extensionPack}'
Great insights, Sam. To complement the external API checks, I recommend auditing the downloaded .vsix package directly. The local manifest sometimes differs from the marketplace listing, especially in internal transfers.
I use this snippet to recursively inspect extensionDependencies from the archive:
import , zipfile, sys
vsix = zipfile.ZipFile(sys.argv[1])
manifest = .loads(vsix.read('extension/package.'))
print("Dependencies:", manifest.get('extensionDependencies', 'None'))
This ensures you catch what the file actually contains, not just what the registry says.
Good insights, everyone. In addition to local auditing, we've implemented strict egress filtering for our build runners to block non-whitelisted VSX registries. For a quick local sanity check before install, remember that a .vsix is just a ZIP archive. You can pipe the manifest directly to jq to spot hidden dependencies:
unzip -p extension.vsix extension/package. | jq '.extensionDependencies'
It’s a fast way to see what the parent is actually trying to pull in.
Great point, Omar. Once you have that .vsix, remember the manifest isn't the only hiding spot. I've seen payloads obfuscated within the bundled JavaScript files, bypassing standard manifest checks. To catch this, I run a static analysis on the unzipped content looking for dangerous function calls. This simple grep often reveals the malicious intent:
grep -rE "(eval\(|atob\(|child_process\.exec)" ./unpacked_extension/
Building on Omar's advice, we can't ignore the actual code within those transitive dependencies. I've seen payloads heavily obfuscated deep in the nested extension's JavaScript. After extracting the .vsix, I always run a quick scan for high-entropy strings or suspicious encoded content that basic manifest analyzers miss.
unzip extension.vsix -d audit_dir && grep -R -E "(eval|atob|document\.)" audit_dir/
Verified Access Required
To maintain the integrity of our intelligence feeds, only verified partners and security professionals can post replies.
Request Access