[{"data":1,"prerenderedAt":470},["ShallowReactive",2],{"switcher-blog-pareja":3,"art-malicious-vs-code-extension-github-breach-en":6},{"en":4,"es":5},"\u002Fen\u002Fblog\u002Fmalicious-vs-code-extension-github-breach\u002F","\u002Fes\u002Fblog\u002Fextension-vs-code-maliciosa-hackeo-github\u002F",{"id":7,"title":8,"author":9,"body":10,"date":455,"description":456,"extension":457,"image":458,"meta":459,"navigation":460,"pareja":461,"path":462,"seo":463,"stem":464,"tags":465,"__hash__":469},"blogEn\u002Fen\u002Fblog\u002Fmalicious-vs-code-extension-github-breach.md","18 minutes was all it took: how a VS Code extension walked off with 3,800 private GitHub repos","Paco Cubel",{"type":11,"value":12,"toc":440},"minimark",[13,18,31,38,41,45,55,62,91,106,110,125,128,156,163,167,177,189,193,196,201,209,242,258,262,287,327,336,340,368,372,379,383,390,393,397,436],[14,15,17],"h2",{"id":16},"no-broken-firewall-no-zero-day-just-one-click","No broken firewall, no zero-day: just one click",[19,20,21,22,26,27,30],"p",{},"We're used to picturing big hacks as assaults on a fortress: walls torn down, impossible exploits, weeks of siege. The reality of May 2026 was far humbler and far more unsettling. ",[23,24,25],"strong",{},"GitHub"," —owned by Microsoft, home to half the planet's code— had its internal repositories stolen, and it all started on ",[23,28,29],{},"the laptop of one of its developers",", who installed an update to an extension they used every day.",[19,32,33,34,37],{},"The exposure window was ",[23,35,36],{},"18 minutes",". That's all the trojanized version was available for. Enough.",[19,39,40],{},"Let's tell it properly, because the underlying lesson isn't about GitHub: it's about you, your team and the laptop you deploy from.",[14,42,44],{"id":43},"what-exactly-happened","What exactly happened",[19,46,47,48,51,52,54],{},"On May 18, 2026, a malicious update to the ",[23,49,50],{},"Nx Console"," extension for Visual Studio Code appeared on the Marketplace. It stayed live for ",[23,53,36],{}," —from 12:30 to 12:48 UTC— before being pulled. In that window, anyone with auto-update enabled swallowed it.",[19,56,57,58,61],{},"What did that update do? It was a ",[23,59,60],{},"credential stealer",". The moment it landed on a developer's machine, it rummaged through and made off with everything juicy it could find:",[63,64,65,72,78],"ul",{},[66,67,68,71],"li",{},[23,69,70],{},"1Password"," vaults,",[66,73,74,77],{},[23,75,76],{},"Claude Code"," configurations (yes, even the tokens of AI tooling),",[66,79,80,83,84,86,87,90],{},[23,81,82],{},"npm",", ",[23,85,25],{}," and ",[23,88,89],{},"AWS"," credentials.",[19,92,93,94,97,98,101,102,105],{},"One of those developers worked at GitHub. With their credentials, the attackers reached the internal infrastructure and ",[23,95,96],{},"exfiltrated roughly 3,800 private repositories"," (the group itself brags about 4,000). On ",[23,99,100],{},"May 20, GitHub confirmed it",". And in case there was any doubt about intent, the attackers put \"GitHub's source code and internal orgs\" ",[23,103,104],{},"up for sale on a criminal forum, with a floor price of $50,000",".",[14,107,109],{"id":108},"it-wasnt-luck-it-was-a-campaign","It wasn't luck: it was a campaign",[19,111,112,113,116,117,120,121,105],{},"Here's where this stops being an anecdote and becomes a pattern. Behind it is a group known as ",[23,114,115],{},"TeamPCP"," (Google tracks it as UNC6780), and the GitHub hit was just ",[23,118,119],{},"one piece"," of a much larger supply chain campaign nicknamed ",[122,123,124],"em",{},"Shai-Hulud",[19,126,127],{},"Between late 2025 and May 2026, that same group:",[63,129,130,143,150],{},[66,131,132,133,136,137,86,139,142],{},"poisoned ",[23,134,135],{},"more than 170 packages"," across ",[23,138,82],{},[23,140,141],{},"PyPI",",",[66,144,145,146,149],{},"compromised the ",[23,147,148],{},"GitHub Actions"," of security vendors as serious as Aqua Security and Checkmarx (Trivy),",[66,151,152,153,105],{},"and hijacked a VS Code extension with ",[23,154,155],{},"2.2 million installs",[19,157,158,159,162],{},"And many of those intrusions started from something as silly as ",[23,160,161],{},"a single improperly rotated access token (PAT)"," or a compromised developer. One key left in a pocket opens the door to the whole house.",[14,164,166],{"id":165},"the-uncomfortable-truth-the-weak-link-is-still-the-human","The uncomfortable truth: the weak link is still the human",[19,168,169,170,173,174],{},"Notice what there ",[23,171,172],{},"wasn't"," in any of this: no dazzling exploit against GitHub's server, no zero-day in its infrastructure. The attacker didn't break the wall. ",[23,175,176],{},"They tricked whoever had the key.",[19,178,179,180,183,184,188],{},"That's the discomfort worth accepting: in most recent major incidents, the human barely \"does\" anything. They're ",[23,181,182],{},"passive",". They are, simply, a credential with legs. They install an extension, run an ",[185,186,187],"code",{},"npm install",", save a token in a config file… and unknowingly become the way in. If in your head security ends at the server's firewall, you're leaving out the device from which that server is governed.",[14,190,192],{"id":191},"how-not-to-be-the-next-headline","How not to be the next headline",[19,194,195],{},"Let's land this, which is what we're here for. You don't have to be GitHub for this to hit you: anyone who installs dependencies and uses extensions is in the same crosshairs. The good news is that the defense is common sense and cheap.",[197,198,200],"h3",{"id":199},"_1-treat-extensions-as-what-they-are-third-party-code-with-permissions","1. Treat extensions as what they are: third-party code with permissions",[63,202,203],{},[66,204,205,208],{},[23,206,207],{},"Disable auto-update for extensions."," It's the detail that turned 18 minutes into a global disaster. In VS Code:",[210,211,216],"pre",{"className":212,"code":213,"language":214,"meta":215,"style":215},"language-json shiki shiki-themes github-dark-high-contrast","\u002F\u002F settings.json\n\"extensions.autoUpdate\": false\n","json","",[185,217,218,227],{"__ignoreMap":215},[219,220,223],"span",{"class":221,"line":222},"line",1,[219,224,226],{"class":225},"sQrFR","\u002F\u002F settings.json\n",[219,228,230,234,238],{"class":221,"line":229},2,[219,231,233],{"class":232},"sTRMh","\"extensions.autoUpdate\"",[219,235,237],{"class":236},"sMAXC",": ",[219,239,241],{"class":240},"sCcAr","false\n",[63,243,244,251],{},[66,245,246,247,250],{},"Install ",[23,248,249],{},"only what you need",". Every extension is someone else's code running with your permissions. Audit your editor and delete what you don't use.",[66,252,253,254,257],{},"Before installing or updating, check ",[23,255,256],{},"who publishes it"," and be wary of brand-new versions.",[197,259,261],{"id":260},"_2-treat-dependencies-with-the-same-distrust","2. Treat dependencies with the same distrust",[63,263,264,281],{},[66,265,266,269,270,273,274,276,277,280],{},[23,267,268],{},"Pin exact versions"," and always use the lockfile. In deployments, ",[185,271,272],{},"npm ci"," (not ",[185,275,187],{},"): it installs exactly what's in ",[185,278,279],{},"package-lock.json",", no surprises.",[66,282,283,286],{},[23,284,285],{},"Disable install scripts",", the classic vector for running code when a package is installed:",[210,288,292],{"className":289,"code":290,"language":291,"meta":215,"style":215},"language-bash shiki shiki-themes github-dark-high-contrast","npm config set ignore-scripts true        # global\nnpm ci --ignore-scripts                    # per project\n","bash",[185,293,294,314],{"__ignoreMap":215},[219,295,296,299,302,305,308,311],{"class":221,"line":222},[219,297,82],{"class":298},"s_sBn",[219,300,301],{"class":232}," config",[219,303,304],{"class":232}," set",[219,306,307],{"class":232}," ignore-scripts",[219,309,310],{"class":240}," true",[219,312,313],{"class":225},"        # global\n",[219,315,316,318,321,324],{"class":221,"line":229},[219,317,82],{"class":298},[219,319,320],{"class":232}," ci",[219,322,323],{"class":240}," --ignore-scripts",[219,325,326],{"class":225},"                    # per project\n",[63,328,329],{},[66,330,331,332,335],{},"Apply a ",[23,333,334],{},"cooldown",": don't jump to the latest version the same day it ships. Most poisoned packages are spotted and pulled within hours.",[197,337,339],{"id":338},"_3-treat-credentials-as-if-they-were-already-leaked","3. Treat credentials as if they were already leaked",[63,341,342,352,358],{},[66,343,344,347,348,351],{},[23,345,346],{},"Short-lived tokens, minimal permissions."," A GitHub PAT with a long expiry and permissions to spare is exactly what these attacks hunt for. Use ",[122,349,350],{},"fine-grained"," tokens, with an expiry and only the permissions strictly needed.",[66,353,354,357],{},[23,355,356],{},"Rotate them for real and completely."," Half-rotating a token (leaving the old one valid \"just in case\") was the origin of several of these intrusions.",[66,359,360,363,364,367],{},[23,361,362],{},"No plaintext secrets on the laptop."," AWS credentials in ",[185,365,366],{},"~\u002F.aws",", tokens in config files… that's precisely what the stealer came for. MFA on everything that allows it.",[197,369,371],{"id":370},"_4-the-developers-laptop-is-production","4. The developer's laptop is production",[19,373,374,375,378],{},"If you deploy from that machine, access private repos or sign releases, ",[23,376,377],{},"it's a production asset",", not \"the work computer.\" It deserves the same care: updated, with the minimum installed, and with credentials that, if leaked, don't unlock the whole kingdom.",[14,380,382],{"id":381},"the-recommendation-no-hedging","The recommendation, no hedging",[19,384,385,386,389],{},"The GitHub attack didn't prove its walls were weak. It proved that ",[23,387,388],{},"it doesn't matter how high the wall is if you leave the key on a laptop that runs third-party code without a second thought",". The software supply chain —extensions, dependencies, tokens— is today the most profitable attack surface there is, precisely because almost nobody watches it.",[19,391,392],{},"If your team installs dependencies and extensions daily, and nobody has ever looked at what permissions they drag in or how well-rotated the tokens are, that's exactly the hole we should talk about. Reviewing it costs an afternoon; discovering it on a code-for-sale forum costs a great deal more.",[14,394,396],{"id":395},"references","References",[63,398,399,408,415,422,429],{},[66,400,401],{},[402,403,407],"a",{"href":404,"rel":405},"https:\u002F\u002Fwww.bleepingcomputer.com\u002Fnews\u002Fsecurity\u002Fgithub-confirms-breach-of-3-800-repos-via-malicious-vscode-extension\u002F",[406],"nofollow","GitHub confirms breach of 3,800 repos via malicious VS Code extension — BleepingComputer",[66,409,410],{},[402,411,414],{"href":412,"rel":413},"https:\u002F\u002Fthehackernews.com\u002F2026\u002F05\u002Fgithub-internal-repositories-breached.html",[406],"The breach came via the Nx Console extension — The Hacker News",[66,416,417],{},[402,418,421],{"href":419,"rel":420},"https:\u002F\u002Fwww.helpnetsecurity.com\u002F2026\u002F05\u002F20\u002Fgithub-breached-teampcp\u002F",[406],"TeamPCP and the $50,000 sale of the code — Help Net Security",[66,423,424],{},[402,425,428],{"href":426,"rel":427},"https:\u002F\u002Fwww.aikido.dev\u002Fblog\u002Fgithub-breached-vs-code-extension",[406],"The credential theft (1Password, Claude Code, AWS) — Aikido",[66,430,431],{},[402,432,435],{"href":433,"rel":434},"https:\u002F\u002Funit42.paloaltonetworks.com\u002Fmonitoring-npm-supply-chain-attacks\u002F",[406],"The Shai-Hulud \u002F UNC6780 campaign on npm — Unit 42 (Palo Alto Networks)",[437,438,439],"style",{},"html pre.shiki code .sQrFR, html code.shiki .sQrFR{--shiki-default:#BDC4CC}html pre.shiki code .sTRMh, html code.shiki .sTRMh{--shiki-default:#ADDCFF}html pre.shiki code .sMAXC, html code.shiki .sMAXC{--shiki-default:#F0F3F6}html pre.shiki code .sCcAr, html code.shiki .sCcAr{--shiki-default:#91CBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s_sBn, html code.shiki .s_sBn{--shiki-default:#FFB757}",{"title":215,"searchDepth":229,"depth":229,"links":441},[442,443,444,445,446,453,454],{"id":16,"depth":229,"text":17},{"id":43,"depth":229,"text":44},{"id":108,"depth":229,"text":109},{"id":165,"depth":229,"text":166},{"id":191,"depth":229,"text":192,"children":447},[448,450,451,452],{"id":199,"depth":449,"text":200},3,{"id":260,"depth":449,"text":261},{"id":338,"depth":449,"text":339},{"id":370,"depth":449,"text":371},{"id":381,"depth":229,"text":382},{"id":395,"depth":229,"text":396},"2026-06-15","The theft of GitHub's internal repositories didn't start by breaking a firewall: it started on a developer's laptop that installed a poisoned extension update. Here's what actually happened, why the weak link is still the human, and how to harden your software supply chain.","md","\u002Fog\u002Fog-default.png",{},true,"extension-vs-code-maliciosa-hackeo-github","\u002Fen\u002Fblog\u002Fmalicious-vs-code-extension-github-breach",{"title":8,"description":456},"en\u002Fblog\u002Fmalicious-vs-code-extension-github-breach",[466,467,468,82],"Security","Supply chain","DevOps","V5fPWquFkl9E3SQVVi4eMUkdcHHWERp_EAkQiaCnzf0",1784782834809]