






















The StepSecurity threat intelligence team has discovered an ongoing campaign in which an attacker is compromising hundreds of GitHub accounts and injecting identical malware into hundreds of Python repositories. The earliest injections date to March 8, 2026, and the campaign is still active with new repos continuing to be compromised. The attack targets Python projects — including Django apps, ML research code, Streamlit dashboards, and PyPI packages — by appending obfuscated code to files like setup.py, main.py, and app.py. Anyone who runs pip install from a compromised repo or clones and executes the code will trigger the malware.

The attacker gains access to developer accounts, takes the latest legitimate commit on each repo's default branch, rebases it with malicious code appended, and force-pushes — making it appear as if nothing changed. The commit message, author, and author date are all preserved from the original. We have filed security issues on the most notable affected repositories to notify maintainers.
This is an ongoing campaign. We are actively tracking this attack and will continue to update this blog post as new information becomes available. Some of these repositories may still contain the malicious code. If you use any Python packages installed directly from GitHub, check that the code on the default branch matches the last legitimate commit from the original author.
The full list of affected repositories can be found by searching GitHub for the malware's marker variable:
GitHub Code Search: lzcdrtfxyqiplpd


The account takeover mechanism has been identified: the compromised developers were infected with the GlassWorm malware through malicious VS Code and Cursor extensions. GlassWorm's stage 3 payload includes a dedicated credential theft module that harvests GitHub tokens from multiple sources: git credential fill, VS Code extension storage, ~/.git-credentials, and the GITHUB_TOKEN environment variable. Once the attacker has these credentials, they use them to force-push malware into all of the victim's repositories.
A Reddit user reported discovering that "null" had committed to most of their repos — and traced the infection back to a compromised Cursor extension . An independent analysis of the GlassWorm payload confirmed it steals GitHub and npm credentials, validates them against the GitHub API, and exfiltrates them to attacker-controlled servers.
The evidence for account-level compromise is clear: when an account with multiple repositories is taken, every repo under that account gets injected. For example, user BierOne has had 6 repos compromised, the organization wecode-bootcamp-korea has had 6 repos hit, and HydroRoll-Team has had 6.
The injection method is sophisticated. Rather than opening a pull request or creating a new commit (both of which would be visible in the repo's activity feed), the attacker:
setup.py, main.py, app.py, etc.)The commit message and author date are preserved from the original commit — only the committer date reveals the tampering. The committer email is also set to the string "null" across many of the malicious commits, which appears to be a fingerprint of the attacker's tooling.

Here are the date discrepancies we found across the most notable repositories:
amirasaran/request_validator — author date: 2017-04-24, committer date: 2026-03-10 (9 year gap)BierOne/relation-vqa — author date: 2019-04-11, committer date: 2026-03-13 (7 year gap)BierOne/bottom-up-attention-vqa — author date: 2021-06-01, committer date: 2026-03-13 (5 year gap)biodatlab/siriraj-assist — author date: 2024-03-19, committer date: 2026-03-13 (2 year gap)amirasaran/django-restful-admin — author date: 2024-11-15, committer date: 2026-03-10 (16 month gap)uknfire/tsmpy — author date: 2025-06-04, committer date: 2026-03-08 (9 month gap)KeithSloan/ImportNURBS — author date: 2025-08-28, committer date: 2026-03-10 (6 month gap)BierOne/ood_coverage — author date: 2024-10-25, committer date: 2026-03-12 (5 month gap)KeithSloan/GDML — author date: 2026-02-06, committer date: 2026-03-11 (33 day gap)The GitHub Events API captures push events with before and after commit SHAs. For amirasaran/django-restful-admin, we can see the exact moment the default branch was replaced:
// March 10, 21:58 UTC — master force-pushed with malicious code
{
"type": "PushEvent",
"actor": "amirasaran", // compromised account
"created_at": "2026-03-10T21:58:02Z",
"ref": "refs/heads/master",
"before": "260ca635...", // clean commit (legitimate PR #16 merge)
"after": "17849e1b..." // malicious rebased commit
}

The before SHA (260ca635) is the legitimate merge commit from PR #16. The after SHA (17849e1b) is the attacker's rebased commit with malware appended to setup.py. Because the attacker uses the compromised account's own credentials, the push appears to come from the repo owner.

