Open VSX "Evil Twin" Extension Campaign
Executive Summary
Between 2026-07-26 and 2026-08-01, 77 counterfeit extensions were published to the Open VSX registry. Each one republished the name, namespace and description of a real VS Code Marketplace extension at a deliberately low version (almost always 0.0.1) from an account that did not own the namespace. The bundled extension.js was replaced with a beacon. All 77 contact mangorbit[.]com, a domain registered 2026-07-15, eleven days before the first upload, through a registrar that redacts registrant details.
The attacker objective is organisational reconnaissance of software engineering estates, not code execution or credential theft. 58 packages ("payload A") are 1.6–3.3 KB and send little more than a hostname. The other 19 ("payload B") are roughly 10 KB and, four to five seconds after editor activation, assemble a single request containing the hostname and OS username, editor name/version/host kind/machine ID, platform, architecture, locale, timezone, workspace folder name and full filesystem path; then they read .git/config and .git/HEAD for the origin and upstream remote host plus organisation, the domain portion of the configured commit email, the current branch and the HEAD commit SHA; then they read CI identity values — GITHUB_REPOSITORY, CI_PROJECT_PATH, the Azure DevOps collection URI, the Buildkite organisation slug, the CircleCI project username, the Codespace name and the Gitpod workspace context URL. On a build runner or a cloud development environment that is the full private repository path, not just the organisation. Up to 60 installed extension IDs, the proxy hostname and the editor's telemetry opt-out state go with it, and the data is sent regardless of that opt-out.
The listings carried a "Telemetry" section that described most of the collection accurately, which is what let the packages pass a human skim. One claim is false: the listing says CI data is marker names only, never values. The code sends both. The listings are also silent on three behaviours the code performs — reporting whether the workspace's own devcontainer.json or .vscode/extensions.json pulled the extension in, reading the editor telemetry preference, and querying a DNS TXT record at _beacon.<domain> for a replacement collector base URL when every hardcoded endpoint fails.
Two properties make this a durable hunt rather than a one-week IOC sweep. First, removal does not un-provision: Open VSX pulled the packages on 2026-08-03, but any image, golden workstation, devcontainer or provisioning script already pinned to one of these names keeps the code, and the beacon fires on every editor start with a retry schedule that persists for seven days. Second, there is nothing for a scanner to match — no eval, no spawned shell, no obfuscation, no write to disk. The signal is behavioural: an extension whose advertised job is inserting file-header comments has no reason to read .git/config, enumerate CI environment variables, and open a connection to an eleven-day-old domain seconds after startup.
Defender priority: block *.mangorbit[.]com at DNS and egress today — Manifold confirms all 77 samples reference it — then inventory what your fleet and your CI images actually have installed, not what your configs request. Q1 and Q11 answer "were we hit"; Q3, Q4 and Q7 are the queries that will still work when the operator moves collectors over the _beacon TXT channel.
Source Review & Web Hunter Notes
| Tier | Source | Key finding | Carry forward |
|---|---|---|---|
| 1 · Primary | Manifold Security — 77 "evil twin" Open VSX extensions (Ax Sharma, Cody Nash, 2026-08-04) | Original research. Full indicator set, the two payload classes, retry schedule, TXT failover, and a 77-row table with extension ID, version, payload class, beacon hosts, first-seen date and VSIX SHA-256. | Yes — every atomic IOC in this pack traces to this source |
| 2 · Corroborating | The Hacker News — Open VSX Removes 77 Malicious Evil Twin Extensions (2026-08-05) | Independent confirmation of dates, counts, the shared exfil domain and the collected field set. Names the 19 recon extension IDs explicitly. | Yes — corroboration for the 19 recon IDs |
| 2 · Corroborating | BleepingComputer — 77 Open VSX extensions found harvesting developer info (2026-08-04) | Confirms the four collector subdomains, the per-package tracking identifier, the 7-day retry, the TXT-record fallback, and that no source code, credentials, tokens, SSH material or browser data were accessed. | Yes — negative scope (what is not stolen) shapes the severity call |
| 2 · Corroborating | Cyber Security News — 77 Evil Twin Open VSX Extensions Exfiltrate Private Git Repository and CI Data (2026-08-05) | Carries a defanged IOC table: domain, four hosts, the three URI paths, the _beacon TXT mechanism with its base=https:// response prefix, and the User-Agent. | Yes — second independent copy of the network IOC set |
| 2 · Corroborating | GBHackers — Fake Open VSX Extensions Hijack AMD, Azure, Salesforce and Government Namespaces | Same indicator table; adds the detail that collector subdomains answer "ok" to any request and that the code treats any HTTP response, including an error status, as delivery success. | Yes — informs the "no payload returned" detection assumption |
| — · Dropped | SC World brief | Returned HTTP 403 to both the fetcher and the crawler; no text could be snapshotted. | No — an uncaptured source cannot back an indicator, so it is not cited and contributes nothing |
Web-hunter decisions
- Prompt-injection screen: all five captured sources were scanned for agent-directed text (instructions to run commands, fetch further URLs "for the real IOCs", claims of authorization, or attempts to address the reader as an automated agent). None found. All fetched content was treated as data.
- Second failover domain deliberately excluded. Manifold states a subset of samples carry a third endpoint on a second registered domain and explicitly declines to publish it. It is therefore not in this pack. Q3 and Q4 are the queries that cover it: they hunt the behaviour (a code editor resolving or contacting a rare external destination) rather than the name.
- Randomised subdomain, one observed value. One sample used a randomised label under
cb.mangorbit[.]com. Exactly one such host appears in the published data and it ships as an IOC; the general case is covered by the suffix regex in Q1 and by blocking the whole zone. - Payload class carried through. The A/B split is not cosmetic — payload B is the one that leaks private repository paths, so it drives severity, containment scope and which IDs go into the high-fidelity Q5 regex.
- Legitimate publishers are victims here. The extension IDs below are the counterfeits' IDs, which reuse real projects' names and namespaces without those projects' knowledge. The genuine extensions are unaffected. Do not treat the vendor names in the ID strings as compromised parties.
Hunt Brief & Attack Chain
Attack chain
| # | Step | Telemetry | Hunt angle |
|---|---|---|---|
| 1 | Operator registers mangorbit[.]com (2026-07-15) with redacted registrant, three-year term to 2029-07-15; stands up pulse, pulse2, api and randomised cb collectors that answer any request with "ok". | Passive DNS, WHOIS, newly-registered-domain feeds | Newly-registered-domain enrichment on any destination first contacted by a developer endpoint |
| 2 | 77 counterfeit packages published to Open VSX from accounts that do not own the namespace, at version 0.0.1–0.0.6, reusing real extension names, namespaces and descriptions. | Registry/mirror download logs; internal Open VSX mirror audit | Mirror logs for the 77 IDs; any extension pulled at a suspiciously low version under a well-known namespace |
| 3 | Install by name — a human choosing from a listing, or an agent / devcontainer.json / .vscode/extensions.json / provisioning script resolving the name with no publisher check. | ProcessRollup2 (--install-extension), ZipFileWritten (VSIX), repo-config file writes | Q5 CLI install of a counterfeit ID · Q6 VSIX written · Q8 repo manifests |
| 4 | On editor activation the extension renders a status-bar checkmark and registers one "it is active" command. It provides no advertised functionality. | None directly observable in EDR | GAP — no endpoint telemetry distinguishes a no-op extension from a working one |
| 5 | Payload A (58 samples) sends hostname, sometimes workspace folder name and editor version, plus a per-package tracking ID, by POST to /api/v1/metrics or /api/v1/events or by GET query string. | DnsRequest, NetworkConnectIP4, proxy/SWG logs | Q1 collector DNS · Q4 editor egress · Q9/Q10 UA and URI paths |
| 6 | Payload B (19 samples) waits 4–5 s, then reads .git/config and .git/HEAD, enumerates CI environment values, lists up to 60 installed extension IDs, reads the proxy hostname and the telemetry opt-out flag, and ships it all in one request. | FileOpenInfo on .git\config, DnsRequest/NetworkConnectIP4 from the extension host | Q7 editor reads .git\config · Q3 rare domain from an editor process |
| 7 | Delivery resilience: multiple endpoints in one array, any HTTP response (including an error status) counted as success, retries at ~15 min, ~50 min, ~3.5 h then every 7–8 h, resuming on every editor restart, giving up only after 7 days. | DnsRequest/NetworkConnectIP4 cadence | Beaconing-cadence shape on the surviving destinations from Q4 |
| 8 | Takedown contingency: if every endpoint fails, query the DNS TXT record at _beacon.<domain> and take a replacement base URL from a base=https:// prefixed response. | DnsRequest (TXT) | Q2 — the one query that survives the operator moving infrastructure |
Hunt hypotheses (ordered by fidelity)
| # | Hypothesis | MITRE | Falcon events | Expected FP | Conf |
|---|---|---|---|---|---|
| H1 | A developer or CI endpoint has resolved mangorbit[.]com or any subdomain of it. | T1071.004, T1041 | DnsRequest | Threat-intel tooling detonating samples; researcher workstations | HIGH |
| H2 | A VSIX matching one of the 77 published SHA-256 hashes exists on an endpoint or in a build image. | T1195.002, T1554 | ZipFileWritten, ProcessRollup2 | Malware-analysis storage | HIGH |
| H3 | One of the 19 recon extension IDs was installed from a command line or pinned in a repo config. | T1176.002, T1204 | ProcessRollup2, file writes | Deliberate research installs | HIGH |
| H4 | An endpoint has issued a DNS TXT lookup for a _beacon.* label — the operator's post-takedown relocation channel. | T1071.004, T1568 | DnsRequest | Very low; no common tooling uses this label | MEDIUM |
| H5 | A code editor or its extension host resolves a rare external domain that few or no other hosts in the fleet resolve. | T1176.002, T1071.001 | DnsRequest | Language servers, registries, AI assistants, vendor telemetry | MEDIUM |
| H6 | A code editor or extension-host process opens an external connection to a destination almost nothing else in the fleet talks to. | T1041 | NetworkConnectIP4 | Same as H5, plus CDN churn | MEDIUM |
| H7 | The editor or extension host reads .git\config / .git\HEAD and opens an external connection within seconds. | T1005, T1083 | FileOpenInfo + NetworkConnectIP4 | High — the built-in Git extension and GitLens do this constantly | MEDIUM |
| H8 | Repository or container configs in the estate pin an extension ID from the counterfeit list, so every rebuild re-installs it. | T1195.002 | File writes; source-control search | High as a standalone signal; low once cross-referenced with the ID list | LOW |
| H9 | Outbound HTTP carries the User-Agent vscode-ext-metrics/1.0 or hits the collector URI paths. | T1071.001 | Proxy / SWG logs in NG-SIEM | Low for the UA; medium for the generic REST paths alone | HIGH |
Consolidated IOC Table
Network & behavioural indicators
| Type | Value | Conf | Action | Context | Expiry |
|---|---|---|---|---|---|
| Domain | mangorbit[.]com | high | detect / block | Apex. Registered 2026-07-15, expires 2029-07-15, registrar redacts registrant. Apex serves a generic landing page. All 77 samples reference the zone. | 2027-02-08 |
| Host | pulse.mangorbit[.]com | high | detect / block | Primary collector. Answers "ok" to any request. | 2027-02-08 |
| Host | pulse2.mangorbit[.]com | high | detect / block | Second collector, paired with pulse in the endpoint array of most samples. | 2027-02-08 |
| Host | api.mangorbit[.]com | high | detect / block | Used by one observed sample (wghats.vscode-nxunit-test-adapter). | 2027-02-08 |
| Host | cb.mangorbit[.]com | high | detect / block | Parent of randomised collector labels. Block the whole zone rather than enumerating children. | 2027-02-08 |
| Host | d9j3drkeib4sa01kk82014tt97wxnndam.cb.mangorbit[.]com | high | hunt | The single published randomised label, used by lego-education.ev3-micropython 0.0.2. | 2026-11-08 |
| URI path | /api/v1/metrics | medium | hunt | POST collector path. Generic on its own — pair with the host or the User-Agent. | 2026-11-08 |
| URI path | /api/v1/events | medium | hunt | Alternate POST collector path. | 2026-11-08 |
| URI path | /t/<24-hex tracking id> | medium | hunt | Per-package tracking endpoint; the 24-hex value identifies which counterfeit extension is installed. | 2026-11-08 |
| DNS | TXT lookup for _beacon.<domain> | high | detect | Takedown contingency. Response is prefixed base=https:// and supplies a replacement collector base URL. Survives any domain block. | 2027-02-08 |
| User-Agent | vscode-ext-metrics/1.0 | high | detect | Distinctive and not used by any legitimate editor component. | 2027-02-08 |
| File name | extension.js | low | enrich | The swapped file inside every counterfeit VSIX. Present in essentially every legitimate extension too — context only, never alert on it. | 2026-09-08 |
| Behaviour | Extension reads .git/config + .git/HEAD then egresses within ~5 s of activation | high | hunt | The core recon-variant signature. Domain-independent; survives infrastructure rotation. | n/a — behavioural |
| Behaviour | Beacon retries at ~15 min / ~50 min / ~3.5 h, then every 7–8 h, for 7 days, resuming on every editor restart | high | hunt | Cadence fingerprint for the recon variant. Use for beaconing analysis on surviving destinations. | n/a — behavioural |
All 77 counterfeit packages
Payload A = lightweight beacon (58, roughly 1.6–3.3 KB). Payload B = reconnaissance payload (19, roughly 10 KB, Git + CI collection). "First seen" is the date Manifold's systems archived the sample. Scroll the table sideways for the VSIX SHA-256.
| # | Extension ID (counterfeit) | Ver | Payload | Beacon hosts | First seen | SHA-256 (VSIX) |
|---|---|---|---|---|---|---|
| 1 | lego-education.ev3-micropython | 0.0.2 | A · light | d9j3drkeib4sa01kk82014tt97wxnndam.cb.mangorbit[.]com | 2026-07-26 | b8cd87d5e36f167f51ed6a6d2e824f76dc1b3792de9c10091e46f87ad07f5d5a |
| 2 | better-ts-errors.better-ts-errors | 0.0.1 | A · light | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-27 | 8763bda130fe4305c4d13c25584c981424dc47d3de3df46ae0ef05466b05526a |
| 3 | groksrc.ruby | 0.0.1 | A · light | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-27 | 415cef17b6560643c0e4dddccd956ae6887c369d84c7f45be638a76dc4930dc1 |
| 4 | maptz.regionfolder | 0.0.1 | A · light | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-27 | 7342f26ff97c0f8dc29edb1c5a5460bafcaacbc84356020a785628b36156ab6e |
| 5 | mitsuhiko.insta | 0.0.1 | A · light | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-27 | 5d62cfb190145bd21f930d4c21da01e8dc951e4112992790361e2fd4b09e66f6 |
| 6 | SBSnippets.pytorch-snippets | 0.0.1 | A · light | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-27 | d51e331ae110eb37d453e78ac4cf3063d4606ebd2357d8ee4342fd8f06933e20 |
| 7 | slb235.vscode-coffeelint | 0.0.1 | A · light | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-27 | 0ccd62eb6c1155c252248a98c8ff8e1a45a5cc721d6725511effb770037690d5 |
| 8 | amd.gaia-vscode | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | 1b775ad3ca61addf5bf3e326951314d80ab526cbab0d745edb3e2bd85de2fcd9 |
| 9 | artsy.artsy-studio-extension-pack | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | a3dbc0a35a781c61c34bf41e9d21eadbf2e77d18531236d1d9c1e622aaf418ef |
| 10 | configcat.configcat-feature-flags | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | 941e9cf4fb7a1cdadb1a0e64fce3262426847f167ad255a3f724d77f2d0ee103 |
| 11 | iotaledger.iota-move | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | fde634f337ab81b3d6bfa320047ff4fe450556fd90f44a30612d549b996edc86 |
| 12 | marketplace.visualstudio | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | 9f8a69c193c3c4b83db18149408a9687d1cb6ca149ffa3a1ca6d72e95e3b6954 |
| 13 | obyte.oscript-vscode-plugin | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | 7179f11710f242a578bd50187dadd7fa2ba0062e11e1a631edcd75417bda3f7e |
| 14 | openeuphoria.vscode-euphoria | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | 0e99e23329f3ca908d7a88ccf102e4b10bd9b67f24fc79408aae15d5d10dfd0a |
| 15 | oss.sfmc-devtools-vscode | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | fc3a32f59300c7e8b3e486f23146224beb658d10ae04098a9e27faf341356fbe |
| 16 | rumbledb.jsoniq-vscode | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | 498b0c9632959eb37cc278f35561f0736c91884b370251dfebae07ff9a942bc3 |
| 17 | ssagov.uef-snippets | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | 95fbdc4c81e14a46dd8f3eb5c4207567baae99712e3ccf8644af09680f3caf5e |
| 18 | taskfile.vscode-task | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-28 | 2526c160b6a08377d02e035fc166e91e8029b997fa6c91169640c2ad2c2ec60b |
| 19 | doi.fileheadercomment | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-30 | 15cd6a4909161c406b8428100d688999377e399ae13aa4da49bb73744933807d |
| 20 | MengsiCode.vscode-django-boilerplate | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-30 | d35b690502fd3b73312aacf62dc700ac1e43bbc01b53d129096c2c0cdf92a1c4 |
| 21 | move.move-analyzer | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-30 | 3b407a548040927802f2cfa05b81eabf48f8b03785ba9fd69a3caa3981a7bc22 |
| 22 | uavcan.dsdl | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-30 | d19c076fc8213617f889a313a3da1565d8364e35c53290fa96400183127b1187 |
| 23 | vs-publisher-988541.apexsql-power-tools | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-30 | 9e148da1d338c32286b54a102ba3a42c7c2627855c3cc820122cff2ee4fc0d90 |
| 24 | 365businessdevelopment.bdev-al-xml-doc | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 03d81fce32d898e4791672e8e324ca8854d4667f56131f9c2b9d13d66faade4f |
| 25 | AcademiaDosDevs.javafx | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 5a1fb5758a6f4b1635d1e1d3fd83c2c13376f466508a949cdad23114af82db45 |
| 26 | AlDuncanson.react-hooks-snippets | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 508d3fb15d317846239e8d1f89ab5a5f31242d159f102602cfbf671103658bd3 |
| 27 | Alex-Chen.gitee-code-settings-sync | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | 7dc683fc3e0acf757cb48091c259bd71a7e3fe985f83d5b40468fa5b58e70804 |
| 28 | AndenetAlexander.vim-cheatsheet | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 3f97f02073caab6208d103693b13105f9dd42daca379e07953e433bd50ec5d3a |
| 29 | AndreyVolosovich.monokai-st3 | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 3e9c96b913b54af9d50966b27b878750c3ae9d8e7944c4437a40d5a5cbc5ef21 |
| 30 | angelo-breuer.license-header-manager | 0.0.2 | A · light | pulse.mangorbit[.]com | 2026-07-31 | aac1efa84293929006436160f77e58e70d9025fc0b320ecc64953e066785a0fa |
| 31 | AshhadDevLab.customtkinter-snippets | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 8e90e637bd7f3a2eaee077f216610248bf4d41c8d1f7880a2d2623be13be339f |
| 32 | AzureADB2CTools.aadb2c | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | 410e6b62b5e62cd8603b374f34e5ec6dad62bb95435fa320e08a850fa359f8c5 |
| 33 | AzurePolicy.azurepolicyextension | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 805c0c50271afbe94301d376cc5eb191feb7a409ea05f7771dcb4a07dcb0442a |
| 34 | BartmanAbyss.amiga-debug | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | 9afe7dd8f3eece28719edac1572ca7adbf307fdb29580535cbc109dec2e44da0 |
| 35 | BDZNH.c-cpp-compile-run-windows | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | 1a1211418b048678ff5a219d29587cb4beea251615bd8319b2a39caca0ac9cf9 |
| 36 | bradymholt.pgformatter | 0.0.1 | A · light | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-31 | 67d23c83dfe5199815aeb471bf468bb99faa6103e7238484e26e106194548642 |
| 37 | BretDoyle.javascript-extensions-pack---js-essentials | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | cf3593f41cb52c4b52386b539bc1b5095e6123de3d6bfda6f85f98b4570af6a4 |
| 38 | casualjim.gotemplate | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-31 | abcdee583b02c827fdd746acad814545f9e823d5c7859743d090e176e0f344f4 |
| 39 | drewbourne.vscode-remark-lint | 0.0.3 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | 48976edf057d4064db52c8ccd5d2b70769008556072cc411617dc6eeb28cbd53 |
| 40 | garaemon.vscode-emacs-tab | 0.0.3 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 573072c5107fb2fc749225caeee8ddabe7b1b6c9b45463a9e3247d4658870d33 |
| 41 | jakeboone02.cypher-query-language | 0.0.2 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 796d0536a0de16cd09f768b24d56763bdf8b36535c4ff946a754ff1f16857a65 |
| 42 | jcamp.dotnet-test-provider-view | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-31 | 3d734eb54a45f18ffa885f21240c95fccbd4a222368044ae01920176a3666d0e |
| 43 | madhavd1.javadoc-tools | 0.0.2 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | db253057b1d54c0d4bf2ea5d358e33043292c0e1780989a030f489a194d83cb5 |
| 44 | miclo.sort-typescript-imports | 0.0.3 | A · light | pulse.mangorbit[.]com | 2026-07-31 | e25513c12e71784067a36db09670c244eb2f22272a5cf4589cb46bc6a27b6fb7 |
| 45 | Mukundan.python-docs | 0.0.3 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | 1a5b4ea5cb768aec7bbba28d881774a124aeb84ee8827d56aa0213ecf59a77a1 |
| 46 | npxms.hide-gitignored | 0.0.2 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 52a54dd4b72d7c113b22c54a3fe48131d064215f1987589b4ce5aac13573c96d |
| 47 | qiu.llvm-ir-language-support | 0.0.6 | A · light | pulse.mangorbit[.]com | 2026-07-31 | b0e0615140a5aebf3ef3e99693e11f3fcc2c2c0c8a60e92fd4152a8e29c418a5 |
| 48 | superposition.supertoml-analyzer | 0.0.1 | B · recon | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-31 | b7655dd07d18bfb6f9b724647fd393ce2f312b2a874a046e2f568aa9c1eec32f |
| 49 | tamuratak.vscode-lezer | 0.0.1 | A · light | pulse.mangorbit[.]com, pulse2.mangorbit[.]com | 2026-07-31 | a27ef5dc929616d46ca1bb3fc12e25195e42726db9135b5946e53036992aa3a5 |
| 50 | tgreen7.vs-code-node-require | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-07-31 | fd8d100ee32bf3f23f9cf82a16c43a61c701380d443d246c34da5aa1d4352e84 |
| 51 | yardensachs.copy-python-path | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | 98bcafa86bc15ecb93ab47a4e5b7914932699d0ae38ff5f50020cb3d54f3b603 |
| 52 | zoxon.monokai-deep | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-07-31 | f6ae05bdf6ed9fd092d0c8976850980b7a03703f9412a8369a5d7ccd8412dba8 |
| 53 | bdaeumer.vscode-eslint | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 18e952ea9848a7bff90d1fdde4b3d6b6648df30959ce6771840ca8cc67fbdfe6 |
| 54 | chavyleung.vscode-pnpm-verlens | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | 6682a6142cf38e877b2683269c413eab1580e36f8e40876febaf1bf72e12c0f8 |
| 55 | chris-hock.pioasm | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | a4f0dd467b3abbdacd5ebb2929de3899c6b3d7b0b90da9ea4ae58e30d101db8f |
| 56 | davidpallinder.rails-test-runner | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 0039e6d3a653d255841320db1dcd9664e1c9ae8b0a0d8a2e9f7e0ecb5fd2c0f6 |
| 57 | Flutterando.flutter-mobx | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 280ef259644dfbd888ad3eb630aaf6f895449b5e97e075e459fc96c266fb8682 |
| 58 | Glavin001.unibeautify-vscode | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 5c41fa68ce0f879199d9b5646cf9b82ebde426e1ee0fa2ae10dd6447b0223c54 |
| 59 | GoBystrokReactJS.gobystrok | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | 15cd8ee6f874539d915a9284adf17a3c2eb0b970c331725a8388f58efba48719 |
| 60 | helixquar.asciidecorator | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 375216cb58f3700c181da52c62eccd4534b0d2aaa0d84df6a9f476f35a87217d |
| 61 | HyperledgerComposer.composer-support-client | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 58b7c5b92ed0756034af0de02c7addcab215ab612bd37c86bad11aafdfb25d62 |
| 62 | Insigne.powershell | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | 041019527f4c13b1007b71d35d4383b13ee8c7bf3eaaff366abe90c6c9a804c3 |
| 63 | Jeremy38100.init-node-script | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | b461c72229cefd5f2078db74565451dc656e71af3a1e95f13db70bf7dec94c70 |
| 64 | jt.jakt | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | 8af30a4e2c57034a64e924a893de50450dd668528f2723a94f907a8a758965ea |
| 65 | kaellarkin.hugo-shortcode-syntax | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | e7ba9bff28fa17d507753e0ca157d7add64cbfed593530504b153e24b6c982c9 |
| 66 | Levertion.mcjson | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | f04b8c9af22fd8e20fd070806b1aa21424a0f6d5e2c770fa9fb1d131b08393fd |
| 67 | magne-sjaastad.opm-flow-editor-support | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | b490d56da1c5a4e26de8c07f907157b0e328c84464df38d2bd1cb92858906f83 |
| 68 | MarinhoBrandao.Angular2Tests | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | 6bbaee0ccfbbd45c7683efbf4688ad4532050f8a0ad6b39ea1b435ce2b6cf546 |
| 69 | microsoft-dciborow.align-bicep | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | c5fe26dbc56a84ab672a28d3d3d767dd1496a56b027ecec7e3f8f25f83cd6a7d |
| 70 | mkdirdocs.mkd-docs | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 0937bfa48a6b2ee53c978c1edea77a04db6ef407adef108b12fb6612e308b275 |
| 71 | nihilus118.perl-debugger | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | c578b03b8bd150ec9bf71326293f9f1abc2f1d451b3ca2ee05a516ec53866b1b |
| 72 | pwrs.cem-language-server-vscode | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | 2364b4cf089da6d4d6c1fe854daca03047eea0849b325a3e9f28fac4584418d2 |
| 73 | wghats.vscode-nxunit-test-adapter | 0.0.1 | A · light | api.mangorbit[.]com | 2026-08-01 | b301202210164188696b6a6fced62cf0c1641dca650ea4981f81b210ced9116f |
| 74 | wordpresstools.wordpress | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 9ec8c53e41766b7b1dbc7fd9acf759b63f65353434231de57d22a6002fef7998 |
| 75 | xnerd.ampscript-language | 0.0.1 | A · light | pulse.mangorbit[.]com | 2026-08-01 | 81402a5dc2901bc68ae7e866d78ad7c0ab1c77868d1cb026f95a68a0de851f5d |
| 76 | ydaveluy.xsmp-modeler | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | 4b3dfffd11366d4171ba57efe53fc4fe4d4c39e9b8590c81365453005aed9b80 |
| 77 | ydaveluy.xsmp-tas-mdk | 0.0.1 | A · light | pulse2.mangorbit[.]com | 2026-08-01 | b9f36f1e416a3b6c650cebcad3dc046c0a0164442d0a2c3ed35279179f457c24 |
The publisher namespaces above (AMD, Artsy, LEGO Education, Hyperledger, Azure, IOTA, Salesforce OSS, the ssagov .gov namespace, and marketplace.visualstudio which impersonates the marketplace itself) were squatted. Those organisations were not involved and the genuine extensions are unaffected.
Affected Surface & Telemetry Matrix
| Surface | Why it is in scope | Required telemetry | Priority | Gap risk |
|---|---|---|---|---|
| Developer workstations — Windows | VS Code, Insiders, VSCodium, Cursor, Windsurf all read Open VSX or a mirror of it. Extensions live under %USERPROFILE%\.vscode\extensions and %USERPROFILE%\.cursor\extensions. | DnsRequest, NetworkConnectIP4, ProcessRollup2, ZipFileWritten | CRITICAL | Low — standard Falcon sensor coverage |
| Developer workstations — macOS / Linux | Same editors, extensions under ~/.vscode/extensions, ~/.vscode-oss/extensions, ~/.cursor/extensions. | DnsRequest, NetworkConnectIP4, ProcessRollup2 | CRITICAL | Medium — file-write event coverage on non-PE files is thinner than on Windows |
| Remote / server-side editor sessions | ~/.vscode-server/extensions on SSH and WSL targets; the extension host runs on the server, so the beacon originates there, not from the laptop. | DnsRequest, NetworkConnectIP4 on the server aid | HIGH | High — remote dev hosts are frequently outside standard endpoint coverage |
| CI/CD build runners | This is where the damage is: GITHUB_REPOSITORY, CI_PROJECT_PATH, Azure DevOps collection URI, Buildkite slug, CircleCI username all resolve to the private repository path. | Endpoint sensor on the runner, or runner egress logs; container image inventory | CRITICAL | High — ephemeral runners often carry no sensor at all |
| Devcontainers / Codespaces / Gitpod | devcontainer.json installs extensions by name on every container build, with no publisher verification. Codespace name and Gitpod workspace context URL are both collected fields. | Image build logs, container runtime telemetry, egress logs | HIGH | High — container-internal process telemetry is commonly absent |
| Golden images / VDI templates | An extension baked into a template silently re-provisions on every new machine, long after the registry takedown. | Image inventory; code --list-extensions at build time | HIGH | Medium — needs a build-time check, not a runtime one |
| Internal Open VSX mirror | A mirror that syncs by name will have cached the counterfeits and can keep serving them after the upstream takedown. | Mirror download / sync logs | HIGH | Medium — mirror logging varies by product |
| Web proxy / secure web gateway | Only place the User-Agent and URI path are visible when TLS is inspected. | Proxy or SWG logs forwarded to NG-SIEM | MEDIUM | High — parser field names are tenant-specific and TLS inspection may not cover dev subnets |
| Recursive DNS resolvers | Catches endpoints with no sensor, including runners and containers, and is the only reliable place to see the _beacon TXT lookups from unmanaged hosts. | Resolver query logs (with record type) | HIGH | Medium — many estates log resolver queries without the record type |
ATT&CK Mapping
| Tactic | Technique | Observed behaviour | Query / control |
|---|---|---|---|
| Initial Access | T1195.002 — Supply Chain Compromise: Compromise Software Supply Chain | 77 counterfeit packages published to Open VSX reusing real extension names and namespaces from accounts that do not own them. | Q5, Q6, Q11 · H-2, H-4 |
| Persistence | T1176.002 — Software Extensions: IDE Extensions | The extension is the persistence. It loads and beacons on every editor start, and a repo-config pin re-installs it on every container rebuild. | Q5, Q6, Q8 · H-2, H-7 |
| Defense Evasion | T1036.005 — Masquerading: Match Legitimate Name or Location | Name, namespace and description copied verbatim from a real VS Code Marketplace extension; version pinned low (0.0.1) to look like an early release. | Q5, Q8 · H-4, H-7 |
| Defense Evasion | T1554 — Compromise Host Software Binary | The legitimate bundled extension.js is swapped for the beacon while the rest of the package looks normal. | Q6, Q11 |
| Execution | T1204 — User Execution | Install by name — a developer choosing from a listing, or an agent / devcontainer / provisioning script resolving the name unattended. | Q5, Q8 · H-4, H-7 |
| Discovery | T1082 — System Information Discovery | Hostname, machine ID, editor name/version/host kind, platform, architecture, locale, timezone. | Q3, Q4, Q7 |
| Discovery | T1033 — System Owner/User Discovery | OS username; domain portion of the configured Git commit email. | Q7 |
| Discovery | T1083 — File and Directory Discovery | Workspace folder name and full filesystem path; presence of the extension's own ID in devcontainer.json / .vscode/extensions.json. | Q7, Q8 |
| Discovery | T1518 — Software Discovery | Enumerates up to 60 installed extension IDs. | Q7 (behavioural) · Partial |
| Discovery | T1016 — System Network Configuration Discovery | Reads the proxy hostname from the environment. | GAP — no distinct endpoint signal |
| Collection | T1005 — Data from Local System | Reads .git/config (origin/upstream remote host + org, commit email domain) and .git/HEAD (branch, HEAD commit SHA); reads CI identity variables. | Q7 · H-9 |
| Collection | T1119 — Automated Collection | Whole collection routine fires automatically 4–5 s after editor activation, with no user interaction. | Q3, Q4 |
| Command & Control | T1071.001 — Application Layer Protocol: Web Protocols | HTTPS POST/GET to /api/v1/metrics, /api/v1/events, /t/<24-hex> with UA vscode-ext-metrics/1.0. | Q1, Q4, Q9, Q10 |
| Command & Control | T1071.004 — Application Layer Protocol: DNS | TXT lookup at _beacon.<domain> returning a base=https:// replacement collector URL. | Q2 · H-1 |
| Command & Control | T1568 — Dynamic Resolution | Randomised subdomain labels under cb.mangorbit[.]com; endpoint array with failover across multiple hosts and a second registered domain. | Q1, Q3 |
| Command & Control | T1008 — Fallback Channels | Multiple hardcoded endpoints, any HTTP status treated as success, 7-day retry ladder resuming on each editor restart. | Q4 (cadence) · Partial |
| Exfiltration | T1041 — Exfiltration Over C2 Channel | The single assembled request carrying host, user, workspace, Git and CI identity. | Q1, Q4, Q9, Q10 |
Native Audit-Log Hunts (non-CQL)
These run outside Falcon and answer the questions endpoint telemetry cannot. Run N1 and N2 first — they are the fastest route to "were we hit", and they work on hosts with no sensor.
| # | Source | What to run / look for | Why it matters |
|---|---|---|---|
| N1 | Recursive DNS resolver logs | Any query for a name ending mangorbit.com, over the full window 2026-07-26 to now. Then any TXT query for a name beginning _beacon., with no domain restriction. | Covers unmanaged hosts, ephemeral runners and containers that carry no sensor. The _beacon query is the only signal that survives the operator relocating collectors. |
| N2 | Endpoint extension inventory | code --list-extensions --show-versions (and the codium, cursor, windsurf, code-insiders equivalents) across the fleet, diffed against the 77 IDs in §4. Playbook P1 wraps this. | "What is installed" is a different question from "what the config requests" — Manifold's core recommendation. Removal from the registry did not remove anything from a machine. |
| N3 | Source-control search across all repositories | Search every repo for .vscode/extensions.json, .devcontainer/devcontainer.json and .devcontainer.json, then grep those files for the 77 counterfeit IDs. | A pinned ID re-installs the extension on every container build and every new joiner's clone. This is the difference between a one-time cleanup and a recurring one. |
| N4 | Internal Open VSX mirror / artifact proxy logs | Download and sync records for any of the 77 extension IDs, and for any package pulled at version 0.0.1 under a namespace you consider well-known. | A name-syncing mirror can keep serving a counterfeit after the upstream takedown. Also gives you the exact list of machines that pulled it. |
| N5 | Web proxy / SWG logs | User-Agent containing vscode-ext-metrics; any request to a host under mangorbit.com; requests to /api/v1/metrics or /api/v1/events paired with either of the first two. | The only place the UA and URI path are visible. Q9 and Q10 are the NG-SIEM versions of the same hunt. |
| N6 | Newly-registered-domain / passive DNS enrichment | For every external destination first contacted by an editor process in the last 30 days, check domain age. Flag anything under 30 days old. | Catches the undisclosed second failover domain, and the next campaign. mangorbit.com was eleven days old when the first package shipped. |
| N7 | CI/CD platform audit logs | On any project whose runner is confirmed or suspected exposed, review subsequent access patterns and treat the private repository path as public knowledge from the exposure date forward. | No credentials were taken, so there is nothing to rotate — the loss is knowledge of your private repo paths, which fuels targeted phishing and follow-on reconnaissance. |
| N8 | Git config on affected hosts | Record which remotes, branches and commit-email domains were readable at the time of exposure, so the scope note in the incident record is factual rather than assumed. | Payload B only reads .git/config and .git/HEAD, strips credentials from remote URLs, and keeps only the email domain — scope the notification accordingly instead of over-reporting. |
CrowdStrike LogScale CQL Hunt Queries
None of these queries carry an in-query time filter. Set the window with the console time picker; each card records the intended lookback in a // LOOKBACK: comment. A bare @timestamp comparison against now() is rejected by Falcon on line 1 and would silently kill the whole query.
Looks for: any DNS resolution of mangorbit.com or any subdomain of it, including the randomised cb labels. This is the "were we hit" query — run it first, over the widest window your retention allows. FP: essentially none outside a malware-analysis host.
// HUNT: Open VSX "Evil Twin" collector domain resolved on an endpoint // MITRE: T1071.004, T1041 | CONF: high FP: low COST: low // REQUIRES: DnsRequest // LOOKBACK: 30d or max retention -- set the window in the console time picker // FALSE POSITIVES: sandbox / malware-analysis workstations detonating the VSIX samples // TUNING: if a research host is a known hit, exclude by aid rather than by ComputerName #event_simpleName=/^DnsRequest$/ | DomainName=/(^|\.)mangorbit\.com$/i | table([@timestamp, aid, ComputerName, UserName, DomainName, ContextBaseFileName, ContextProcessId]) | sort(@timestamp, order=desc, limit=2000)
Looks for: lookups of any _beacon.-prefixed label. This is the channel the operator uses to publish a replacement collector base URL after a takedown, so it is the one query here that keeps working when mangorbit.com stops resolving. FP: low — no mainstream tooling uses this label (ACME uses _acme-challenge, DKIM uses _domainkey), so treat any hit as high priority.
// HUNT: DNS TXT takedown-contingency lookup (_beacon.<domain>) for a replacement collector // MITRE: T1071.004, T1568 | CONF: medium FP: low COST: low // REQUIRES: DnsRequest -- record type is optional but sharpens the result // LOOKBACK: 30d or max retention // FALSE POSITIVES: underscore-prefixed service labels are common, but the _beacon label itself is not // TUNING: keep the ^_beacon\. anchor. If your tenant populates RequestType, add | RequestType = 16 // to keep TXT only. Any surviving hit is worth a same-day pivot to Q4 on that aid. #event_simpleName=/^DnsRequest$/ | DomainName=/^_beacon\./i | table([@timestamp, aid, ComputerName, UserName, DomainName, RequestType, ContextBaseFileName, ContextProcessId]) | sort(@timestamp, order=desc, limit=1000)
Looks for: domains resolved by an editor or extension-host process that almost nothing else in the fleet resolves. This is the query that catches the undisclosed second failover domain and the next campaign, because it never names an indicator. FP: medium — language servers, package registries, AI assistants and vendor telemetry all resolve long-tail names from the editor process; the exclusion regex and the host-count ceiling are what make it readable.
// HUNT: Rare external domain resolved by a code editor or its extension host // MITRE: T1176.002, T1071.001 | CONF: medium FP: medium COST: medium // REQUIRES: DnsRequest with ContextBaseFileName populated // LOOKBACK: 7d (widen to 30d once the exclusion list is tuned) // FALSE POSITIVES: language servers, registries, AI coding assistants, update and telemetry endpoints // TUNING: extend the exclusion regex with your own approved dev estate -- internal registries, your // artifact proxy, your SSO domain -- then raise DistinctHosts until the result set is reviewable. // Enrich survivors with domain age; anything under 30 days old goes straight to Q4 on that aid. #event_simpleName=/^DnsRequest$/ | ContextBaseFileName=/^(code|code-insiders|codium|vscodium|cursor|windsurf|code-oss|node|electron)(\.exe)?$/i | !DomainName=/\.(microsoft|windows|visualstudio|github|githubusercontent|githubassets|npmjs|pypi|crates|nuget|maven|jetbrains|google|gstatic|apple|cloudflare|akamai|azureedge|openai|anthropic)\.(com|net|org|io|dev|ai)$/i | groupBy([DomainName], function=[count(as=Lookups), count(aid, distinct=true, as=DistinctHosts), collect(ComputerName, limit=10), collect(ContextBaseFileName, limit=5), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)], limit=20000) | DistinctHosts <= 3 | Lookups > 1 | sort(DistinctHosts, order=asc, limit=500)
Looks for: the IP-layer half of Q3 — editor and extension-host processes opening web-port connections to external addresses that few hosts in the fleet touch. Use it when DNS context is missing (DoH, a resolver you do not log, or a hardcoded IP). FP: medium — CDN address churn makes a single IP look rare even for a common service, so always resolve survivors back to a name before judging.
// HUNT: Editor / extension-host process egressing to a rare external destination // MITRE: T1041, T1071.001 | CONF: medium FP: medium COST: medium // REQUIRES: NetworkConnectIP4 with ContextBaseFileName populated // LOOKBACK: 7d // FALSE POSITIVES: marketplace and update traffic, language servers, AI assistants; CDN churn makes // individual edge IPs look rare even when the service behind them is ubiquitous // TUNING: add !cidr() lines for your egress proxy, your VPN concentrators and your CDN provider ranges. // Resolve every surviving RemoteAddressIP4 to a name before triage, then feed it back into Q1/Q3. #event_simpleName=/^NetworkConnectIP4$/ | ContextBaseFileName=/^(code|code-insiders|codium|vscodium|cursor|windsurf|code-oss|node|electron)(\.exe)?$/i | !cidr(RemoteAddressIP4, subnet="10.0.0.0/8") | !cidr(RemoteAddressIP4, subnet="172.16.0.0/12") | !cidr(RemoteAddressIP4, subnet="192.168.0.0/16") | !cidr(RemoteAddressIP4, subnet="127.0.0.0/8") | !cidr(RemoteAddressIP4, subnet="169.254.0.0/16") | !cidr(RemoteAddressIP4, subnet="100.64.0.0/10") | in(RemotePort_decimal, values=[80, 443, 8443]) | groupBy([RemoteAddressIP4, ContextBaseFileName], function=[count(as=Conns), count(aid, distinct=true, as=DistinctHosts), collect(ComputerName, limit=10), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)], limit=20000) | DistinctHosts <= 5 | sort(DistinctHosts, order=asc, limit=500)
Looks for: --install-extension invocations naming one of the 19 reconnaissance-payload IDs — the ones that leak Git and CI identity. This is how provisioning scripts, Dockerfiles and devcontainer post-create hooks install extensions, so it is the highest-fidelity way to find automated re-provisioning. FP: low — a match is either a real install or a deliberate research install.
// HUNT: Command-line install of a counterfeit Open VSX extension ID (payload B / recon set) // MITRE: T1176.002, T1195.002, T1204 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // LOOKBACK: 30d or max retention // NOTE: this regex carries the 19 reconnaissance IDs. The full 77 are in the IOC appendix; use the // lookup-file form in Q11 rather than pasting 77 alternatives into a regex. // FALSE POSITIVES: an analyst deliberately reproducing the sample // TUNING: drop the --install-extension line to also catch marketplace-UI installs recorded elsewhere // in the command line, at the cost of some noise from log-shipping agents echoing the ID #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/--install-extension/i | CommandLine=/(amd\.gaia-vscode|artsy\.artsy-studio-extension-pack|configcat\.configcat-feature-flags|iotaledger\.iota-move|marketplace\.visualstudio|obyte\.oscript-vscode-plugin|openeuphoria\.vscode-euphoria|oss\.sfmc-devtools-vscode|rumbledb\.jsoniq-vscode|ssagov\.uef-snippets|taskfile\.vscode-task|doi\.fileheadercomment|mengsicode\.vscode-django-boilerplate|move\.move-analyzer|uavcan\.dsdl|vs-publisher-988541\.apexsql-power-tools|casualjim\.gotemplate|jcamp\.dotnet-test-provider-view|superposition\.supertoml-analyzer)/i | table([@timestamp, aid, ComputerName, UserName, FileName, ParentBaseFileName, CommandLine]) | sort(@timestamp, order=desc, limit=1000)
Looks for: VSIX archives landing on disk, with the hash projected so you can diff against the 77 published SHA-256 values. A VSIX is a ZIP, so ZipFileWritten is the event that sees it. FP: medium — every legitimate extension install and update writes here too, which is why this is an inventory query first and a detection only once joined to the hash list or to Q1.
// HUNT: VSIX extension package written to disk (inventory + hash surface) // MITRE: T1176.002, T1554 | CONF: medium FP: medium COST: medium // REQUIRES: ZipFileWritten (a VSIX is a ZIP archive) // LOOKBACK: 30d // FALSE POSITIVES: every legitimate extension install and auto-update writes a VSIX // TUNING: this is an inventory sweep, not an alert. Scope it to CI/build images and golden images // first, then diff SHA256HashData against the 77 published hashes -- or just run Q11, which does // the diff server-side via a lookup file. #event_simpleName=/^ZipFileWritten$/ | TargetFileName=/\.vsix$/i | table([@timestamp, aid, ComputerName, UserName, TargetFileName, SHA256HashData, ContextBaseFileName]) | sort(@timestamp, order=desc, limit=2000)
Looks for: the recon variant's collection step — the editor or extension host opening .git\config and .git\HEAD. On its own this is ordinary editor behaviour; its value is as a confirmation query on a host already surfaced by Q1, Q3, Q4 or Q11. FP: high — the built-in Git extension, GitLens and most source-control providers read these files continuously. Never schedule this as an alert.
// HUNT: Code editor or extension host reading .git\config / .git\HEAD (recon-variant collection) // MITRE: T1005, T1083, T1033 | CONF: medium FP: high COST: high // REQUIRES: FileOpenInfo -- ⚠ validate in tenant: file-read telemetry is off by default in many // sensor policies, and enabling it fleet-wide is expensive. Confirm before relying on this card. // LOOKBACK: 24h, scoped to specific hosts // FALSE POSITIVES: the built-in Git extension, GitLens, source-control providers and language // servers all read .git\config constantly -- this is normal, expected editor behaviour // TUNING: run this ONLY scoped to an aid already surfaced by Q1/Q3/Q4/Q11 -- add a top-level // aid filter before the event filter. Then correlate on ContextProcessId with an external // connection from the same process id inside a 10-second window; that pairing is the signal, // the file read alone is not. #event_simpleName=/^FileOpenInfo$/ | ContextBaseFileName=/^(code|code-insiders|codium|vscodium|cursor|windsurf|code-oss|node|electron)(\.exe)?$/i | TargetFileName=/[\\\/]\.git[\\\/](config|HEAD)$/i | table([@timestamp, aid, ComputerName, UserName, TargetFileName, ContextBaseFileName, ContextProcessId]) | sort(@timestamp, order=desc, limit=2000)
Looks for: .vscode\extensions.json and devcontainer.json being written on endpoints and build hosts — the files that cause an extension to be re-installed by name on every clone and every container rebuild. FP: high — normal repository work touches these constantly. Its job is to give you a file list to grep against the 77 IDs, and native hunt N3 (a source-control-wide search) is the better primary. Use this to catch the copies that only exist inside images.
// HUNT: Repository extension manifests written (.vscode\extensions.json, devcontainer.json) // MITRE: T1176.002, T1195.002, T1036.005 | CONF: low FP: high COST: medium // REQUIRES: CriticalFileModified / FileOpenInfo -- ⚠ validate in tenant: non-PE file event // coverage is policy-dependent and varies between Windows, macOS and Linux sensors // LOOKBACK: 30d // FALSE POSITIVES: ordinary repository work, container rebuilds, IaC templating and repo scaffolding // all write these files -- expect a large, benign result set // TUNING: treat this as an inventory sweep, not an alert. Scope to CI/build hosts and golden-image // builders first, then grep the resulting paths for the 77 counterfeit IDs in the IOC appendix. // For repositories, use native hunt N3 instead -- a source-control search sees every branch. #event_simpleName=/^(CriticalFileModified|FileOpenInfo)$/ | TargetFileName=/(extensions\.json|devcontainer\.json)$/i | table([@timestamp, aid, ComputerName, UserName, TargetFileName, ContextBaseFileName]) | sort(@timestamp, order=desc, limit=2000)
Looks for: the User-Agent vscode-ext-metrics/1.0. No legitimate editor component uses it, so a single hit is actionable — and it identifies the beacon even after the operator moves to a new domain. FP: low. The pattern matches only the product token, not the version, so a bumped version still hits.
// HUNT: Beacon User-Agent vscode-ext-metrics in web-proxy / secure-web-gateway telemetry // MITRE: T1071.001, T1041 | CONF: high FP: low COST: low // REQUIRES: third-party proxy / SWG logs forwarded to NG-SIEM // ⚠ validate in tenant: parser field names differ by vendor -- http_user_agent, user_agent, // cs_user_agent, useragent. Confirm against one sample row and adjust before scheduling. // LOOKBACK: 30d or max retention // FALSE POSITIVES: none known -- the token is not used by any shipping editor component // TUNING: match the product token only, not the /1.0 version, so a bumped version still fires http_user_agent=/vscode-ext-metrics/i | table([@timestamp, src_ip, user, http_user_agent, url]) | sort(@timestamp, order=desc, limit=1000)
Looks for: the three collector paths — /api/v1/metrics, /api/v1/events, and the per-package tracking path /t/ followed by a 24-character hex identifier. The tracking path is the distinctive one; it also tells you which counterfeit extension is installed. FP: medium — the two /api/v1/ paths are generic REST endpoints used by plenty of legitimate SaaS.
// HUNT: Collector URI paths -- /api/v1/metrics, /api/v1/events, /t/<24-hex tracking id> // MITRE: T1041, T1071.001 | CONF: medium FP: medium COST: medium // REQUIRES: proxy / SWG URL logging forwarded to NG-SIEM // ⚠ validate in tenant: the URL field may be url, uri, uri_path, cs_uri_stem or request // LOOKBACK: 30d // FALSE POSITIVES: /api/v1/metrics and /api/v1/events are generic REST paths shipped by many // legitimate products -- on their own they are not evidence of anything // TUNING: alert only on the /t/ 24-hex branch, or pair the /api/v1 branch with the mangorbit host // filter or the Q9 User-Agent. Add | url=/mangorbit/i as a second stage to make it high-fidelity. url=/\/api\/v1\/(metrics|events)$|\/t\/[0-9a-f]{24}$/i | table([@timestamp, src_ip, user, http_user_agent, url]) | sort(@timestamp, order=desc, limit=1000)
Looks for: any of the 77 published VSIX SHA-256 values on an endpoint. Uses a lookup file so all 77 hashes stay out of the query text and the enrichment columns come back with the hit — you learn the extension ID and the payload class in the same row. Upload the "VSIX SHA-256 lookup" block from §10 as openvsx_eviltwin_vsix.csv before running. FP: low.
// HUNT: Known counterfeit VSIX SHA-256 present on an endpoint (all 77 packages) // MITRE: T1195.002, T1554 | CONF: high FP: low COST: medium // REQUIRES: a lookup file uploaded at Falcon -> NG-SIEM -> Lookup files. Copy the "VSIX SHA-256 // lookup" block from the IOC appendix and save it as openvsx_eviltwin_vsix.csv. Lookup files are // per search head -- upload separately in each region if you run multi-region tenants. // LOOKBACK: 30d or max retention // FALSE POSITIVES: a malware-analysis host holding the samples deliberately // TUNING: if a research host is a known holder, exclude it by aid rather than removing hashes #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|ZipFileWritten|PeFileWritten)$/ | match(file="openvsx_eviltwin_vsix.csv", field=SHA256HashData, column=sha256, include=[extension_id, payload], strict=true) | table([@timestamp, aid, ComputerName, UserName, SHA256HashData, extension_id, payload, TargetFileName, FileName]) | sort(@timestamp, order=desc, limit=1000)
Custom IOA & Scheduled-Search Alert Package
What belongs where
| Signal | Right mechanism | Why |
|---|---|---|
| Collector domain and hosts | Falcon IOC Management — domain IOCs, action prevent | Domain blocking belongs in IOC Management, not in a Custom IOA. Blocking the zone covers all 77 samples in one entry. |
| 77 VSIX SHA-256 values | Falcon IOC Management — hash IOCs, action prevent | Atomic, published, and stable. The CSV in §10 imports directly. |
| Counterfeit extension installed via CLI | Custom IOA (process-creation rule, below) | Command-line pattern is deterministic and cheap to evaluate on the sensor. |
| Q1, Q2, Q9, Q11 | Scheduled Search with email notification | Low FP, high confidence — safe to alert on. |
| Q3, Q4, Q6, Q7, Q8, Q10 | Investigate-only hunt queries | FP medium or high. Run on a cadence and review; do not page on them. |
IOA-1 — Counterfeit Open VSX extension install
| Field | Value |
|---|---|
| Rule group | Developer Supply Chain |
| Rule type | Process Creation |
| Platform | Windows, macOS, Linux (create one per platform) |
| Action | Detect — severity High. Do not set Block: killing an editor mid-install is disruptive and the install has already happened by the time the process exits. |
| Image filename pattern | .*(code|code-insiders|codium|cursor|windsurf)(\.exe)? |
| Command line pattern | .*--install-extension\s+(amd\.gaia-vscode|iotaledger\.iota-move|move\.move-analyzer|ssagov\.uef-snippets|oss\.sfmc-devtools-vscode|vs-publisher-988541\.apexsql-power-tools|marketplace\.visualstudio|configcat\.configcat-feature-flags|taskfile\.vscode-task|doi\.fileheadercomment|uavcan\.dsdl|casualjim\.gotemplate|jcamp\.dotnet-test-provider-view|superposition\.supertoml-analyzer|obyte\.oscript-vscode-plugin|openeuphoria\.vscode-euphoria|rumbledb\.jsoniq-vscode|artsy\.artsy-studio-extension-pack|mengsicode\.vscode-django-boilerplate).* |
| Exclusions | Exclude the aid of any sanctioned malware-analysis workstation. Do not exclude build agents — those are the highest-value hits. |
| Deployment path | Endpoint security → Custom IOA rule groups → new group → Process Creation rule → assign the group to your developer and build-agent prevention policies. |
| Validation | On a test host, run the editor CLI with --install-extension and a harmless ID that you have temporarily added to the pattern. Confirm the IOA fires, then revert the pattern. |
Scheduled Search — SS-1
| Field | Value |
|---|---|
| Name | DETECT — Open VSX Evil Twin collector contact (mangorbit) |
| Query | Q1 |
| Schedule | Every 1 hour, 1-hour window (plus one historical run over max retention when you first deploy) |
| Description | Fires when any managed endpoint resolves the Open VSX "Evil Twin" campaign collector zone. All 77 counterfeit extensions reference this zone, so a hit means a counterfeit extension is installed and active on that host. |
| Notification | Email to the SOC queue; severity High |
Alert email — SS-1
Subject: [HIGH] Detection: Open VSX "Evil Twin" extension beacon — developer host contacted mangorbit
WHAT FIRED
Host {{ComputerName}} (aid {{aid}}), user {{UserName}}, resolved {{DomainName}} at {{@timestamp}}.
Resolving process: {{ContextBaseFileName}} (pid {{ContextProcessId}}).
WHAT IT MEANS
mangorbit[.]com is the collector zone for a campaign of 77 counterfeit VS Code-compatible
extensions published to the Open VSX registry between 2026-07-26 and 2026-08-01. A DNS
resolution from a developer endpoint means one of those extensions is installed and running
on that machine. The packages were pulled from Open VSX on 2026-08-03; removal from the
registry does not remove the code from a machine or an image.
58 of the 77 send only host-level data. The other 19 send Git remote host and organisation,
the commit-email domain, the current branch, the HEAD commit SHA, and CI identity values --
on a build runner or cloud dev environment that is the full private repository path.
No source code, credentials, tokens, SSH material or browser data are taken.
IMMEDIATE ACTIONS
1. Confirm the zone is blocked at DNS and egress, and that the Falcon domain IOC is set to prevent.
2. On the host, list installed editor extensions and compare against the 77 IDs (playbook P1).
3. Determine payload class: if the installed ID is one of the 19 recon IDs, treat repository
and CI identity for that host's workspaces as disclosed.
4. Check whether the install came from a repo config (devcontainer.json / .vscode/extensions.json)
-- if so, the same extension is on every machine and every container built from that repo.
5. Remove the extension, then verify the beacon stops (re-run Q1 scoped to the aid after a restart).
ESCALATION
Escalate to the incident channel if the host is a CI/build runner, a shared golden image, or a
cloud development environment -- those expose private repository paths rather than just a hostname.
Triage checklist — L1
- Record the aid, ComputerName, UserName and the exact collector host from the alert. Note whether it is
pulse,pulse2,apior a randomisedcblabel. - Classify the host: developer workstation, remote dev server, CI/build runner, container, or golden-image builder. This single fact sets the severity.
- Run playbook P1 on the host to list installed extensions, and match against the 77 IDs in §4.
- Look the matched ID up in the payload column. B means Git and CI identity left the machine; A means hostname-level data only.
- Run Q1 scoped to that aid over full retention to establish the first-contact date — that is the start of the exposure window.
- Search the host's repositories and any devcontainer/provisioning config for the matched ID (native hunt N3). A pin means this recurs on rebuild.
- Run Q3 and Q4 on the same aid to check for contact with the undisclosed second failover domain.
- Remove the extension, restart the editor, and confirm no further Q1 hits for that aid over the next 24 hours.
Pivot queries
- Host-scoped: add a top-level
aidfilter to Q3, Q4 and Q7 to reconstruct everything the extension host did on that machine. - Environment-wide: run Q11 across max retention to find every other host holding one of the 77 VSIX files, including ones that never got to beacon.
- Infrastructure-wide: run Q2 with no host filter — a
_beaconTXT hit anywhere means the operator has already moved, and Q1 is now blind.
Machine-Readable IOC Appendix
type,value,action,severity,expiration,description,tags domain,mangorbit.com,prevent,high,2027-02-08,Open VSX Evil Twin collector zone,campaign:OpenVSX-EvilTwin domain,pulse.mangorbit.com,prevent,high,2027-02-08,Open VSX Evil Twin primary collector,campaign:OpenVSX-EvilTwin domain,pulse2.mangorbit.com,prevent,high,2027-02-08,Open VSX Evil Twin secondary collector,campaign:OpenVSX-EvilTwin domain,api.mangorbit.com,prevent,high,2027-02-08,Open VSX Evil Twin collector (single sample),campaign:OpenVSX-EvilTwin domain,cb.mangorbit.com,prevent,high,2027-02-08,Open VSX Evil Twin randomised-label collector parent,campaign:OpenVSX-EvilTwin domain,d9j3drkeib4sa01kk82014tt97wxnndam.cb.mangorbit.com,prevent,high,2027-02-08,Observed randomised collector label,campaign:OpenVSX-EvilTwin sha256,b8cd87d5e36f167f51ed6a6d2e824f76dc1b3792de9c10091e46f87ad07f5d5a,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX lego-education.ev3-micropython lightweight payload A,campaign:OpenVSX-EvilTwin sha256,8763bda130fe4305c4d13c25584c981424dc47d3de3df46ae0ef05466b05526a,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX better-ts-errors.better-ts-errors lightweight payload A,campaign:OpenVSX-EvilTwin sha256,415cef17b6560643c0e4dddccd956ae6887c369d84c7f45be638a76dc4930dc1,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX groksrc.ruby lightweight payload A,campaign:OpenVSX-EvilTwin sha256,7342f26ff97c0f8dc29edb1c5a5460bafcaacbc84356020a785628b36156ab6e,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX maptz.regionfolder lightweight payload A,campaign:OpenVSX-EvilTwin sha256,5d62cfb190145bd21f930d4c21da01e8dc951e4112992790361e2fd4b09e66f6,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX mitsuhiko.insta lightweight payload A,campaign:OpenVSX-EvilTwin sha256,d51e331ae110eb37d453e78ac4cf3063d4606ebd2357d8ee4342fd8f06933e20,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX SBSnippets.pytorch-snippets lightweight payload A,campaign:OpenVSX-EvilTwin sha256,0ccd62eb6c1155c252248a98c8ff8e1a45a5cc721d6725511effb770037690d5,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX slb235.vscode-coffeelint lightweight payload A,campaign:OpenVSX-EvilTwin sha256,1b775ad3ca61addf5bf3e326951314d80ab526cbab0d745edb3e2bd85de2fcd9,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX amd.gaia-vscode recon payload B,campaign:OpenVSX-EvilTwin sha256,a3dbc0a35a781c61c34bf41e9d21eadbf2e77d18531236d1d9c1e622aaf418ef,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX artsy.artsy-studio-extension-pack recon payload B,campaign:OpenVSX-EvilTwin sha256,941e9cf4fb7a1cdadb1a0e64fce3262426847f167ad255a3f724d77f2d0ee103,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX configcat.configcat-feature-flags recon payload B,campaign:OpenVSX-EvilTwin sha256,fde634f337ab81b3d6bfa320047ff4fe450556fd90f44a30612d549b996edc86,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX iotaledger.iota-move recon payload B,campaign:OpenVSX-EvilTwin sha256,9f8a69c193c3c4b83db18149408a9687d1cb6ca149ffa3a1ca6d72e95e3b6954,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX marketplace.visualstudio recon payload B,campaign:OpenVSX-EvilTwin sha256,7179f11710f242a578bd50187dadd7fa2ba0062e11e1a631edcd75417bda3f7e,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX obyte.oscript-vscode-plugin recon payload B,campaign:OpenVSX-EvilTwin sha256,0e99e23329f3ca908d7a88ccf102e4b10bd9b67f24fc79408aae15d5d10dfd0a,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX openeuphoria.vscode-euphoria recon payload B,campaign:OpenVSX-EvilTwin sha256,fc3a32f59300c7e8b3e486f23146224beb658d10ae04098a9e27faf341356fbe,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX oss.sfmc-devtools-vscode recon payload B,campaign:OpenVSX-EvilTwin sha256,498b0c9632959eb37cc278f35561f0736c91884b370251dfebae07ff9a942bc3,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX rumbledb.jsoniq-vscode recon payload B,campaign:OpenVSX-EvilTwin sha256,95fbdc4c81e14a46dd8f3eb5c4207567baae99712e3ccf8644af09680f3caf5e,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX ssagov.uef-snippets recon payload B,campaign:OpenVSX-EvilTwin sha256,2526c160b6a08377d02e035fc166e91e8029b997fa6c91169640c2ad2c2ec60b,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX taskfile.vscode-task recon payload B,campaign:OpenVSX-EvilTwin sha256,15cd6a4909161c406b8428100d688999377e399ae13aa4da49bb73744933807d,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX doi.fileheadercomment recon payload B,campaign:OpenVSX-EvilTwin sha256,d35b690502fd3b73312aacf62dc700ac1e43bbc01b53d129096c2c0cdf92a1c4,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX MengsiCode.vscode-django-boilerplate recon payload B,campaign:OpenVSX-EvilTwin sha256,3b407a548040927802f2cfa05b81eabf48f8b03785ba9fd69a3caa3981a7bc22,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX move.move-analyzer recon payload B,campaign:OpenVSX-EvilTwin sha256,d19c076fc8213617f889a313a3da1565d8364e35c53290fa96400183127b1187,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX uavcan.dsdl recon payload B,campaign:OpenVSX-EvilTwin sha256,9e148da1d338c32286b54a102ba3a42c7c2627855c3cc820122cff2ee4fc0d90,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX vs-publisher-988541.apexsql-power-tools recon payload B,campaign:OpenVSX-EvilTwin sha256,03d81fce32d898e4791672e8e324ca8854d4667f56131f9c2b9d13d66faade4f,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX 365businessdevelopment.bdev-al-xml-doc lightweight payload A,campaign:OpenVSX-EvilTwin sha256,5a1fb5758a6f4b1635d1e1d3fd83c2c13376f466508a949cdad23114af82db45,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX AcademiaDosDevs.javafx lightweight payload A,campaign:OpenVSX-EvilTwin sha256,508d3fb15d317846239e8d1f89ab5a5f31242d159f102602cfbf671103658bd3,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX AlDuncanson.react-hooks-snippets lightweight payload A,campaign:OpenVSX-EvilTwin sha256,7dc683fc3e0acf757cb48091c259bd71a7e3fe985f83d5b40468fa5b58e70804,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX Alex-Chen.gitee-code-settings-sync lightweight payload A,campaign:OpenVSX-EvilTwin sha256,3f97f02073caab6208d103693b13105f9dd42daca379e07953e433bd50ec5d3a,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX AndenetAlexander.vim-cheatsheet lightweight payload A,campaign:OpenVSX-EvilTwin sha256,3e9c96b913b54af9d50966b27b878750c3ae9d8e7944c4437a40d5a5cbc5ef21,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX AndreyVolosovich.monokai-st3 lightweight payload A,campaign:OpenVSX-EvilTwin sha256,aac1efa84293929006436160f77e58e70d9025fc0b320ecc64953e066785a0fa,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX angelo-breuer.license-header-manager lightweight payload A,campaign:OpenVSX-EvilTwin sha256,8e90e637bd7f3a2eaee077f216610248bf4d41c8d1f7880a2d2623be13be339f,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX AshhadDevLab.customtkinter-snippets lightweight payload A,campaign:OpenVSX-EvilTwin sha256,410e6b62b5e62cd8603b374f34e5ec6dad62bb95435fa320e08a850fa359f8c5,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX AzureADB2CTools.aadb2c lightweight payload A,campaign:OpenVSX-EvilTwin sha256,805c0c50271afbe94301d376cc5eb191feb7a409ea05f7771dcb4a07dcb0442a,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX AzurePolicy.azurepolicyextension lightweight payload A,campaign:OpenVSX-EvilTwin sha256,9afe7dd8f3eece28719edac1572ca7adbf307fdb29580535cbc109dec2e44da0,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX BartmanAbyss.amiga-debug lightweight payload A,campaign:OpenVSX-EvilTwin sha256,1a1211418b048678ff5a219d29587cb4beea251615bd8319b2a39caca0ac9cf9,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX BDZNH.c-cpp-compile-run-windows lightweight payload A,campaign:OpenVSX-EvilTwin sha256,67d23c83dfe5199815aeb471bf468bb99faa6103e7238484e26e106194548642,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX bradymholt.pgformatter lightweight payload A,campaign:OpenVSX-EvilTwin sha256,cf3593f41cb52c4b52386b539bc1b5095e6123de3d6bfda6f85f98b4570af6a4,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX BretDoyle.javascript-extensions-pack---js-essentials lightweight payload A,campaign:OpenVSX-EvilTwin sha256,abcdee583b02c827fdd746acad814545f9e823d5c7859743d090e176e0f344f4,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX casualjim.gotemplate recon payload B,campaign:OpenVSX-EvilTwin sha256,48976edf057d4064db52c8ccd5d2b70769008556072cc411617dc6eeb28cbd53,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX drewbourne.vscode-remark-lint lightweight payload A,campaign:OpenVSX-EvilTwin sha256,573072c5107fb2fc749225caeee8ddabe7b1b6c9b45463a9e3247d4658870d33,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX garaemon.vscode-emacs-tab lightweight payload A,campaign:OpenVSX-EvilTwin sha256,796d0536a0de16cd09f768b24d56763bdf8b36535c4ff946a754ff1f16857a65,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX jakeboone02.cypher-query-language lightweight payload A,campaign:OpenVSX-EvilTwin sha256,3d734eb54a45f18ffa885f21240c95fccbd4a222368044ae01920176a3666d0e,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX jcamp.dotnet-test-provider-view recon payload B,campaign:OpenVSX-EvilTwin sha256,db253057b1d54c0d4bf2ea5d358e33043292c0e1780989a030f489a194d83cb5,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX madhavd1.javadoc-tools lightweight payload A,campaign:OpenVSX-EvilTwin sha256,e25513c12e71784067a36db09670c244eb2f22272a5cf4589cb46bc6a27b6fb7,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX miclo.sort-typescript-imports lightweight payload A,campaign:OpenVSX-EvilTwin sha256,1a5b4ea5cb768aec7bbba28d881774a124aeb84ee8827d56aa0213ecf59a77a1,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX Mukundan.python-docs lightweight payload A,campaign:OpenVSX-EvilTwin sha256,52a54dd4b72d7c113b22c54a3fe48131d064215f1987589b4ce5aac13573c96d,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX npxms.hide-gitignored lightweight payload A,campaign:OpenVSX-EvilTwin sha256,b0e0615140a5aebf3ef3e99693e11f3fcc2c2c0c8a60e92fd4152a8e29c418a5,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX qiu.llvm-ir-language-support lightweight payload A,campaign:OpenVSX-EvilTwin sha256,b7655dd07d18bfb6f9b724647fd393ce2f312b2a874a046e2f568aa9c1eec32f,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX superposition.supertoml-analyzer recon payload B,campaign:OpenVSX-EvilTwin sha256,a27ef5dc929616d46ca1bb3fc12e25195e42726db9135b5946e53036992aa3a5,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX tamuratak.vscode-lezer lightweight payload A,campaign:OpenVSX-EvilTwin sha256,fd8d100ee32bf3f23f9cf82a16c43a61c701380d443d246c34da5aa1d4352e84,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX tgreen7.vs-code-node-require lightweight payload A,campaign:OpenVSX-EvilTwin sha256,98bcafa86bc15ecb93ab47a4e5b7914932699d0ae38ff5f50020cb3d54f3b603,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX yardensachs.copy-python-path lightweight payload A,campaign:OpenVSX-EvilTwin sha256,f6ae05bdf6ed9fd092d0c8976850980b7a03703f9412a8369a5d7ccd8412dba8,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX zoxon.monokai-deep lightweight payload A,campaign:OpenVSX-EvilTwin sha256,18e952ea9848a7bff90d1fdde4b3d6b6648df30959ce6771840ca8cc67fbdfe6,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX bdaeumer.vscode-eslint lightweight payload A,campaign:OpenVSX-EvilTwin sha256,6682a6142cf38e877b2683269c413eab1580e36f8e40876febaf1bf72e12c0f8,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX chavyleung.vscode-pnpm-verlens lightweight payload A,campaign:OpenVSX-EvilTwin sha256,a4f0dd467b3abbdacd5ebb2929de3899c6b3d7b0b90da9ea4ae58e30d101db8f,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX chris-hock.pioasm lightweight payload A,campaign:OpenVSX-EvilTwin sha256,0039e6d3a653d255841320db1dcd9664e1c9ae8b0a0d8a2e9f7e0ecb5fd2c0f6,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX davidpallinder.rails-test-runner lightweight payload A,campaign:OpenVSX-EvilTwin sha256,280ef259644dfbd888ad3eb630aaf6f895449b5e97e075e459fc96c266fb8682,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX Flutterando.flutter-mobx lightweight payload A,campaign:OpenVSX-EvilTwin sha256,5c41fa68ce0f879199d9b5646cf9b82ebde426e1ee0fa2ae10dd6447b0223c54,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX Glavin001.unibeautify-vscode lightweight payload A,campaign:OpenVSX-EvilTwin sha256,15cd8ee6f874539d915a9284adf17a3c2eb0b970c331725a8388f58efba48719,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX GoBystrokReactJS.gobystrok lightweight payload A,campaign:OpenVSX-EvilTwin sha256,375216cb58f3700c181da52c62eccd4534b0d2aaa0d84df6a9f476f35a87217d,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX helixquar.asciidecorator lightweight payload A,campaign:OpenVSX-EvilTwin sha256,58b7c5b92ed0756034af0de02c7addcab215ab612bd37c86bad11aafdfb25d62,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX HyperledgerComposer.composer-support-client lightweight payload A,campaign:OpenVSX-EvilTwin sha256,041019527f4c13b1007b71d35d4383b13ee8c7bf3eaaff366abe90c6c9a804c3,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX Insigne.powershell lightweight payload A,campaign:OpenVSX-EvilTwin sha256,b461c72229cefd5f2078db74565451dc656e71af3a1e95f13db70bf7dec94c70,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX Jeremy38100.init-node-script lightweight payload A,campaign:OpenVSX-EvilTwin sha256,8af30a4e2c57034a64e924a893de50450dd668528f2723a94f907a8a758965ea,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX jt.jakt lightweight payload A,campaign:OpenVSX-EvilTwin sha256,e7ba9bff28fa17d507753e0ca157d7add64cbfed593530504b153e24b6c982c9,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX kaellarkin.hugo-shortcode-syntax lightweight payload A,campaign:OpenVSX-EvilTwin sha256,f04b8c9af22fd8e20fd070806b1aa21424a0f6d5e2c770fa9fb1d131b08393fd,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX Levertion.mcjson lightweight payload A,campaign:OpenVSX-EvilTwin sha256,b490d56da1c5a4e26de8c07f907157b0e328c84464df38d2bd1cb92858906f83,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX magne-sjaastad.opm-flow-editor-support lightweight payload A,campaign:OpenVSX-EvilTwin sha256,6bbaee0ccfbbd45c7683efbf4688ad4532050f8a0ad6b39ea1b435ce2b6cf546,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX MarinhoBrandao.Angular2Tests lightweight payload A,campaign:OpenVSX-EvilTwin sha256,c5fe26dbc56a84ab672a28d3d3d767dd1496a56b027ecec7e3f8f25f83cd6a7d,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX microsoft-dciborow.align-bicep lightweight payload A,campaign:OpenVSX-EvilTwin sha256,0937bfa48a6b2ee53c978c1edea77a04db6ef407adef108b12fb6612e308b275,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX mkdirdocs.mkd-docs lightweight payload A,campaign:OpenVSX-EvilTwin sha256,c578b03b8bd150ec9bf71326293f9f1abc2f1d451b3ca2ee05a516ec53866b1b,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX nihilus118.perl-debugger lightweight payload A,campaign:OpenVSX-EvilTwin sha256,2364b4cf089da6d4d6c1fe854daca03047eea0849b325a3e9f28fac4584418d2,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX pwrs.cem-language-server-vscode lightweight payload A,campaign:OpenVSX-EvilTwin sha256,b301202210164188696b6a6fced62cf0c1641dca650ea4981f81b210ced9116f,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX wghats.vscode-nxunit-test-adapter lightweight payload A,campaign:OpenVSX-EvilTwin sha256,9ec8c53e41766b7b1dbc7fd9acf759b63f65353434231de57d22a6002fef7998,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX wordpresstools.wordpress lightweight payload A,campaign:OpenVSX-EvilTwin sha256,81402a5dc2901bc68ae7e866d78ad7c0ab1c77868d1cb026f95a68a0de851f5d,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX xnerd.ampscript-language lightweight payload A,campaign:OpenVSX-EvilTwin sha256,4b3dfffd11366d4171ba57efe53fc4fe4d4c39e9b8590c81365453005aed9b80,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX ydaveluy.xsmp-modeler lightweight payload A,campaign:OpenVSX-EvilTwin sha256,b9f36f1e416a3b6c650cebcad3dc046c0a0164442d0a2c3ed35279179f457c24,prevent,high,2027-02-08,OpenVSX EvilTwin counterfeit VSIX ydaveluy.xsmp-tas-mdk lightweight payload A,campaign:OpenVSX-EvilTwin
sha256,extension_id,payload b8cd87d5e36f167f51ed6a6d2e824f76dc1b3792de9c10091e46f87ad07f5d5a,lego-education.ev3-micropython,A 8763bda130fe4305c4d13c25584c981424dc47d3de3df46ae0ef05466b05526a,better-ts-errors.better-ts-errors,A 415cef17b6560643c0e4dddccd956ae6887c369d84c7f45be638a76dc4930dc1,groksrc.ruby,A 7342f26ff97c0f8dc29edb1c5a5460bafcaacbc84356020a785628b36156ab6e,maptz.regionfolder,A 5d62cfb190145bd21f930d4c21da01e8dc951e4112992790361e2fd4b09e66f6,mitsuhiko.insta,A d51e331ae110eb37d453e78ac4cf3063d4606ebd2357d8ee4342fd8f06933e20,SBSnippets.pytorch-snippets,A 0ccd62eb6c1155c252248a98c8ff8e1a45a5cc721d6725511effb770037690d5,slb235.vscode-coffeelint,A 1b775ad3ca61addf5bf3e326951314d80ab526cbab0d745edb3e2bd85de2fcd9,amd.gaia-vscode,B a3dbc0a35a781c61c34bf41e9d21eadbf2e77d18531236d1d9c1e622aaf418ef,artsy.artsy-studio-extension-pack,B 941e9cf4fb7a1cdadb1a0e64fce3262426847f167ad255a3f724d77f2d0ee103,configcat.configcat-feature-flags,B fde634f337ab81b3d6bfa320047ff4fe450556fd90f44a30612d549b996edc86,iotaledger.iota-move,B 9f8a69c193c3c4b83db18149408a9687d1cb6ca149ffa3a1ca6d72e95e3b6954,marketplace.visualstudio,B 7179f11710f242a578bd50187dadd7fa2ba0062e11e1a631edcd75417bda3f7e,obyte.oscript-vscode-plugin,B 0e99e23329f3ca908d7a88ccf102e4b10bd9b67f24fc79408aae15d5d10dfd0a,openeuphoria.vscode-euphoria,B fc3a32f59300c7e8b3e486f23146224beb658d10ae04098a9e27faf341356fbe,oss.sfmc-devtools-vscode,B 498b0c9632959eb37cc278f35561f0736c91884b370251dfebae07ff9a942bc3,rumbledb.jsoniq-vscode,B 95fbdc4c81e14a46dd8f3eb5c4207567baae99712e3ccf8644af09680f3caf5e,ssagov.uef-snippets,B 2526c160b6a08377d02e035fc166e91e8029b997fa6c91169640c2ad2c2ec60b,taskfile.vscode-task,B 15cd6a4909161c406b8428100d688999377e399ae13aa4da49bb73744933807d,doi.fileheadercomment,B d35b690502fd3b73312aacf62dc700ac1e43bbc01b53d129096c2c0cdf92a1c4,MengsiCode.vscode-django-boilerplate,B 3b407a548040927802f2cfa05b81eabf48f8b03785ba9fd69a3caa3981a7bc22,move.move-analyzer,B d19c076fc8213617f889a313a3da1565d8364e35c53290fa96400183127b1187,uavcan.dsdl,B 9e148da1d338c32286b54a102ba3a42c7c2627855c3cc820122cff2ee4fc0d90,vs-publisher-988541.apexsql-power-tools,B 03d81fce32d898e4791672e8e324ca8854d4667f56131f9c2b9d13d66faade4f,365businessdevelopment.bdev-al-xml-doc,A 5a1fb5758a6f4b1635d1e1d3fd83c2c13376f466508a949cdad23114af82db45,AcademiaDosDevs.javafx,A 508d3fb15d317846239e8d1f89ab5a5f31242d159f102602cfbf671103658bd3,AlDuncanson.react-hooks-snippets,A 7dc683fc3e0acf757cb48091c259bd71a7e3fe985f83d5b40468fa5b58e70804,Alex-Chen.gitee-code-settings-sync,A 3f97f02073caab6208d103693b13105f9dd42daca379e07953e433bd50ec5d3a,AndenetAlexander.vim-cheatsheet,A 3e9c96b913b54af9d50966b27b878750c3ae9d8e7944c4437a40d5a5cbc5ef21,AndreyVolosovich.monokai-st3,A aac1efa84293929006436160f77e58e70d9025fc0b320ecc64953e066785a0fa,angelo-breuer.license-header-manager,A 8e90e637bd7f3a2eaee077f216610248bf4d41c8d1f7880a2d2623be13be339f,AshhadDevLab.customtkinter-snippets,A 410e6b62b5e62cd8603b374f34e5ec6dad62bb95435fa320e08a850fa359f8c5,AzureADB2CTools.aadb2c,A 805c0c50271afbe94301d376cc5eb191feb7a409ea05f7771dcb4a07dcb0442a,AzurePolicy.azurepolicyextension,A 9afe7dd8f3eece28719edac1572ca7adbf307fdb29580535cbc109dec2e44da0,BartmanAbyss.amiga-debug,A 1a1211418b048678ff5a219d29587cb4beea251615bd8319b2a39caca0ac9cf9,BDZNH.c-cpp-compile-run-windows,A 67d23c83dfe5199815aeb471bf468bb99faa6103e7238484e26e106194548642,bradymholt.pgformatter,A cf3593f41cb52c4b52386b539bc1b5095e6123de3d6bfda6f85f98b4570af6a4,BretDoyle.javascript-extensions-pack---js-essentials,A abcdee583b02c827fdd746acad814545f9e823d5c7859743d090e176e0f344f4,casualjim.gotemplate,B 48976edf057d4064db52c8ccd5d2b70769008556072cc411617dc6eeb28cbd53,drewbourne.vscode-remark-lint,A 573072c5107fb2fc749225caeee8ddabe7b1b6c9b45463a9e3247d4658870d33,garaemon.vscode-emacs-tab,A 796d0536a0de16cd09f768b24d56763bdf8b36535c4ff946a754ff1f16857a65,jakeboone02.cypher-query-language,A 3d734eb54a45f18ffa885f21240c95fccbd4a222368044ae01920176a3666d0e,jcamp.dotnet-test-provider-view,B db253057b1d54c0d4bf2ea5d358e33043292c0e1780989a030f489a194d83cb5,madhavd1.javadoc-tools,A e25513c12e71784067a36db09670c244eb2f22272a5cf4589cb46bc6a27b6fb7,miclo.sort-typescript-imports,A 1a5b4ea5cb768aec7bbba28d881774a124aeb84ee8827d56aa0213ecf59a77a1,Mukundan.python-docs,A 52a54dd4b72d7c113b22c54a3fe48131d064215f1987589b4ce5aac13573c96d,npxms.hide-gitignored,A b0e0615140a5aebf3ef3e99693e11f3fcc2c2c0c8a60e92fd4152a8e29c418a5,qiu.llvm-ir-language-support,A b7655dd07d18bfb6f9b724647fd393ce2f312b2a874a046e2f568aa9c1eec32f,superposition.supertoml-analyzer,B a27ef5dc929616d46ca1bb3fc12e25195e42726db9135b5946e53036992aa3a5,tamuratak.vscode-lezer,A fd8d100ee32bf3f23f9cf82a16c43a61c701380d443d246c34da5aa1d4352e84,tgreen7.vs-code-node-require,A 98bcafa86bc15ecb93ab47a4e5b7914932699d0ae38ff5f50020cb3d54f3b603,yardensachs.copy-python-path,A f6ae05bdf6ed9fd092d0c8976850980b7a03703f9412a8369a5d7ccd8412dba8,zoxon.monokai-deep,A 18e952ea9848a7bff90d1fdde4b3d6b6648df30959ce6771840ca8cc67fbdfe6,bdaeumer.vscode-eslint,A 6682a6142cf38e877b2683269c413eab1580e36f8e40876febaf1bf72e12c0f8,chavyleung.vscode-pnpm-verlens,A a4f0dd467b3abbdacd5ebb2929de3899c6b3d7b0b90da9ea4ae58e30d101db8f,chris-hock.pioasm,A 0039e6d3a653d255841320db1dcd9664e1c9ae8b0a0d8a2e9f7e0ecb5fd2c0f6,davidpallinder.rails-test-runner,A 280ef259644dfbd888ad3eb630aaf6f895449b5e97e075e459fc96c266fb8682,Flutterando.flutter-mobx,A 5c41fa68ce0f879199d9b5646cf9b82ebde426e1ee0fa2ae10dd6447b0223c54,Glavin001.unibeautify-vscode,A 15cd8ee6f874539d915a9284adf17a3c2eb0b970c331725a8388f58efba48719,GoBystrokReactJS.gobystrok,A 375216cb58f3700c181da52c62eccd4534b0d2aaa0d84df6a9f476f35a87217d,helixquar.asciidecorator,A 58b7c5b92ed0756034af0de02c7addcab215ab612bd37c86bad11aafdfb25d62,HyperledgerComposer.composer-support-client,A 041019527f4c13b1007b71d35d4383b13ee8c7bf3eaaff366abe90c6c9a804c3,Insigne.powershell,A b461c72229cefd5f2078db74565451dc656e71af3a1e95f13db70bf7dec94c70,Jeremy38100.init-node-script,A 8af30a4e2c57034a64e924a893de50450dd668528f2723a94f907a8a758965ea,jt.jakt,A e7ba9bff28fa17d507753e0ca157d7add64cbfed593530504b153e24b6c982c9,kaellarkin.hugo-shortcode-syntax,A f04b8c9af22fd8e20fd070806b1aa21424a0f6d5e2c770fa9fb1d131b08393fd,Levertion.mcjson,A b490d56da1c5a4e26de8c07f907157b0e328c84464df38d2bd1cb92858906f83,magne-sjaastad.opm-flow-editor-support,A 6bbaee0ccfbbd45c7683efbf4688ad4532050f8a0ad6b39ea1b435ce2b6cf546,MarinhoBrandao.Angular2Tests,A c5fe26dbc56a84ab672a28d3d3d767dd1496a56b027ecec7e3f8f25f83cd6a7d,microsoft-dciborow.align-bicep,A 0937bfa48a6b2ee53c978c1edea77a04db6ef407adef108b12fb6612e308b275,mkdirdocs.mkd-docs,A c578b03b8bd150ec9bf71326293f9f1abc2f1d451b3ca2ee05a516ec53866b1b,nihilus118.perl-debugger,A 2364b4cf089da6d4d6c1fe854daca03047eea0849b325a3e9f28fac4584418d2,pwrs.cem-language-server-vscode,A b301202210164188696b6a6fced62cf0c1641dca650ea4981f81b210ced9116f,wghats.vscode-nxunit-test-adapter,A 9ec8c53e41766b7b1dbc7fd9acf759b63f65353434231de57d22a6002fef7998,wordpresstools.wordpress,A 81402a5dc2901bc68ae7e866d78ad7c0ab1c77868d1cb026f95a68a0de851f5d,xnerd.ampscript-language,A 4b3dfffd11366d4171ba57efe53fc4fe4d4c39e9b8590c81365453005aed9b80,ydaveluy.xsmp-modeler,A b9f36f1e416a3b6c650cebcad3dc046c0a0164442d0a2c3ed35279179f457c24,ydaveluy.xsmp-tas-mdk,A
# Behavioural signals -- no atomic indicator required. # These keep working after the operator moves collectors over the _beacon TXT channel. 1. Editor or extension-host process resolves a domain registered within the last 30 days, within seconds of editor activation. -> Q3 + domain-age enrichment 2. DNS TXT lookup for any label beginning "_beacon." -> Q2 3. Extension host reads .git/config and .git/HEAD, then opens an external connection from the same process id inside 10 s. -> Q7 correlated with Q4 4. Outbound HTTP User-Agent "vscode-ext-metrics" (any version). -> Q9 5. Request path /t/ followed by exactly 24 hex characters. -> Q10 6. Beacon cadence: retries at ~15 min, ~50 min, ~3.5 h, then every 7-8 h, resuming on editor restart, ceasing after 7 days. 7. Extension provides no advertised feature: status-bar checkmark plus a single "extension is active" command, and nothing else. 8. Editor extension installed at version 0.0.1 under a namespace belonging to a well-known vendor. -> Q5, Q8, N3, N4
amd.gaia-vscode artsy.artsy-studio-extension-pack casualjim.gotemplate configcat.configcat-feature-flags doi.fileheadercomment iotaledger.iota-move jcamp.dotnet-test-provider-view marketplace.visualstudio MengsiCode.vscode-django-boilerplate move.move-analyzer obyte.oscript-vscode-plugin openeuphoria.vscode-euphoria oss.sfmc-devtools-vscode rumbledb.jsoniq-vscode ssagov.uef-snippets superposition.supertoml-analyzer taskfile.vscode-task uavcan.dsdl vs-publisher-988541.apexsql-power-tools
# POSIX -- list what is ACTUALLY installed, for every editor variant present.
for cli in code code-insiders codium code-oss cursor windsurf; do
command -v "$cli" >/dev/null 2>&1 && \
"$cli" --list-extensions --show-versions 2>/dev/null | sed "s|^|$cli |"
done | sort -u
# Also enumerate the extension directories directly -- catches editors whose CLI
# is not on PATH, and copies baked into images by a Dockerfile COPY.
ls -1 ~/.vscode/extensions ~/.vscode-server/extensions ~/.vscode-oss/extensions \
~/.cursor/extensions ~/.windsurf/extensions 2>/dev/null | sort -u
# Repository / image config pins -- these re-install on every rebuild.
grep -rIl --include=extensions.json --include=devcontainer.json \
-e "recommendations" -e "customizations" . 2>/dev/null
# Block the whole zone. Manifold confirms all 77 samples reference it, and the # randomised cb.* labels make host-by-host blocking pointless. mangorbit.com *.mangorbit.com # Also alert (do not silently drop) on outbound DNS TXT queries for any _beacon.* label. # That is the operator's post-takedown relocation channel; a silent drop hides the pivot. _beacon.* # Sinkhole verification after deployment: # Windows : Resolve-DnsName pulse.mangorbit.com # POSIX : dig +short pulse.mangorbit.com # Expect NXDOMAIN or your sinkhole address. A real answer means the block is not applied.
Every atomic indicator above is traced to a saved source snapshot in OpenVSX-EvilTwin-Extensions-Hunt-sources/. The second failover domain referenced by a subset of samples is deliberately absent — Manifold declined to publish it, so this pack cannot cite it. Q3 and Q4 cover it behaviourally.
Detection Validation Gates
| Gate | Check | Pass criteria |
|---|---|---|
| G1 — Telemetry ready | Confirm DnsRequest and NetworkConnectIP4 return rows with ContextBaseFileName populated for at least one known developer host in the last 24 h. | Both events present with a non-empty process context. If ContextBaseFileName is blank, Q3 and Q4 silently return nothing. |
| G2 — Editor coverage | Run Q3 with the domain-exclusion line removed and confirm each editor variant your estate actually uses appears in the results. | Every editor in use appears at least once. If cursor or windsurf never appears, its process name differs in your build — fix the regex before trusting the query. |
| G3 — File telemetry reality check | Confirm FileOpenInfo and CriticalFileModified actually return rows in your tenant. | Rows returned. If not, mark Q7 and Q8 as unavailable in your coverage record rather than leaving them looking deployed. This is the most common silent failure in this pack. |
| G4 — Proxy parser field names | Pull one sample proxy row and confirm the User-Agent and URL field names match Q9 and Q10. | Field names confirmed and the queries edited to match. Both cards carry a ⚠ validate in tenant note precisely because this varies by vendor. |
| G5 — Benign baseline | Run Q3 and Q4 over 7 days on a normal population and record the volume before tuning. | A reviewable result set after the exclusion list is extended. If either returns more than roughly 100 rows a day, keep tuning; do not schedule it. |
| G6 — Positive test, network | From an isolated test host, issue a DNS lookup for a sinkholed name in the zone and a TXT lookup for a _beacon.-prefixed label under a domain you control. | Q1 and Q2 both fire within the expected ingest delay. Never execute a real sample to test — a DNS lookup is sufficient and safe. |
| G7 — Positive test, install | On a test host, temporarily add a harmless extension ID to the Q5 regex and install it via the editor CLI. | Q5 returns the row; the IOA-1 rule fires. Revert the pattern afterwards. |
| G8 — Lookup file loaded | Run Q11 with strict=false against any event stream and confirm the enrichment columns appear. | extension_id and payload are populated on matched rows. If the columns are empty, the lookup file did not load or the column headers differ. |
| G9 — Promotion | Only Q1, Q2, Q9 and Q11 are eligible for a Scheduled Search with email notification. | Q3, Q4, Q6, Q7, Q8 and Q10 remain investigate-only until their FP rating is measured at low in your own environment. |
| G10 — Block verification | After deploying the DNS/egress block and the Falcon domain IOC, resolve a collector host from a normal developer subnet. | NXDOMAIN or sinkhole. A real answer means the block did not reach that subnet — dev VLANs are a frequent gap. |
Hardening — Tiered
H-1 · Block the collector zone at DNS, egress and in Falcon
MITRE M1037 (Filter Network Traffic) · M1031 (Network Intrusion Prevention). Block mangorbit.com and *.mangorbit.com at the recursive resolver, the egress firewall and the SWG, and import the domain rows from §10 into Falcon IOC Management with action prevent. Alert rather than silently drop on _beacon.* TXT queries — a silent drop hides the operator's relocation attempt.
Verify: Resolve-DnsName pulse.mangorbit.com (Windows) or dig +short pulse.mangorbit.com (POSIX) from a developer subnet returns NXDOMAIN or your sinkhole. CQL spot-check: Q1 returns no new rows after the block date.
H-2 · Inventory what is installed, not what is requested
MITRE M1033 (Limit Software Installation) · CIS Controls v8 §2.1 (software inventory). Run playbook P1 across every developer workstation, remote dev host, CI runner image and golden image; diff against the 77 IDs in §4. This is Manifold's headline recommendation and it is the only step that finds copies the registry takedown left behind.
Verify: an inventory record exists for every host in the developer and build estate, with a date after 2026-08-08 and a recorded diff result.
H-3 · Purge repository and container config pins
MITRE M1051 (Update Software) · M1045 (Code Signing) as the long-term control. Search every repository for .vscode/extensions.json, .devcontainer/devcontainer.json, .devcontainer.json, Dockerfiles and provisioning scripts referencing any of the 77 IDs, and remove them. A surviving pin re-installs the extension on every clone and every container build, indefinitely.
Verify: a source-control-wide search for the 19 recon IDs returns zero results on the default branch of every repository, and zero in image build contexts.
H-4 · Enforce an extension allowlist in the editor itself
MITRE M1033 (Limit Software Installation) · vendor guidance: VS Code's extensions.allowed policy setting. Publish an allowlist keyed by publisher.extension — optionally pinned to specific versions — and deploy it via Group Policy, Intune, Jamf or a managed settings.json. This turns "install by name" from an unchecked operation into an approved one, which is the actual root cause here. Playbook P2 has the configuration.
Verify: on a piloted host, attempt to install an extension outside the allowlist and confirm the editor refuses. ⚠ best practice / vendor setting — no CIS benchmark covers editor extension policy yet.
H-5 · Pin the internal mirror by publisher and version
MITRE M1051 · M1016 (Vulnerability Scanning). If you mirror Open VSX internally, pin every entry by publisher and version rather than syncing by name, and treat the Open VSX unverified-publisher banner as a blocking condition in automated installs rather than a cosmetic notice. A name-syncing mirror will have cached these counterfeits and can keep serving them after the upstream takedown.
Verify: mirror configuration shows explicit publisher and version pins; a test sync of a namespace with an unverified publisher is rejected.
H-6 · Deny-by-default egress from CI runners and devcontainers
MITRE M1037 (Filter Network Traffic) · M1030 (Network Segmentation) · CIS Controls v8 §13.4, §4.4. Build runners and devcontainers should reach your registries, your artifact proxy and your VCS — and nothing else. This is the control that would have contained the actual damage here, because the runner is where GITHUB_REPOSITORY and CI_PROJECT_PATH resolve to a private repository path.
Verify: from inside a build container, an outbound HTTPS request to an arbitrary external host fails. Re-run after every runner image change.
H-7 · Stop exporting repository-identifying CI variables to untrusted steps
MITRE M1018 (User Account Management) · M1022 (Restrict File and Directory Permissions). Scope the environment visible to third-party tooling — including editor extensions running in a devcontainer — so that repository-path variables and cloud workspace identifiers are only present in the steps that genuinely need them. Prefer short-lived OIDC tokens over long-lived variables generally, and treat the repository path itself as sensitive metadata on private projects.
Verify: print the environment in a representative untrusted step and confirm no repository-path variable is present. ⚠ best practice, no formal benchmark for this specific control.
H-8 · Runtime behavioural monitoring of extensions and agents
MITRE M1040 (Behavior Prevention on Endpoint). Static scanning cannot see this campaign: the code is clean, legible, commented and honest about most of what it does. The only reliable signal is runtime behaviour — what an extension reads and where it connects, in the environment where it runs. Establish per-extension behavioural baselines for the editor estate and alert on deviation. Q3, Q4 and Q7 are the interim version of this control.
Verify: a documented behavioural baseline exists for the top 20 extensions in use, and a deviation alert has been tested end to end.
H-9 · Treat the developer toolchain as a first-class supply chain
MITRE M1051 · M1016 · NIST SP 800-161r1 (C-SCRM) · SLSA provenance. Bring editor extensions into the same intake, SBOM and provenance process as application dependencies: an owner, a review record, a pinned version, and a periodic re-review. The gap this campaign exploited is that two registries hold the same names under different ownership rules, and a name resolved by an agent or a container build cannot tell which one it got.
Verify: the extension allowlist has a named owner and a review cadence; new entries require a provenance check against the upstream project.
H-10 · Separate developer identity from CI identity
MITRE M1026 (Privileged Account Management) · M1030 (Network Segmentation). Assume the workstation is an untrusted reader of repository metadata. Anything a workstation-resident process can read about your repositories and CI should be information you are content to have leave the building — and everything above that bar should live only in the build plane, behind H-6 and H-7.
Verify: a documented data-classification decision covering repository paths and CI identifiers, with the controls that enforce it named.
Deployable Playbooks
P1 · Fleet extension inventory and removal
Prerequisites: RMM or configuration-management agent able to run a script as the logged-in user (extensions are per-user, so a SYSTEM-context run finds nothing); PowerShell 5.1+ on Windows or bash on POSIX; the 77-ID list from §4 staged as ids.txt, one ID per line.
Reboot required: No. The editor must be restarted for a removal to take effect.
Rollback: Reinstall the extension from your approved source with code --install-extension <publisher.name>@<version>. Step 3 writes each removed ID and version to the report file before uninstalling, so the pre-change state is recoverable. Do not use this rollback for any of the 77 IDs — those are counterfeits and the genuine project is a different package.
# --- Step 1. Enumerate what is installed, per editor variant, as the logged-in user ---
# Windows (PowerShell, user context)
$clis = 'code','code-insiders','codium','code-oss','cursor','windsurf'
$found = foreach ($c in $clis) {
$exe = Get-Command $c -ErrorAction SilentlyContinue
if ($exe) { & $exe.Source --list-extensions --show-versions 2>$null |
ForEach-Object { [pscustomobject]@{ Cli=$c; Id=$_ } } }
}
$found | Export-Csv "$env:TEMP\ext-inventory.csv" -NoTypeInformation
# POSIX (bash)
for c in code code-insiders codium code-oss cursor windsurf; do
command -v "$c" >/dev/null 2>&1 && \
"$c" --list-extensions --show-versions 2>/dev/null | sed "s|^|$c,|"
done | sort -u > /tmp/ext-inventory.csv
# --- Step 2. Diff against the counterfeit list (ids.txt, one publisher.name per line) ---
# Windows
$bad = Get-Content .\ids.txt
$hits = $found | Where-Object { $bad -contains ($_.Id -split '@')[0] }
$hits | Format-Table -AutoSize
# POSIX
cut -d, -f2 /tmp/ext-inventory.csv | cut -d@ -f1 | sort -u > /tmp/installed.txt
comm -12 /tmp/installed.txt <(sort -u ids.txt)
# --- Step 3. Record, then remove ---
# Record FIRST so the pre-change state is recoverable, then uninstall.
# Windows
$hits | Export-Csv "$env:TEMP\ext-removed.csv" -NoTypeInformation
foreach ($h in $hits) { & (Get-Command $h.Cli).Source --uninstall-extension ($h.Id -split '@')[0] }
# POSIX
comm -12 /tmp/installed.txt <(sort -u ids.txt) | tee /tmp/ext-removed.txt | \
while read -r id; do code --uninstall-extension "$id"; done
# --- Step 4. Sweep the extension directories for leftovers ---
# An uninstall can leave the directory behind; the beacon lives in extension.js inside it.
# Windows
Get-ChildItem "$env:USERPROFILE\.vscode\extensions","$env:USERPROFILE\.cursor\extensions" `
-Directory -ErrorAction SilentlyContinue | Where-Object { $bad -contains ($_.Name -replace '-\d+\.\d+\.\d+$','') }
# POSIX
ls -1d ~/.vscode/extensions/* ~/.vscode-server/extensions/* ~/.cursor/extensions/* 2>/dev/null
# --- Step 5. Verify ---
# Restart the editor, then re-run Step 1 and confirm no ID from ids.txt appears.
# Then re-run CQL Q1 scoped to this aid over the next 24 h and confirm no new collector contact.
P2 · Editor extension allowlist policy
Prerequisites: VS Code 1.96 or later (the extensions.allowed policy setting); a management channel that can write a machine-scope settings file or a policy value — Group Policy / Intune on Windows, a configuration profile or Jamf on macOS, configuration management on Linux; an agreed allowlist of publisher.extension entries.
Reboot required: No. Editor restart required.
Rollback: Delete the extensions.allowed key from the machine settings file (or set the policy to Not Configured) and restart the editor; extension installation returns to unrestricted immediately. Back up the existing machine settings.json before writing, as shown in step 1.
# --- Step 1. Back up the existing machine-scope settings ---
# Windows : %ProgramData%\Microsoft\VS Code\settings.json (or the Intune/GPO policy path)
# macOS : /Library/Application Support/Code/User/settings.json
# Linux : /etc/vscode/settings.json (path varies by packaging -- confirm on a pilot host)
Copy-Item "$env:ProgramData\Microsoft\VS Code\settings.json" `
"$env:ProgramData\Microsoft\VS Code\settings.json.bak" -ErrorAction SilentlyContinue
# --- Step 2. Write the allowlist ---
# "true" allows any version of that extension; a version array pins it.
# An entry of "*": false denies everything not explicitly listed -- that is the whole point.
{
"extensions.allowed": {
"*": false,
"ms-python.python": true,
"ms-vscode.cpptools": true,
"esbenp.prettier-vscode": ["11.0.0"],
"dbaeumer.vscode-eslint": true
},
"extensions.autoUpdate": false,
"extensions.autoCheckUpdates": false
}
# --- Step 3. Deploy to a pilot ring ---
# Start with one team. An over-tight allowlist breaks people's editors, which is how
# an allowlist gets rolled back permanently. Collect the misses, then widen once.
# --- Step 4. Verify ---
# On a pilot host: code --install-extension some.unlisted-extension
# Expect the install to be refused. Then confirm the approved set still installs.
# --- Step 5. Wire the allowlist to the intake process (H-9) ---
# Every addition needs an owner and a provenance check against the upstream project,
# or the allowlist becomes a list of everything anyone ever asked for.
P3 · Block deployment and verification
Prerequisites: administrative access to the recursive resolver / DNS firewall, the egress firewall or SWG, and Falcon IOC Management (write scope on the IOC Management API or console role); the CSV from §10.
Reboot required: No.
Rollback: Remove the zone entries from the resolver and firewall policy, and set the imported Falcon IOCs to no action or delete them by tag campaign:OpenVSX-EvilTwin — the tag exists specifically so the whole import can be reversed in one operation. Rollback takes effect at the next policy push; no restart needed.
# --- Step 1. Resolver / DNS firewall --- # Add a blocking or sinkhole entry for the zone and everything under it. mangorbit.com *.mangorbit.com # Configure the response as a sinkhole address you monitor, not NXDOMAIN, if you want # to keep seeing which hosts are still trying. NXDOMAIN is fine if you rely on Q1 instead. # --- Step 2. Egress firewall / SWG --- # Same zone. Also create an ALERT (not a drop) rule for outbound DNS TXT queries whose # QNAME begins "_beacon." -- a silent drop hides the operator relocating collectors. # --- Step 3. Falcon IOC Management --- # Console: Endpoint security -> IOC management -> Import. Use the CSV from section 10. # Set the domain rows to action=prevent and confirm they apply to your developer host groups. # Tag every row campaign:OpenVSX-EvilTwin so the whole import can be reversed at once. # --- Step 4. Verify from a developer subnet, not from the SOC subnet --- Resolve-DnsName pulse.mangorbit.com # Windows -- expect NXDOMAIN or sinkhole dig +short pulse.mangorbit.com # POSIX -- expect empty or sinkhole # A real answer means the policy did not reach that VLAN. Developer and lab VLANs are # the usual gap, and they are exactly the population that matters here. # --- Step 5. Confirm in telemetry --- # Re-run CQL Q1 the day after deployment. New rows after the block date mean either the # block is incomplete, or a host is using a resolver you do not control (check for DoH).
P4 · CI image and repository sweep
Prerequisites: read access to every repository and to the image build definitions; the 77-ID list as ids.txt; a source-control search API token or a local clone of each repository.
Reboot required: No.
Rollback: None required — this playbook only reads. The follow-up removals it feeds are covered by P1 (endpoints) and by ordinary version control (repository configs revert with git revert of the cleanup commit).
# --- Step 1. Find every extension-pinning config in the estate ---
find . -type f \( -name extensions.json -o -name devcontainer.json \) \
-not -path "*/node_modules/*" -print > /tmp/ext-configs.txt
# --- Step 2. Grep those configs for the counterfeit IDs ---
grep -Fnif ids.txt $(cat /tmp/ext-configs.txt) 2>/dev/null
# --- Step 3. Check image build definitions too ---
# A Dockerfile RUN line or a post-create hook installs by name just as a devcontainer does.
grep -rIn --include=Dockerfile --include="*.dockerfile" --include="*.sh" --include="*.yml" \
-e "--install-extension" . 2>/dev/null | grep -Ff ids.txt
# --- Step 4. Check what the built images actually contain ---
# The config is the request; the image is the fact. Inspect a built image, not just its recipe.
docker run --rm --entrypoint sh <image> -c \
'ls -1 /home/*/.vscode-server/extensions /root/.vscode-server/extensions 2>/dev/null'
# --- Step 5. Record and remediate ---
# For each hit: remove the pin, rebuild the image, and run P1 on any host built from it.
# Record the repository, the file, the ID and the payload class (A or B) -- payload B means
# that project's private repository path was disclosed from every machine that built it.
Containment Runbook
| Phase | Actions | Owner | Evidence to capture |
|---|---|---|---|
| 0 · Scope hour 0–2 | Run Q1 and Q11 over max retention with no host filter. Run native hunt N1 against resolver logs to catch sensorless hosts. Classify every hit: workstation, remote dev host, CI runner, container, golden image. | Threat hunting | Host list with aid, first-contact timestamp, and the matched extension ID and payload class for each. |
| 1 · Isolate hour 0–4 | Deploy playbook P3 — zone block at resolver, egress and Falcon IOC. Do not network-contain workstations for this threat: no code execution, no lateral movement and no credential theft is in evidence, so containment cost outweighs benefit. Do quarantine affected CI runner images from further builds until swept. | Network ops + SOC | Block policy diff with timestamp; verification output from P3 step 4 taken from a developer subnet. |
| 2 · Eradicate — endpoints day 0–2 | Run playbook P1 across the affected population. Remove the extension, sweep the extension directory for leftovers, restart the editor. | Endpoint engineering | ext-removed.csv per host; post-removal inventory showing a clean diff. |
| 3 · Eradicate — configs and images day 0–5 | Run playbook P4. Remove every repository and Dockerfile pin, rebuild affected images, re-run P1 on hosts built from them. Purge the counterfeits from any internal Open VSX mirror cache. | Platform / DevEx | Cleanup commit SHAs; rebuilt image digests; mirror cache purge record. |
| 4 · Assess disclosure day 1–5 | For every payload B hit, record what was readable at the time: Git origin and upstream host plus organisation, commit-email domain, branch, HEAD commit SHA, workspace path, and any CI identity value the host exported. On a runner, that includes the private repository path. Payload A hits are hostname-level only — do not over-report them. | Incident response | Per-host disclosure record, scoped to what the code actually reads. No credentials, tokens, SSH material, source code or browser data are in scope. |
| 5 · Downstream risk day 2–10 | Nothing needs rotating — no secrets were taken. What changed is that private repository paths, organisation names and developer email domains may now be known to a third party. Brief the affected teams to expect targeted phishing referencing real internal project names, and add those project names to your phishing-simulation and detection content. | Incident response + security awareness | Notification record; phishing-content update ticket. |
| 6 · Recover day 3–14 | Deploy the H-4 allowlist to a pilot ring, then broadly. Pin the internal mirror (H-5). Apply deny-by-default egress to runners and devcontainers (H-6). Keep the zone block permanent — the registration runs to 2029. | Platform / DevEx + network ops | Allowlist policy deployed and verified per P2 step 4; runner egress policy verified from inside a build container. |
| 7 · Watch ongoing | Keep Q2 scheduled indefinitely — a _beacon TXT hit means the operator has relocated and Q1 is blind. Keep Q3 and Q4 on a weekly cadence with domain-age enrichment. Re-run Q11 after any change to the runner or golden-image build. | Threat hunting | Scheduled-search configuration; weekly hunt notes. |
Detection Coverage Map
| Technique | Behaviour | CQL | IOA / control | Coverage |
|---|---|---|---|---|
| T1195.002 | Counterfeit package published and pulled from Open VSX | Q5, Q6, Q11 | H-2, H-5, N4 | GOOD |
| T1176.002 | Malicious IDE extension installed and loading on every editor start | Q5, Q6, Q8, Q11 | IOA-1, H-4 | GOOD |
| T1036.005 | Name, namespace and description copied from a real extension; version pinned to 0.0.1 | Q5, Q8 | H-4, H-5, N3, N4 | PARTIAL — no endpoint telemetry distinguishes a counterfeit name from a real one; caught at the registry and config layer, not on the host |
| T1554 | extension.js swapped for a beacon inside an otherwise normal package | Q6, Q11 | H-8 | GOOD — hash-level, for the 77 known samples |
| T1204 | Install by name, by a human or by an agent / devcontainer / provisioning script | Q5, Q8 | H-3, H-4, N3 | PARTIAL — GUI marketplace installs leave no command line |
| T1082 / T1033 | Host, user, editor, platform, locale and timezone collected | Q3, Q4, Q7 | H-6, H-8 | PARTIAL — inferred from the egress, not observed directly |
| T1005 / T1083 | .git/config and .git/HEAD read; workspace path collected | Q7 | H-8 | PARTIAL — depends on FileOpenInfo being enabled, which it often is not (gate G3) |
| T1518 | Up to 60 installed extension IDs enumerated | — | H-8 | GAP — reading the extensions directory is indistinguishable from the editor's own startup behaviour |
| T1016 | Proxy hostname read from the environment | — | H-7 | GAP — an in-process environment read produces no endpoint event |
| — | CI identity values read (GITHUB_REPOSITORY, CI_PROJECT_PATH, Azure DevOps URI, Buildkite slug, CircleCI username, Codespace name, Gitpod URL) | — | H-6, H-7 | GAP — same reason. This is the highest-impact behaviour in the campaign and it is invisible to endpoint telemetry. Prevention (H-6, H-7) is the only real answer. |
| — | Extension reports whether a repo config pulled it in; reads the telemetry opt-out and ignores it | — | H-3 | GAP — in-process logic, no observable artefact |
| T1071.001 / T1041 | HTTPS beacon to the collector with a distinctive User-Agent and URI paths | Q1, Q4, Q9, Q10 | H-1, H-6 | GOOD where proxy logs reach NG-SIEM; PARTIAL without them |
| T1071.004 / T1568 | DNS TXT lookup at _beacon.<domain> for a replacement collector; randomised cb labels | Q1, Q2 | H-1 | GOOD |
| T1008 | Multi-endpoint failover, any HTTP status counted as success, 7-day retry ladder | Q4 (cadence) | H-1, H-6 | PARTIAL — the undisclosed second failover domain is only reachable behaviourally, via Q3 and Q4 |
Validation gates: §11 carries ten gates. The three that decide whether this pack works in your tenant are G1 (is ContextBaseFileName populated — without it Q3 and Q4 return nothing and look clean), G3 (does FileOpenInfo return rows at all — if not, Q7 and Q8 are unavailable and the T1005 row above drops to GAP), and G4 (do your proxy parser field names match Q9 and Q10). Only Q1, Q2, Q9 and Q11 are eligible for promotion to a Scheduled Search per G9; everything else stays investigate-only until its FP rate is measured locally.
Standing gap: steps 4, and the environment-variable and extension-enumeration reads in step 6 of the attack chain, produce no endpoint artefact at all — they happen inside the Node process. No query in this pack can close them, and none pretends to. They are covered by prevention (H-4, H-6, H-7) and by runtime behavioural monitoring (H-8), not by detection.
Hunt Summary Ticket
TITLE: Hunt - Open VSX "Evil Twin" counterfeit extension campaign (mangorbit collector)
SEVERITY: High
SCOPE: Developer workstations (Windows / macOS / Linux), remote dev hosts, CI/CD build
runners, devcontainers and Codespaces, golden images and VDI templates, internal
Open VSX mirror, web proxy / SWG, recursive DNS resolvers.
HYPOTHESIS: One or more endpoints or build images in the estate carry one of 77 counterfeit
Open VSX extensions published 2026-07-26 to 2026-08-01, each beaconing to
mangorbit[.]com. 19 of the 77 exfiltrate Git remote host and organisation, commit
email domain, branch, HEAD commit SHA and CI identity values -- on a build runner
that is the full private repository path. Packages were removed from Open VSX on
2026-08-03; installed copies and config pins are unaffected by that removal and
the beacon still fires on every editor start.
QUERIES: Q1 collector domain resolved CONF high / FP low [ALERT]
Q2 DNS TXT _beacon.* takedown-contingency lookup CONF med / FP low [ALERT]
Q3 rare external domain from an editor process CONF med / FP med [HUNT]
Q4 editor / extension-host rare external egress CONF med / FP med [HUNT]
Q5 CLI install of a counterfeit extension ID CONF high / FP low [ALERT]
Q6 VSIX written to disk CONF med / FP med [HUNT]
Q7 editor reads .git config / HEAD CONF med / FP high [HUNT]
Q8 repo extension manifests written CONF low / FP high [HUNT]
Q9 User-Agent vscode-ext-metrics in proxy logs CONF high / FP low [ALERT]
Q10 collector URI paths in proxy logs CONF med / FP med [HUNT]
Q11 known counterfeit VSIX SHA-256 sweep (all 77) CONF high / FP low [ALERT]
DO FIRST: 1. Block mangorbit.com and *.mangorbit.com at resolver, egress and Falcon IOC
management (playbook P3). Verify from a DEVELOPER subnet, not the SOC subnet.
2. Run Q1 and Q11 over maximum retention with no host filter.
3. Run native hunt N1 against resolver logs -- catches sensorless runners and
containers that Q1 cannot see.
4. Run playbook P1 across developer hosts, runner images and golden images.
5. Run playbook P4 across repositories and image build definitions.
FINDINGS: [ ] hosts with collector contact (Q1 / N1): ____
[ ] hosts with a matching VSIX hash (Q11): ____
[ ] repository or image configs pinning a counterfeit ID (P4): ____
[ ] of the above, how many are payload B (Git + CI disclosure): ____
[ ] CI runners or cloud dev environments affected: ____
GAPS: - Extension enumeration, CI environment-variable reads and the proxy-hostname read
happen in-process and produce NO endpoint artefact. Prevention only (H-6, H-7).
- Q7 and Q8 depend on FileOpenInfo / CriticalFileModified, which are off by default
in many sensor policies. Confirm via validation gate G3 before relying on them.
- The second failover domain used by a subset of samples was not published by the
researchers, so it is not in this pack. Q3 and Q4 cover it behaviourally.
- GUI marketplace installs leave no command line, so Q5 misses them.
ACTIONS: [ ] P3 block deployed and verified from a developer subnet
[ ] P1 inventory + removal complete across the developer and build estate
[ ] P4 repository and image sweep complete; pins removed; images rebuilt
[ ] Q1 / Q2 / Q9 / Q11 promoted to Scheduled Searches (gate G9)
[ ] Payload-B disclosure record written per affected host
[ ] Affected teams briefed on targeted phishing using real internal project names
[ ] H-4 extension allowlist piloted (playbook P2)
[ ] H-6 deny-by-default egress applied to CI runners and devcontainers
OWNER: SOC / Threat Hunting (co-owners: Platform-DevEx for P2/P4, Network Ops for P3)
VERSION: v0.1 (2026-08-08)
Changelog
References
| Tier | Source | Used for | Accessed |
|---|---|---|---|
| 1 · Primary | Manifold Security — 77 "evil twin" Open VSX extensions: 19 copy private repo and CI data to a new domain (Ax Sharma, Cody Nash, 2026-08-04) | Every atomic indicator; the 77-package table with versions, payload class, beacon hosts, first-seen dates and VSIX SHA-256; retry schedule; TXT failover; payload field lists | 2026-08-08 |
| 2 | The Hacker News — Open VSX Removes 77 Malicious Evil Twin Extensions Exfiltrating Developer Data (2026-08-05) | Independent confirmation of counts, dates, collected field set; the 19 recon extension IDs | 2026-08-08 |
| 2 | BleepingComputer — 77 Open VSX extensions found harvesting developer info (Lawrence Abrams, 2026-08-04) | Collector subdomains; per-package tracking ID; 7-day retry; TXT fallback; explicit negative scope (no source code, credentials, tokens, SSH material or browser data) | 2026-08-08 |
| 2 | Cyber Security News — 77 Evil Twin Open VSX Extensions Exfiltrate Private Git Repository and CI Data (2026-08-05) | Second independent IOC table: domain, hosts, URI paths, _beacon TXT with base=https:// response prefix, User-Agent | 2026-08-08 |
| 2 | GBHackers — Fake Open VSX Extensions Hijack AMD, Azure, Salesforce and Government Namespaces | Corroborating IOC table; collectors answer "ok"; any HTTP status treated as delivery success | 2026-08-08 |
| Framework | MITRE ATT&CK T1176.002 — Software Extensions: IDE Extensions | Primary technique mapping and mitigation routing | 2026-08-08 |
| Framework | MITRE ATT&CK T1195.002 — Compromise Software Supply Chain | Initial-access mapping; M1051 / M1016 routing for H-5 and H-9 | 2026-08-08 |
| Framework | NIST SP 800-161r1 — Cybersecurity Supply Chain Risk Management | Authority behind H-9 (treat the developer toolchain as a first-class supply chain) | 2026-08-08 |
| Vendor | Visual Studio Code — enterprise deployment and the extensions.allowed policy | Configuration behind H-4 and playbook P2 | 2026-08-08 |
Snapshot provenance. The verbatim fetched text of every cited news and research source is saved alongside this pack in OpenVSX-EvilTwin-Extensions-Hunt-sources/ (01-manifold-security.txt, 02-thehackernews.txt, 03-bleepingcomputer.txt, 04-cybersecuritynews.txt, 05-gbhackers.txt). Every atomic indicator shipped in §10 was mechanically traced back to that text. No source contained agent-directed text or instructions.