GitHub Breach: How a VS Code Extension Became a Weapon
How GitHub's Internal Repos Were Stolen Through a VS Code Extension
GitHub confirmed on May 19, 2026 that a threat actor called TeamPCP (tracked as UNC6780) exfiltrated approximately 3,800 internal repositories after a single employee installed a poisoned VS Code extension downloaded from the official VS Code Marketplace. Credentials and session tokens were harvested from the developer's machine. Within roughly 30 minutes, TeamPCP was iterating through private repositories.
The instinct that this was a "browser extension" attack isn't entirely wrong — the mental model is the same. VS Code extensions and browser extensions share the same structural flaw: they're third-party code running with elevated permissions, and the installation ceremony does almost nothing to communicate that risk. The only difference is which credential store got drained.

The attack against GitHub is the clearest demonstration yet that the developer workstation is the new perimeter. This post breaks down exactly what happened, why extensions are structurally dangerous, and the concrete steps to audit, scope, and harden your extension surface — in VS Code and in your browser.
Why Extensions Are Structurally a Security Problem
Before getting into the fix, you need to understand the threat model.
A VS Code extension runs in the same Node.js process as the editor's extension host. It can read every file on your filesystem that VS Code can reach, execute arbitrary shell commands via child_process, make outbound network requests to any host, read your environment variables (including $HOME, $PATH, and anything exported in your shell profile), access .git config files and credential helpers, and interact with VS Code's built-in authentication providers that store GitHub, Azure, and other OAuth tokens.
There is no OS-level sandbox. VS Code isolates the extension host process from the renderer process, but not from your system. An extension claiming to "enhance your TypeScript experience" has the same filesystem and network access as one that explicitly states it reads your home directory. The Marketplace does not enforce any runtime capability restrictions.
Browser extensions are gated differently but share the same fundamental design. The manifest.json declares which permissions the extension requests — tabs, storage, cookies, identity, webRequest, host permissions — and the browser shows a permission prompt at install time. The problem is that most developers click through that dialog the same way they click through a license agreement.
The shared failure mode across both ecosystems: everything relies on your judgment at install time, with minimal runtime enforcement after that. Once an extension is installed and trusted, it can do nearly anything within its declared capabilities — and in VS Code's case, even the declaration layer is thin.
What Actually Happened — The TeamPCP Attack Chain
The poisoned extension was the Nx Console — a legitimate, widely-used monorepo tooling extension with 2.2 million installs and a verified publisher badge on the Marketplace. Not a sketchy one-off from an unknown handle. A tool that millions of developers use daily, with an established reputation and a real organization behind it. TeamPCP compromised a version of it via a malicious update, and the backdoored release was live on the Marketplace for 18 minutes before it was pulled.
The attack chain:
- GitHub employee already has Nx Console installed, or installs the update through VS Code's auto-update mechanism
- The compromised version activates, runs a background process, and reads VS Code's stored authentication tokens and session material
- Exfiltrated credentials include GitHub OAuth tokens with scope broad enough to list and clone internal repositories
- TeamPCP uses those tokens to pull approximately 3,800 internal repos before GitHub detects anomalous activity and revokes the session
The total time from installation to first exfiltration was almost certainly under an hour. GitHub employees' tokens naturally carry access to GitHub's own internal GitHub organization — which means the scope was there from day one, not something the attacker had to escalate to.
GitHub confirmed the breach is currently limited to internal repositories with no evidence of customer data exposure, but the investigation was ongoing at the time of disclosure. Any breach at that scale with that detection lag means the exfiltration was likely complete before the response began. TeamPCP subsequently offered the data for sale on cybercrime forums at $50,000.
How to Vet Extensions Before Installing
VS Code Extensions — The Five-Point Check
Before installing any VS Code extension, run through this checklist. All five take under three minutes total.
1. Publisher verification — but don't stop there. On the Marketplace page, the publisher name sits directly below the extension title. Click it. A legitimate publisher has a verified checkmark badge, multiple published extensions, a consistent naming convention, and a verifiable identity. An extension published by a handle like DevUtils2024 with one extension and 30 downloads is a hard stop. But the GitHub breach was through Nx Console, a verified publisher with 2.2 million installs. The checkmark tells you the publisher is who they say they are. It does not tell you their latest update is clean. A verified publisher is a starting point, not a finish line.
2. Download count and review age — and recent changelog. Organic adoption for legitimate developer tools takes months. An extension with 50 downloads and five-star reviews all written in the last two weeks is a red flag. But the more important habit for established extensions is checking the changelog before every update. When VS Code prompts you to update an extension, open the release notes first. A changelog entry that doesn't match the version bump, or one that's unusually vague ("performance improvements") on an extension that previously had detailed notes, is worth a second look at the source repo diff.
3. Source repository. Every legitimate extension links to a source repository in the Marketplace metadata sidebar. If there is no repository link, the extension is unverifiable — do not install it. If there is a link, open it and check: when was the last commit, does the code match what the extension claims to do, how many contributors does it have, and does the README match the Marketplace description word for word? A two-sentence README on an extension that claims deep IDE integration is a mismatch worth investigating.
4. package.json activation events. If you clone the repository, look at the activationEvents array in package.json. An extension activating on "*" (every VS Code event) has no reason to do that unless it's genuinely providing editor-wide functionality. Cross-reference the activation scope against the feature set. A color theme that activates on onStartupFinished and requests vscode.workspace.fs is doing something a color theme has no reason to do.
5. Post-install network behavior. After installing anything new, open VS Code's Output panel and switch to the extension's output channel. Any network requests to IPs or domains unrelated to the extension's stated purpose are an immediate uninstall trigger. On macOS you can also run a quick check from the terminal:
# Extensions installed most recently — check these first
ls -lt ~/.vscode/extensions/ | head -20
# Monitor extension host connections after install (macOS)
lsof -i -n -P | grep "Code Helper"Browser Extensions — Scoping Permissions to Specific Sites
Browser extensions have a more granular permission model than VS Code, and you can enforce it after the fact. The "This can read and change all your data on all websites" permission is the nuclear option — most extensions request it because it's the path of least resistance, not because they actually need it. You can change this without breaking most extensions.
Chrome, Brave, Arc, Edge (Chromium-based):
- Navigate to
chrome://extensions - Click Details on the extension
- Under Site access, switch from On all sites to On specific sites or When you click the extension
- For any extension scoped to one service — a GitHub productivity tool, a Notion clipper, a Linear keyboard shortcut extension — restrict it to that domain only
github.com
notion.so
linear.app
Switching to When you click means the extension only runs when you explicitly activate it via the toolbar icon. This eliminates passive page injection entirely. For clippers, inspectors, and on-demand tools, this is the correct default. The extension still works — you just have to click it.
Firefox:
- Open
about:addons - Select the extension
- Go to the Permissions tab
- Click the
...menu and choose Always Active, Only in Private Windows, or set it to Only when clicked
Firefox also supports optional permissions — extensions can request additional access only when a specific feature is triggered rather than at install time. Prefer extensions built around this model; it's a strong signal that the developer thought about their blast radius.
Safari:
- Safari → Settings → Extensions
- Select the extension
- Under Websites, set the allowed access to specific domains
The rule: scope every browser extension to the minimum surface it needs to function. An extension that enhances GitHub's pull request UI does not need to run on your bank's website, your company's internal tools, or every authenticated session you have open in that browser profile.
Failure Modes — What Goes Wrong When You Skip This
The trusted-extension trap
The extension used in the breach was Nx Console — 2.2 million installs, verified publisher, actively maintained. It was pulled from the Marketplace in 18 minutes after the malicious version was flagged. But 18 minutes is enough. If VS Code auto-updated the extension on your machine before the pull, you were exposed and had no way to know.
This changes the threat model entirely. The advice of "only install from verified publishers with high download counts" was reasonable guidance before this incident. It is insufficient after it. A compromised update to any extension you already trust bypasses every install-time check you have. The attack surface is not the initial install — it is every subsequent update to every extension already on your machine.
The revoked-token problem
Revoking a stolen OAuth token after exfiltration has already completed is evidence containment, not remediation. The data is gone. GitHub revoked the session, but 3,800 repositories had already been pulled before the revocation triggered.
Token hygiene matters before the breach. GitHub exposes all active OAuth grants at github.com/settings/applications. Any third-party app with repo scope or admin:org scope that you have not actively used in 90 days should be revoked. If you cannot identify what granted that token or why it has that scope, revoke it immediately.
The update-as-attack-vector problem
Nx Console proved what security researchers had been warning about for years: the update mechanism is the most reliable attack vector in the extension ecosystem. The extension was clean for years. One malicious version, live for 18 minutes, was enough to breach GitHub.
VS Code auto-updates extensions silently by default. If you're running sensitive work on a machine, disable this:
// User settings or workspace .vscode/settings.json
{
"extensions.autoUpdate": false
}With auto-update off, extensions update only when you manually trigger it from the sidebar. Before updating any extension you consider security-adjacent, check the changelog on the Marketplace or the release notes on the source repository. This takes 30 seconds and eliminates the auto-update injection vector entirely.
The middle-ground setting "extensions.autoUpdate": "onlyEnabledExtensions" still auto-updates but skips disabled extensions. Better than the default but not fully controlled.
Token scope accumulation
The GitHub breach worked at scale because the stolen token had enough scope to access internal repositories. Developer tokens accumulate scope over time — you add admin:org once for a specific migration task and forget to narrow it afterward. Three years later, your VS Code GitHub integration is holding a token that can enumerate org membership and list private repositories across every organization you belong to.
Audit your token scopes now at github.com/settings/tokens. For every token, ask: what specific operation does this exist for, and does the scope match that operation exactly? Fine-grained personal access tokens (PATs) with expiry dates and repository-specific scopes are the current best practice. Classic PATs with broad scope and no expiry are standing vulnerabilities.
Tradeoffs — Where Restriction Has Real Costs
Knowing where the friction lands matters before applying blanket rules.
VS Code extension restriction — VS Code does not have a per-extension network permission model like browsers do. You either trust it or you don't install it. The practical mitigation is workspace-level extension profiles: use a .vscode/extensions.json unwantedRecommendations list to disable specific extensions in sensitive workspaces, and keep a minimal extension set for codebases with credentials or internal access.
Browser "When you click" mode — Extensions that inject UI into every page (grammar checkers, dark mode tools, tab managers, reading mode tools) will break in When you click mode because they need to run passively on page load. The call is per-extension: does this extension need to be active on every page, or does it only need to run when I invoke it? If it genuinely needs passive access, keep it on the specific sites where you actually use it rather than all sites.
Auto-update disabled — You will miss legitimate security patches if extensions cannot update automatically. The tradeoff: "extensions.autoUpdate": false plus a weekly manual review of pending updates in the Extensions sidebar. One tab open, 60 seconds, and you stay in control of what changes on your machine.
The core tradeoff in every case is: a small amount of friction at install and update time versus the cost of responding to a credential theft. A 90-second extension audit at install is orders of magnitude cheaper than the incident response, credential rotation, and audit log review that follows.
Operational Reality — What to Do Right Now
If you haven't done any of this, start today. Do it in one sitting.
VS Code — 10 minutes:
- Open the Extensions sidebar (
Cmd+Shift+X) - For every installed extension, click the gear icon and verify the publisher is traceable
- Uninstall anything you haven't used in the last 30 days — no exceptions
- For the extensions you keep, open their Marketplace page and confirm the source repository is public and recently active
- Set
"extensions.autoUpdate": falsein user settings and create a recurring weekly reminder to manually review pending updates
Browser — 5 minutes:
- Open
chrome://extensions(orabout:addonsin Firefox) - For every extension, click Details and check the Site access setting
- Switch anything set to On all sites to On specific sites or When you click the extension
- Remove any extension you don't recognize or haven't used in 30 days
GitHub OAuth tokens — 5 minutes:
- Go to
github.com/settings/applications→ Authorized OAuth Apps - Revoke anything you don't recognize or no longer use
- Go to Personal access tokens and delete any token with no expiry and scope broader than what it was created for
- If you're in a GitHub organization, check
github.com/organizations/[org]/settings/audit-logfor recent OAuth authorizations and repository access events
The GitHub breach is a reminder that "verified publisher with millions of installs" is not a security guarantee — it's a starting condition. The Nx Console extension had every signal we associate with trustworthy software. It still became the entry point for one of the most significant internal breaches in GitHub's history.
The extension ecosystem is the developer supply chain, and updates are the weakest link in it. TeamPCP found GitHub through a single employee, a compromised update to a trusted extension, and a token scope that was wider than it needed to be. The first variable was outside the employee's control. The second and third were not.
For more on supply chain attacks in the developer ecosystem, see The npm Supply Chain Attack Problem Is Getting Worse.
References
- GitHub confirms breach of 3,800 repos via malicious VSCode extension — BleepingComputer
- TeamPCP breached GitHub's internal codebase via poisoned VS Code extension — Help Net Security
- GitHub confirms being hacked by TeamPCP, says customer data unaffected — The Record