I woke up to the news that GitHub had to deal with a breach affecting around 3,800 repositories, reportedly linked to a malicious VSCode extension. According to early reports, the attack vector was an extension that quietly harvested credentials and tokens from infected machines. My first reaction wasn't shock — it was "yeah, that tracks."
If you've been a developer for more than a few years, you know this was always going to happen. We install extensions like we install browser bookmarks. Click, trust, move on. So let's actually talk about why this keeps happening and how to lock things down without quitting your editor entirely.
The real problem: your editor is a trust black hole
Here's the thing nobody wants to admit. When you install a VSCode extension, you're essentially running arbitrary Node.js code with the same permissions as your user account. That means it can:
- Read every file in every folder you open
- Spawn child processes (so, shell access)
- Make outbound HTTP requests to anywhere
- Read environment variables
- Touch your
~/.ssh,~/.aws,~/.npmrc,.envfiles
There is no fine-grained permission system. No "this extension wants to access network — allow?" prompt. It just runs. I spent an afternoon last month digging through extension source code on the marketplace and the variance in what extensions actually do versus what they claim to do is wider than you'd hope.
The attack pattern is usually one of two flavors:
-
Typosquatting — an extension named
prettier-vscode(legit) vsprettier_vscodeorprettierr-vscode - Supply chain hijack — a legit extension's publisher account gets compromised, and a malicious update ships to existing users
Step 1: Audit what you already have
First things first, see what's actually installed. Run this:
code --list-extensions --show-versions
Now take that list and check each one against the marketplace. Specifically look at:
- Publisher verification — is there a blue checkmark on their publisher page?
- Install count and age — a brand-new extension with 50 installs that does something important is suspicious
- Source code link — does it point to a real GitHub repo? Is the repo active?
I wrote a quick script to dump this into a CSV so I could review my whole list in one go:
#!/usr/bin/env bash
# audit-extensions.sh
set -euo pipefail
EXT_DIR="$HOME/.vscode/extensions"
echo "name,version,publisher,repo"
for pkg in "$EXT_DIR"/*/package.json; do
# jq parses each package.json — skip ones missing fields rather than crashing
name=$(jq -r '.name // "unknown"' "$pkg")
ver=$(jq -r '.version // "?"' "$pkg")
pub=$(jq -r '.publisher // "?"' "$pkg")
repo=$(jq -r '.repository.url // .repository // "none"' "$pkg")
echo "$name,$ver,$pub,$repo"
done
Run it, open the CSV in whatever you like, and delete anything you can't justify keeping. I had a markdown linter from 2019 that I genuinely don't remember installing. Gone.
Step 2: Stop storing long-lived tokens on disk
This is the part that actually saved a friend of mine last year. The malicious extension scenario is bad because of what it can find. Tokens with no expiry, sitting in plain text. So let's not have those.
For GitHub specifically, switch to the git credential manager backed by your OS keychain. On macOS:
# Set git to use the macOS keychain
git config --global credential.helper osxkeychain
# Remove any plaintext tokens you might have in ~/.netrc or hardcoded URLs
grep -r 'github.com' ~/.gitconfig ~/.netrc 2>/dev/null || echo "clean"
For SSH keys, use a passphrase and load them through ssh-agent. A passphrase-less SSH key is essentially a plaintext credential. I know it's annoying. Do it anyway.
Then rotate. Just rotate everything periodically. GitHub fine-grained personal access tokens support expiration dates — use them. Set them to 30 or 60 days. If a token leaks, the blast radius is now measured in weeks, not years.
Step 3: Sandbox per-project with dev containers
This is the bigger architectural fix. Instead of opening sensitive client work in the same editor instance that has every extension you've ever installed, isolate it.
The official Dev Containers extension from Microsoft lets you define a container per project, and extensions can be scoped to that container. Here's a minimal .devcontainer/devcontainer.json I use:
{
"name": "client-project",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"customizations": {
"vscode": {
// Only these extensions load inside the container.
// Your local extensions don't get repo access.
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
},
"remoteEnv": {
// Don't forward host secrets into the container
"GITHUB_TOKEN": ""
}
}
Now when you Reopen in Container, your sketchy markdown preview extension from 2019 doesn't get to read this project's source code. It runs on the host; the project lives in the container.
Is this overhead? Yes. Is it worth it for anything client-related or anything touching production credentials? Also yes.
Step 4: Enable VSCode's restricted mode (workspace trust)
This one is free and most people have it on by default, but worth verifying. In your settings.json:
{
"security.workspace.trust.enabled": true,
"security.workspace.trust.startupPrompt": "always",
"security.workspace.trust.untrustedFiles": "prompt"
}
This won't stop a malicious extension you explicitly trusted, but it does limit what runs when you open a random repo someone DMs you. Tasks don't auto-run, debuggers don't auto-attach.
Step 5: Monitor outbound traffic from your editor
This is the nuclear option but honestly the most informative thing I've ever done. Install Little Snitch on macOS or use OpenSnitch on Linux and watch what your editor connects to.
The first week is noisy. You'll approve a lot of legit stuff — GitHub Copilot phoning home, the marketplace checking for updates, language servers downloading grammars. But once it's quiet, anything new sticks out. An extension making a request to api.something-i-dont-recognize.com becomes immediately visible instead of buried in network noise.
Prevention checklist
A quick recap of stuff that should just be baseline hygiene at this point:
- Audit your extension list every quarter. Delete what you don't use.
- Use OS keychain for git credentials. No plaintext tokens.
- Set expiration dates on every personal access token you create.
- Use dev containers for any project where the source is sensitive.
- Verify publisher status before installing new extensions.
- Keep workspace trust enabled.
- If you can stomach it, run an outbound firewall and review what your editor connects to.
None of this requires you to live in fear or stop using extensions. It just means treating the editor like what it actually is — a runtime executing third-party code on your machine with full access to everything you care about. The breach reported by GitHub is going to happen again, probably this year. Be ready before it does.
Top comments (0)