The injected code is appended to the end of whatever Python file the attacker targets. It's obfuscated with three layers: base64 decoding, zlib decompression, and XOR decryption (key: 134). The variable names are randomized 15-character strings, but the base64 payload blob is identical across all compromised repos, stored in a variable named lzcdrtfxyqiplpd.
# Obfuscation wrapper (appended to end of legitimate Python file)
# -*- coding: utf-8 -*-
aqgqzxkfjzbdnhz = __import__('base64')
wogyjaaijwqbpxe = __import__('zlib')
idzextbcjbgkdih = 134
qyrrhmmwrhaknyf = lambda d, o: bytes([b ^ idzextbcjbgkdih for b in d])
lzcdrtfxyqiplpd = '<4,800-character base64 blob>'
runzmcxgusiurqv = wogyjaaijwqbpxe.decompress(
aqgqzxkfjzbdnhz.b64decode(lzcdrtfxyqiplpd))
ycqljtcxxkyiplo = qyrrhmmwrhaknyf(runzmcxgusiurqv, idzextbcjbgkdih)
exec(compile(ycqljtcxxkyiplo, '<>', 'exec'))
After deobfuscation, the malware executes the following sequence:
The malware first checks if the system is Russian — examining locale settings, timezone, and UTC offset. If the system is Russian, execution is skipped entirely. This is a well-known pattern in Eastern European cybercrime operations to avoid targeting domestic systems.
# Comments in the deobfuscated code are in Russian:
# "Эмуляция объекта Buffer из Node.js" (Emulation of Node.js Buffer object)
# "Получение подписей для адреса Solana" (Getting signatures for Solana address)
# "Проверка, находится ли система в России" (Checking if system is in Russia)
Instead of connecting to a traditional C2 server that could be taken down, the malware reads its instructions from the Solana blockchain. It queries a specific Solana address for transaction memos containing JSON data with a payload URL:
Solana C2 address: BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC
The malware tries 9 different Solana RPC endpoints as fallbacks, making it highly resilient to any single endpoint being blocked:
api.mainnet-beta.solana.comsolana-mainnet.gateway.tatum.iogo.getblock.ussolana-rpc.publicnode.comapi.blockeden.xyzsolana.drpc.orgsolana.leorpc.comsolana.api.onfinality.iosolana.api.pocket.networkUsing the blockchain as a C2 channel means the attacker can update the payload URL at any time by posting a new transaction — and no one can delete or censor the instructions once they're on-chain.

Once the malware retrieves the payload URL from the Solana memo, it:
nodejs.org to the user's home directory (cross-platform: Windows/macOS/Linux, x64/ARM)i.js) that decrypts and executes the payload via the downloaded Node.js~/init.json) with a 2-day recheck timer to avoid repeated executionThe final JS payload is encrypted with AES, making static analysis of the second stage impossible without the server-side key. Based on the infrastructure pattern (Solana C2 + Node.js execution + AES encryption + CIS exclusion), this is consistent with known crypto wallet stealer / infostealer campaigns.
To confirm the malware behavior, we ran the compromised setup.py from amirasaran/django-restful-admin in a controlled GitHub Actions environment with StepSecurity Harden-Runner monitoring all network activity. The results confirm the full attack chain described above.
Within seconds of executing python3 setup.py, Harden-Runner captured the following network activity from the python3.12 process:
api.mainnet-beta.solana.com (208.91.111.195:443) — the malware querying the blockchain for its C2 instructions217.69.0.159:80 — the URL decoded from the Solana transaction memonodejs.org (172.66.128.70:443) — downloading Node.js v22.9.0 to execute the encrypted JavaScript payload/home/runner/node-v22.9.0-linux-x64/bin/node deployed — Harden-Runner automatically detected the new binary and attached TLS monitoring

