critical Threat analysis

GlassWorm Developer Supply-Chain Botnet Takedown

CrowdStrike, Google, and Shadowserver disrupted GlassWorm command-and-control on 2026-05-26 after the campaign used Open VSX extensions, npm and Python packages, and poisoned GitHub repositories to maintain access to developer systems.

#supply-chain#vscode#open-vsx#npm#pypi
On this page 0% read

    Executive Summary

    On 2026-05-26 at 14:00 UTC, CrowdStrike says it coordinated with Google and the Shadowserver Foundation to disrupt the GlassWorm botnet’s command-and-control channels, cutting infected developer machines off from new operator instructions and payload delivery CrowdStrike. The takedown does not prove that infected hosts are clean. It gives defenders a short containment window to find developer workstations, CI runners, and build boxes that installed GlassWorm-linked extensions or packages.

    GlassWorm is a developer-tooling supply-chain campaign. CrowdStrike describes trojanized VS Code-compatible extensions, malicious npm and Python packages, and more than 300 poisoned GitHub repositories created or modified with previously stolen developer credentials CrowdStrike. Socket’s April 2026 research adds the clearest registry-level detail: a cluster of 73 Open VSX impersonation extensions, including activated hosts that used extensionPack transitive delivery, bundled native .node installers, or obfuscated JavaScript to retrieve VSIX payloads from GitHub Socket.

    Treat any confirmed GlassWorm hit as a developer identity incident, not just malware cleanup. The affected endpoints can hold GitHub, npm, Open VSX, SSH, cloud, Kubernetes, package-registry, AI-tooling, and wallet credentials.

    Key Facts

    threat_type: "developer tooling supply-chain botnet"
    ecosystems:
      - "Open VSX"
      - "VS Code-compatible extension marketplaces"
      - "npm"
      - "PyPI / Python packages"
      - "GitHub repositories"
    first_seen: "at least early 2025"
    disruption_time_utc: "2026-05-26T14:00:00Z"
    primary_targets:
      - "software developer workstations"
      - "CI/CD runners"
      - "source-code repositories"
      - "package registry publisher accounts"
    execution_paths:
      - "Open VSX extension activation"
      - "VS Code-compatible IDE extension installation"
      - "npm postinstall hooks"
      - "Python setup scripts"
      - "poisoned repository code changes"
    confirmed_malicious_extensions:
      - "outsidestormcommand.monochromator-theme"
      - "keyacrosslaud.auto-loop-for-antigravity"
      - "krundoven.ironplc-fast-hub"
      - "boulderzitunnel.vscode-buddies"
      - "cubedivervolt.html-code-validate"
      - "winnerdomain17.version-lens-tool"
    activated_april_29_hosts:
      - "drobnyak.angular-auto-helper"
      - "galushko.vsclassic-auto-pilot"
      - "gusarev.mermaid-super-studio"
      - "lavrentev.project-live-studio"
      - "lesnitsky.tikbook-easy-lens"
      - "mashulin.vue-easy-studio"
      - "mitrokhin.vsc-easy-studio"
      - "mlechevik.nunjucks-rich-pilot"
      - "mokridin.material-pro-suite"
      - "ovchinin.markdown-live-craft"
      - "peschanov.dbcode-smart-suite"
      - "platarov.podmanager-pro-craft"
      - "polikash.pretty-deep-kit"
      - "porzhnev.swiftformat-deep-hub"
      - "smolyak.slog-smart-studio"
      - "svetelin.industrious-live-hub"
      - "tarasenya.todo-rich-hub"
    hashes:
      - "1b62b7c2ed7cc296ce821f977ef7b22bae59ef1dcdb9a34ae19467ee39bcf168"
      - "4ebfe8f66ca7e9751060b3301b5e8838d6017593cdae748541de83bfa28183bd"
      - "97c275e3406ad6576529f41604ad138c5bdc4297d195bf61b049e14f6b30adfd"
    network_iocs_defanged:
      - "164.92.88[.]210"
      - "github[.]com/SquadMagistrate10/wnxtgkih"
      - "github[.]com/francesca898/dqwffqw"
      - "github[.]com/ColossusQuailPray/oiegjqde"
    confidence: "high"
    last_verified: "2026-05-27"

    Source Confidence & Evidence Mapping

    • confirmed: CrowdStrike states that the GlassWorm botnet disruption occurred on 2026-05-26 at 14:00 UTC with Google and Shadowserver cooperation CrowdStrike.
    • confirmed: CrowdStrike describes four C2 resolution channels: Solana transaction memos, BitTorrent DHT, Google Calendar event titles, and direct server connections CrowdStrike.
    • confirmed: Socket identifies 73 Open VSX impersonation extensions and an April 29 activation wave involving 23 new versions across 22 copycat extensions Socket.
    • confirmed: Socket lists native installer hashes, a downloaded VSIX payload hash, GitHub payload-hosting repositories, six confirmed malicious extensions, and 17 activated April 29 host extensions Socket.
    • likely: Any developer endpoint that ran a malicious extension should be treated as a credential exposure point even if the post-disruption C2 no longer responds.
    • unclear: The complete downstream victim count and every poisoned repository are not publicly enumerated.

    Impact Determination

    ClassificationCriteriaRequired evidenceRequired actionClosure condition
    Confirmed compromiseHost installed a confirmed malicious extension, executed a GlassWorm package hook, matched a listed payload hash, or connected to the CrowdStrike-operated sinkhole address after disruption.Extension inventory, package-manager logs, endpoint file hashes, process telemetry, DNS/proxy/firewall logs, and preserved IDE extension directories.Isolate the host, preserve disk and volatile evidence, rotate developer and CI credentials from a clean system, and review recent repository and registry activity.Endpoint is rebuilt or forensically cleared, tokens are revoked, repository writes are audited, and registry publisher sessions are reset.
    Presumed exposedHost installed one of the activated April 29 Open VSX hosts, used Open VSX auto-update during the exposure window, or ran package installs from a repository later tied to GlassWorm.IDE extension manifests, Open VSX cache, shell history, npm/Python install logs, and EDR process telemetry.Freeze developer credentials, audit GitHub and registry activity, and collect endpoint artifacts before removal.No execution, no suspicious network, and no credential use after exposure are confirmed.
    Potentially exposedDeveloper or CI asset uses VS Code-compatible IDEs, Open VSX, npm, PyPI, or unreviewed GitHub dependencies but no inventory has been collected.Asset inventory, extension lists, package-lock exports, GitHub audit logs, and proxy telemetry.Run the endpoint and network hunts below.Scope is mapped to confirmed, presumed, or not exposed.
    Not exposedNo listed extensions, hashes, GitHub payload repositories, sinkhole traffic, suspicious IDE installs, or GlassWorm package execution appears in complete telemetry.Negative results from endpoint, package, repository, and network searches covering the full exposure period.Record evidence and keep auto-update controls enabled.Evidence package is retained with query timestamps.
    UnknownIDE extension directories, network telemetry, or developer identity logs are unavailable.Gap statement naming missing systems and time ranges.Decide rotations based on the highest-value credentials reachable from affected developers.Missing telemetry is recovered or risk acceptance is approved.

    Timeline

    • Early 2025: CrowdStrike says GlassWorm operators were already targeting software developers through the open-source supply chain CrowdStrike.
    • 2026-04-25: Socket publishes the 73-extension Open VSX sleeper cluster report Socket.
    • 2026-04-29T18:15:00Z to 2026-04-29T19:34:00Z: Socket observes an activation wave pushing 23 new versions across 22 copycat extensions Socket.
    • 2026-05-26T14:00:00Z: CrowdStrike coordinates simultaneous disruption of GlassWorm C2 channels CrowdStrike.
    • 2026-05-27: This site adds a standalone GlassWorm response article because no local post previously covered the campaign.

    What Happened

    GlassWorm blended social engineering with developer ecosystem mechanics. Socket’s Open VSX cluster used copied names, icons, descriptions, and README content to impersonate legitimate extensions. Some extensions started as sleepers, then later received updates that pulled or installed malicious payloads through extension dependencies, extensionPack, GitHub-hosted VSIX files, native .node modules, or obfuscated JavaScript Socket.

    CrowdStrike’s takedown write-up expands the campaign boundary beyond Open VSX. It says GlassWorm used VS Code-compatible extensions, npm and Python package execution paths, and poisoned GitHub repositories, with a full-featured Node.js RAT and resilient C2 resolution across blockchain, peer-to-peer, public calendar, and direct server infrastructure CrowdStrike.

    Technical Analysis

    Initial Access

    Initial access can arrive through a developer installing a cloned extension, an auto-updated Open VSX extension, a malicious npm package with a postinstall hook, a Python package setup script, or a poisoned GitHub repository. The most defensible public extension anchors are the six confirmed malicious extensions and the April 29 activated host extensions listed by Socket Socket.

    Loader Behavior

    Socket observed two important loader styles. One uses platform-specific native .node binaries loaded from extension activation code. Another uses obfuscated JavaScript to retrieve a VSIX payload from GitHub and install it into multiple IDEs with the --install-extension flow Socket. This means source review of the installed extension package may miss the behavior that ultimately executes.

    Command And Control

    CrowdStrike says GlassWorm used four C2 layers: Solana memo dead drops, BitTorrent DHT configuration lookups, Google Calendar event-title dead drops, and direct VPS-hosted server connections CrowdStrike. After the takedown, CrowdStrike shared a sinkhole-style indicator: infected machines now beacon to 164.92.88[.]210 CrowdStrike.

    Credential Risk

    The campaign targets developer systems because one endpoint can hold keys to source control, package publishing, cloud deployments, CI runners, and internal services. Rotate credentials in dependency order: registry tokens and GitHub sessions first, then cloud and CI/CD secrets, then SSH keys and workstation-local secrets.

    Detection and Hunting

    Script: local repository and exported telemetry scope

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
    LOG_ROOT = os.environ.get("LOG_ROOT", "")
    OUT = Path(os.environ.get("OUT", "hp-glassworm-developer-supply-chain-botnet-scope"))
    SINCE = "2026-04-29T18:15:00Z"
    UNTIL = "2026-05-27T23:59:59Z"
    
    PACKAGES = [
    ]
    VERSIONS = [
    ]
    FILES = [
    ]
    DOMAINS = [
      "www.crowdstrike.com",
    ]
    URLS = [
      "https://www.crowdstrike.com/en-us/blog/inside-crowdstrike-takedown-of-a-developer-targeting-botnet/",
      "https://socket.dev/blog/73-open-vsx-sleeper-extensions-glassworm",
    ]
    IPS = [
      "164.92.88.210",
    ]
    HASHES = [
      "1b62b7c2ed7cc296ce821f977ef7b22bae59ef1dcdb9a34ae19467ee39bcf168",
      "4ebfe8f66ca7e9751060b3301b5e8838d6017593cdae748541de83bfa28183bd",
      "97c275e3406ad6576529f41604ad138c5bdc4297d195bf61b049e14f6b30adfd",
    ]
    PROCESS_PATTERNS = [
    ]
    NETWORK_PATTERNS = [
    ]
    
    # Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
    # Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
    
    OUT.mkdir(parents=True, exist_ok=True)
    indicators_file = OUT / "indicators.txt"
    
    # Collect unique indicators
    indicators = set()
    for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
        for val in group:
            if val:
                indicators.add(val)
    
    with open(indicators_file, "w") as f:
        for ind in sorted(indicators):
            f.write(ind + "\n")
    
    print(f"[+] Written unique selectors to {indicators_file}")
    
    # Walk local directory
    print(f"[+] Scanning directory: {ROOT} for selectors...")
    matches = []
    exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
    for root, dirs, filenames in os.walk(ROOT):
        dirs[:] = [d for d in dirs if d not in exclude_dirs]
        for filename in filenames:
            filepath = Path(root) / filename
            try:
                content = filepath.read_text(errors="ignore")
                for ind in indicators:
                    if ind in content:
                        matches.append(f"{filepath}: found '{ind}'")
            except Exception:
                pass
    
    if matches:
        (OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
        print(f"[!] Found {len(matches)} matches in codebase!")
    
    # Optional Log Scanning
    if LOG_ROOT and os.path.exists(LOG_ROOT):
        print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
        log_matches = []
        for root, _, filenames in os.walk(LOG_ROOT):
            for filename in filenames:
                filepath = Path(root) / filename
                try:
                    content = filepath.read_text(errors="ignore")
                    for ind in indicators:
                        if ind in content:
                            log_matches.append(f"{filepath}: found '{ind}'")
                except Exception:
                    pass
        if log_matches:
            (OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
            print(f"[!] Found {len(log_matches)} matches in logs!")
    
        if PACKAGES:
            registry_dir = OUT / "registry"
            registry_dir.mkdir(exist_ok=True)
    
    print(f"[+] Wrote scope artifacts under {OUT}")

    Downstream Abuse Audits

    Script: GitHub organization run, release, secret, and workflow audit

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    if "ORG" not in os.environ:
        print("ERROR: Set ORG environment variable to the GitHub organization to audit", file=sys.stderr)
        sys.exit(1)
    
    ORG = os.environ["ORG"]
    SINCE = "2026-04-29T18:15:00Z"
    UNTIL = "2026-05-27T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-glassworm-developer-supply-chain-botnet-github-audit"))
    
    SELECTORS = [
      "www.crowdstrike.com",
      "https://www.crowdstrike.com/en-us/blog/inside-crowdstrike-takedown-of-a-developer-targeting-botnet/",
      "https://socket.dev/blog/73-open-vsx-sleeper-extensions-glassworm",
      "164.92.88.210",
      "1b62b7c2ed7cc296ce821f977ef7b22bae59ef1dcdb9a34ae19467ee39bcf168",
      "4ebfe8f66ca7e9751060b3301b5e8838d6017593cdae748541de83bfa28183bd",
      "97c275e3406ad6576529f41604ad138c5bdc4297d195bf61b049e14f6b30adfd",
    ]
    
    # Positive signal: a workflow run, release, secret, key, package, or workflow change overlaps the exposure window and references an incident selector.
    # Remediation trigger: unauthorized post-exposure write activity or a secret-bearing run matching an incident selector requires token revocation and downstream cloud/registry review.
    
    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "runs").mkdir(exist_ok=True)
    (OUT / "logs").mkdir(exist_ok=True)
    (OUT / "repos").mkdir(exist_ok=True)
    
    # 1. Write incident-selectors file
    selectors_file = OUT / "incident-selectors.txt"
    with open(selectors_file, "w") as sf:
        for s in SELECTORS:
            if s:
                sf.write(s + "\n")
    
    # 2. Get list of repos
    print(f"[+] Fetching repositories for organization: {ORG}")
    repo_res = subprocess.run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner"], capture_output=True, text=True)
    if repo_res.returncode != 0:
        print(f"[-] Failed to fetch repos: {repo_res.stderr}", file=sys.stderr)
        sys.exit(1)
    
    repos = [r["nameWithOwner"] for r in json.loads(repo_res.stdout)]
    
    for repo in repos:
        safe_repo = repo.replace("/", "__")
        print(f"[+] Auditing repository: {repo}")
        
        # Check runs in the window
        runs_res = subprocess.run([
            "gh", "api", f"/repos/{repo}/actions/runs",
            "-f", "per_page=100",
            "-f", f"created=>={SINCE}",
            "--paginate"
        ], capture_output=True, text=True)
        
        if runs_res.returncode == 0:
            try:
                all_runs = json.loads(runs_res.stdout).get("workflow_runs", [])
                filtered_runs = [r for r in all_runs if r["created_at"] <= UNTIL]
                
                if filtered_runs:
                    with open(OUT / "runs" / f"{safe_repo}-runs.jsonl", "w") as rf:
                        for run in filtered_runs:
                            rf.write(json.dumps(run) + "\n")
                            
                            # Fetch log dynamically
                            run_id = str(run["id"])
                            log_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--log"], capture_output=True, text=True)
                            if log_res.returncode == 0:
                                (OUT / "logs" / f"{safe_repo}-{run_id}.log").write_text(log_res.stdout)
                                
                            # Fetch details
                            view_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--json", "databaseId,workflowName,headSha,event,createdAt,jobs"], capture_output=True, text=True)
                            if view_res.returncode == 0:
                                (OUT / "runs" / f"{safe_repo}-{run_id}.json").write_text(view_res.stdout)
            except Exception as e:
                print(f"[-] Error parsing runs for {repo}: {e}")
    
        # Check releases in window
        subprocess.run(["gh", "api", f"/repos/{repo}/releases", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check repo secrets updated in window
        subprocess.run(["gh", "api", f"/repos/{repo}/actions/secrets", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check deploy keys
        subprocess.run(["gh", "api", f"/repos/{repo}/keys", "-f", "per_page=100", "--paginate"], capture_output=True)
    
    # Scan output directory for any indicator selector matches
    print("[+] Scanning gathered telemetry for indicator matches...")
    subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)], capture_output=False)
    
    print(f"[+] Wrote GitHub audit artifacts under {OUT}")

    Script: cloud OIDC and deployment credential follow-on audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2026-04-29T18:15:00Z"
    UNTIL = "2026-05-27T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-glassworm-developer-supply-chain-botnet-cloud-audit"))
    AWS_REGIONS = os.environ.get("AWS_REGIONS", "us-east-1").split(",")
    
    # Positive signal: token exchange or privileged write activity occurs in the exposure window from GitHub, CI/CD, package registry, or deployment automation identity.
    # Remediation trigger: unexpected write, deploy, IAM, secret, or registry activity tied to an exposed CI/CD path requires trust-policy disablement and credential rotation.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    # 1. AWS CloudTrail Audit
    print("[+] Querying AWS CloudTrail for Web Identity token exchanges...")
    aws_events = []
    for region in AWS_REGIONS:
        res = subprocess.run([
            "aws", "cloudtrail", "lookup-events",
            "--region", region,
            "--start-time", SINCE,
            "--end-time", UNTIL,
            "--lookup-attributes", "AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity",
            "--output", "json"
        ], capture_output=True, text=True)
        
        if res.returncode == 0:
            try:
                events = json.loads(res.stdout).get("Events", [])
                for event in events:
                    ct = json.loads(event.get("CloudTrailEvent", "{}"))
                    ct["region"] = region
                    aws_events.append(ct)
            except Exception as e:
                print(f"[-] Error parsing AWS CloudTrail events: {e}")
    
    if aws_events:
        with open(OUT / "aws-assume-role-with-web-identity.jsonl", "w") as f:
            for ev in aws_events:
                f.write(json.dumps(ev) + "\n")
                
        # Audit follow-on events for returned access keys
        for ev in aws_events:
            access_key = ev.get("responseElements", {}).get("credentials", {}).get("accessKeyId")
            region = ev.get("region", "us-east-1")
            if access_key:
                print(f"[+] Enumerating AWS events for AccessKey: {access_key}")
                f_res = subprocess.run([
                    "aws", "cloudtrail", "lookup-events",
                    "--region", region,
                    "--start-time", SINCE,
                    "--end-time", UNTIL,
                    "--lookup-attributes", f"AttributeKey=AccessKeyId,AttributeValue={access_key}",
                    "--output", "json"
                ], capture_output=True, text=True)
                if f_res.returncode == 0:
                    try:
                        f_events = json.loads(f_res.stdout).get("Events", [])
                        with open(OUT / "aws-follow-on-api-calls.jsonl", "a") as ff:
                            for fe in f_events:
                                ff.write(fe.get("CloudTrailEvent", "{}") + "\n")
                    except Exception as e:
                        print(f"[-] Error writing follow-on events: {e}")
    
    # 2. Azure Activity Log Audit
    print("[+] Querying Azure activity logs...")
    az_res = subprocess.run([
        "az", "monitor", "activity-log", "list",
        "--start-time", SINCE,
        "--end-time", UNTIL,
        "--query", "[?contains(operationName.value, 'write') || contains(operationName.value, 'delete') || contains(operationName.value, 'Microsoft.ManagedIdentity')]",
        "-o", "json"
    ], capture_output=True, text=True)
    
    if az_res.returncode == 0:
        (OUT / "azure-write-delete-activity.json").write_text(az_res.stdout)
    
    # 3. GCP Logging Audit
    print("[+] Querying GCP Cloud Logging...")
    gcp_filter = f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName="google.sts.v1.SecurityTokenService.ExchangeToken" OR protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy")'
    gcp_res = subprocess.run([
        "gcloud", "logging", "read", gcp_filter,
        "--format", "json"
    ], capture_output=True, text=True)
    
    if gcp_res.returncode == 0:
        (OUT / "gcp-token-and-iam-activity.json").write_text(gcp_res.stdout)
    
    print(f"[+] Wrote cloud audit artifacts under {OUT}")

    Script: registry metadata and artifact audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2026-04-29T18:15:00Z"
    OUT = Path(os.environ.get("OUT", "hp-glassworm-developer-supply-chain-botnet-registry-audit"))
    PACKAGES = [
    ]
    VERSIONS = [
    ]
    
    # Positive signal: workflow files or extensions reference the affected action/extension names or versions.
    # Remediation trigger: exposed secrets or OIDC federation policies must be immediately rotated.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    with open(OUT / "affected-versions.txt", "w") as av:
        for version in VERSIONS:
            if version:
                av.write(version + "\n")
    
        # 1. Search local workspace files for the affected actions/extensions
        print("[+] Scanning workspace workflows for selectors...")
        for file in Path(".").glob(".github/workflows/**/*.yml"):
            subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(OUT / "affected-versions.txt"), str(file)])
    
        # 2. HOW TO ROTATE EXPOSED GITHUB ACTIONS SECRETS:
        # Overwrite compromised secrets with newly generated credentials:
        # subprocess.run(["gh", "secret", "set", "COMPROMISED_SECRET_NAME", "--body", "my-new-secret-value", "--repo", "my-org/my-repo"])
        # For organization-level secrets:
        # subprocess.run(["gh", "secret", "set", "COMPROMISED_SECRET_NAME", "--org", "my-org", "--visibility", "private"])
        # Revoke compromised OIDC federated trust credentials in AWS/GCP and redeploy the IAM trust policy:
        # subprocess.run(["aws", "iam", "update-assume-role-policy", "--role-name", "my-role-name", "--policy-document", "file://new-clean-trust-policy.json"])
    
    print(f"[+] Wrote registry audit artifacts under {OUT}")

    Remediation & Credential Rotation Plan

    Containment

    1. Remove affected developer hosts from the network after collecting volatile process, network, and extension inventory evidence.
    2. Disable IDE extension auto-update for VS Code-compatible clients until extension provenance is reviewed.
    3. Block or alert on the listed GitHub payload repositories and the CrowdStrike sinkhole indicator in proxy, DNS, and firewall telemetry.

    Eradication

    1. Uninstall listed extensions and preserve their directories first.
    2. Rebuild confirmed developer endpoints where token theft, RAT execution, or sinkhole traffic is found.
    3. Revoke active GitHub, npm, PyPI, Open VSX, cloud, SSH, and CI/CD tokens for affected users from a clean administrative workstation.

    Recovery

    1. Re-issue package registry tokens with least privilege and short expiry.
    2. Require signed commits, protected branches, and package publishing approval for affected repositories.
    3. Re-enable extension installation only from allowlisted publishers and pinned extension IDs.

    Closure Gates

    • No listed extension IDs, payload repositories, hashes, or sinkhole traffic remain in endpoint and network telemetry.
    • GitHub and package registry audit logs show no unexplained repository writes, workflow changes, package publications, or new tokens during the exposure window.
    • All credentials reachable from confirmed hosts have been revoked and reissued from a clean environment.

    Sources

    1. CrowdStrike: Disrupting Glassworm: Inside CrowdStrike’s Takedown of a Developer-Targeting Botnet
    2. Socket: 73 Open VSX Sleeper Extensions Linked to GlassWorm Show New Malware Activations
    3. The Hacker News: GlassWorm Malware Takedown Disrupts Developer Supply Chain Attack Infrastructure
    4. Open VSX Registry

    Indicators of Compromise

    6 IOCs
    Defang IOCs
    domain www.crowdstrike.com www[.]crowdstrike[.]com
    url https://www.crowdstrike.com/en-us/blog/inside-crowdstrike-takedown-of-a-developer-targeting-botnet/ hxxps://www[.]crowdstrike[.]com/en-us/blog/inside-crowdstrike-takedown-of-a-developer-targeting-botnet/
    url https://socket.dev/blog/73-open-vsx-sleeper-extensions-glassworm hxxps://socket[.]dev/blog/73-open-vsx-sleeper-extensions-glassworm
    hash 1b62b7c2ed7cc296ce821f977ef7b22bae59ef1dcdb9a34ae19467ee39bcf168 1b62b7c2ed7cc296ce821f977ef7b22bae59ef1dcdb9a34ae19467ee39bcf168
    hash 4ebfe8f66ca7e9751060b3301b5e8838d6017593cdae748541de83bfa28183bd 4ebfe8f66ca7e9751060b3301b5e8838d6017593cdae748541de83bfa28183bd
    hash 97c275e3406ad6576529f41604ad138c5bdc4297d195bf61b049e14f6b30adfd 97c275e3406ad6576529f41604ad138c5bdc4297d195bf61b049e14f6b30adfd