None of these connections belong in a Python project's CI/CD pipeline. A setup.py has no legitimate reason to contact Solana RPC endpoints, download Node.js, or connect to unknown IPs. Harden-Runner's network egress monitoring flags exactly this kind of anomalous activity. Add Harden-Runner to your workflows to detect compromised dependencies before they can exfiltrate data.
The full workflow run with Harden-Runner insights is available at: StepSecurity Insights Dashboard.
StepSecurity monitors outbound network connections from your GitHub Actions runners, detects anomalous activity like the ForceMemo campaign, and secures your software supply chain.
Because the attacker uses the Solana blockchain as a C2 channel, the full history of C2 instructions is permanently recorded on-chain and available for analysis. We queried the C2 address BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC and decoded all transaction memos.
The attacker owns the private key for this address and uses Solana's Memo program to post JSON instructions. The malware on victim machines queries this address via Solana RPC, reads the latest memo, decodes the base64 link field, and connects to the resulting URL. The blockchain only records the attacker's side — victim connections happen off-chain to the payload servers.
The earliest transaction on the C2 address dates to November 27, 2025 — over three months before the first GitHub repo injections on March 8, 2026. The address has 50 transactions total, with the attacker regularly updating the payload URL, sometimes multiple times per day. This suggests the attacker was targeting other infection vectors before pivoting to GitHub repos.
Each memo contains a base64-encoded HTTP URL pointing to the current payload server. Decoding all memos reveals the attacker has rotated through 6 different server IPs over the campaign's lifetime:
45.32.151.157 — active December 2025 (earliest)45.32.150.97 — active February 2026217.69.11.57 — active February 2026217.69.11.99 — active February–March 2026217.69.0.159 — active March 13, 2026 (current — confirmed by Harden-Runner)45.76.44.240 — active March 13, 2026The 45.x IPs are in the Vultr hosting range. The 217.69.x IPs are in a Russian hosting range — consistent with the CIS exclusion behavior and Russian-language code comments in the malware.
One transaction from February 25, 2026 contains a direct C2 configuration instead of a payload link — likely posted during a testing or reconfiguration phase:
{
"c2server": "http://217.69.11.99:5000", // Main C2 server
"checkIp": "http://217.69.11.99", // Victim IP fingerprinting
"dht_data": "217.69.11.99:10000" // DHT peer-to-peer fallback
}
This reveals three components of the attacker's infrastructure: a C2 server on port 5000, a victim fingerprinting endpoint (likely checking the victim's IP and geolocation before serving a payload), and a DHT (Distributed Hash Table) node on port 10000 — a peer-to-peer fallback that makes the C2 infrastructure even more resilient to takedowns.
The C2 address was funded on November 27, 2025 by wallet G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t, which currently holds ~495 SOL. The C2 address itself holds only 0.006 SOL — just enough for transaction fees to post memo updates.
The Solana C2 address used by ForceMemo - BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC - is the same wallet address used by the GlassWorm malware campaign, a self-propagating worm that has been targeting VS Code and OpenVSX extensions since October 2025. This was first documented by Koi Security in their analysis of GlassWorm's Wave 4 (December 2025), which pivoted to macOS with trojanized crypto wallets.
The overlap is not just the wallet. Aikido Security reported that the GlassWorm actor compromised 151+ GitHub repositories between March 3–9, 2026 using invisible Unicode characters to hide payloads - with the decoded payload fetching its C2 instructions from the same Solana wallet. This means two parallel waves were hitting GitHub repos in early March 2026 from the same actor:
The two campaigns use different delivery methods and different code obfuscation (Unicode steganography vs. base64/zlib/XOR), but share the same Solana C2 infrastructure. This strongly suggests ForceMemo is a new delivery vector operated by the GlassWorm threat actor, expanding from VS Code extensions to mass GitHub account takeover.
While we cannot see the final JavaScript payload (it is AES-encrypted with a key delivered via HTTP headers), the infrastructure pattern is consistent with known crypto wallet stealer / infostealer campaigns:
checkIp endpoint) — allows the attacker to filter or customize payloads per victimThe most likely targets are browser crypto wallet extensions (seed phrases, private keys), stored credentials and session cookies, and SSH keys.
So far, we have identified hundreds of Python repositories across hundreds of GitHub accounts injected with identical malware, and the number continues to grow. The targeted repos include Django web applications, machine learning research code, Streamlit dashboards, Flask APIs, and Python packages installable via pip install from GitHub URLs. Several of the compromised repos have setup.py files — meaning a pip install directly from the repo executes the malware during installation.
The full list of affected repositories can be found by searching GitHub for the malware's marker variable:
GitHub Code Search: lzcdrtfxyqiplpd

The strongest evidence for account-level compromise is the pattern of multiple repositories being hit per account. These numbers are growing as the campaign continues:
wecode-bootcamp-korea (Organization) — 6 repos compromisedHydroRoll-Team (Organization) — 6 repos compromisedBierOne (User) — 6+ repos compromisedgnlxpy (User) — 6 repos compromisedFo2sh88 (User) — 6 repos compromisedwatercrawl (Organization) — 4 repos compromisedtavasolireza (User) — 4 repos compromisedBishalBudhathoki (User) — 4 repos compromisediperformance (User) — 4 repos compromisedamirasaran (User) — 3 repos compromisedKeithSloan (User) — 2 repos compromisedThe attacker's tooling selects the most prominent Python entry point in each repo:
main.py — most common target (~70 repos)setup.py — triggers on pip install . (~25 repos)app.py — Flask/Streamlit apps (~25 repos)manage.py — Django projects (~20 repos)app/__init__.py — package init files (~8 repos)streamlit_app.py, run.py, config.py, cli.py, noxfile.pyG2YxRa... transfers SOL and first payload URLs point to 45.32.151.157. The attacker is likely targeting other infection vectors before GitHub repos.45.32.151.157, 45.32.150.97, 217.69.11.57, 217.69.11.99), posting ~40 memo transactions with updated payload URLs.metalogico/issued, uknfire/tsmpy, gnlxpy/* repos, wecode-bootcamp-korea/* reposamirasaran/* repos (including 70-star django-restful-admin), KeithSloan/ImportNURBS, watercrawl/* reposKeithSloan/GDMLBierOne/ood_coverage (34-star ICLR paper)BierOne/bottom-up-attention-vqa, BierOne/relation-vqa, biodatlab/siriraj-assist, HydroRoll-Team/HydroRollKeithSloan/GDML restored at 14:05 UTC). StepSecurity publishes initial findings and files security issues on affected repos.BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SCG2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3tlzcdrtfxyqiplpd134"null" (string)~/init.jsoni.js (in script directory)45.32.151.157 (active Dec 2025)
45.32.150.97 (active Feb 2026)
217.69.11.57 (active Feb 2026)
217.69.11.99 (active Feb–Mar 2026, C2 server on :5000, DHT on :10000)
217.69.0.159 (active Mar 2026 — current)
45.76.44.240 (active Mar 2026)
api.mainnet-beta.solana.com
solana-mainnet.gateway.tatum.io
go.getblock.us
solana-rpc.publicnode.com
api.blockeden.xyz
solana.drpc.org
solana.leorpc.com
solana.api.onfinality.io
solana.api.pocket.network
We are tracking this campaign as ForceMemo, derived from its two most distinctive technical artifacts:
If you install Python packages directly from GitHub (e.g., pip install git+https://github.com/...) or clone and run Python repos:
grep -r "lzcdrtfxyqiplpd" .~/init.json on your system — this is the malware's persistence filels ~/node-v22*i.js in any recently-cloned project directoriesWe filed security issues on the most notable affected repositories to notify maintainers:
StepSecurity threat intelligence was the first to discover and publicly report on this campaign. The team is actively monitoring the situation and will continue to update this post. StepSecurity continuously monitors the open source and CI/CD ecosystem for emerging threats — including supply chain attacks, compromised GitHub Actions, malicious packages, and account takeover campaigns like this one.
StepSecurity customers receive threat intelligence alerts directly in their dashboard, with actionable guidance on whether they are affected and how to remediate.
Harden-Runner monitors network traffic, file system changes, and process activity on GitHub Actions runners. It is designed to catch exactly the kind of anomalous behavior that supply chain attacks like ForceMemo produce.
When we ran the compromised setup.py with Harden-Runner in audit mode, it captured every outbound connection the malware made:
api.mainnet-beta.solana.com:443 — Solana blockchain C2 query217.69.0.159:80 — Payload URL fetched from the Solana memonodejs.org:443 — Node.js v22.9.0 downloadAll three connections were flagged as "Not in baseline" — meaning they had never been seen in any previous run of the workflow. A Python setup.py has no legitimate reason to contact Solana RPC endpoints, download Node.js, or connect to unknown IPs.
Once a baseline is established, Harden-Runner can block any outbound connection that is not in the allowed list. This would have prevented the malware from reaching the Solana C2, downloading Node.js, or exfiltrating data.
StepSecurity monitors outbound network connections from your GitHub Actions runners, detects anomalous activity like the ForceMemo campaign, and secures your software supply chain.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。