TL;DR:
- What happened: On June 8, 2026, Socket published a follow-up to its June 6 PyPI weekend-report. Researchers found 23 more malicious Python packages tied to the Mini Shai-Hulud, Miasma, and Hades worms, bringing the cross-ecosystem total to 471 affected artifacts across 106 npm packages and 37 PyPI packages.[1] Several of the newer packages contain a fake "policy" JavaScript comment block at the top of their payloads. The comment is full of words like "nuclear weapons" and "biological weapons" and looks, to a language model reading the file from the top, like a system prompt the scanner is supposed to refuse.[1][2]
- The trick: The strings are not malware. They are a steganographic decoy. The goal is to make an AI security scanner trip its own safety refusal and stop analyzing the file before it reaches the actual malicious code further down. SentinelOne senior researcher John Scott-Railton, a former Citizen Lab senior researcher, flagged the technique on June 10: "malware developers added nuclear & biological weapons text to to their spyware. Goal? To trigger LLM safety refusals... so that their spyware wouldn't be analyzed by an AI security scanner."[2]
- Why it works: When a security pipeline uses a frontier model to triage a suspicious file and the model sees policy-triggering content at the top, the model can refuse, abort, or refuse to summarize. The malware slips into the low-priority queue or gets archived as "AI-flagged, not for review." Scott-Railton: "Cleanest practical example I can think of for why over-indexing on first order safety alignment is risky. When closed (and open) models ship with aggressive refusals, they will be sprinkled with second-order blindspots that attackers will discover... and exploit."[2]
- What is actually compromised: A PyPI bioinformatics cluster (embiggen, ensmallen, gpsea, phenopacket-store-toolkit, ppkt2synergy, pyphetools) plus MCP-themed packages (langchain-core-mcp, openai-mcp, instructor-mcp, ray-mcp-server, tiktoken-mcp) and typosquats (rsquests, tlask, rlask). Real packages used in graph learning, patient phenotyping, phenopacket tooling, and AI agent development. Many of them hit real scientific workflows.[1]
- The hard part: The newer bioinformatics packages do not use a .pth startup hook. They embed their trigger in a compiled .abi3.so native extension that runs at import time. A package reviewer reading only the Python source sees nothing. The native extension loads, dlopen-style, and the JavaScript stealer runs as a side effect. This is the next generation of the worm, not a fix for a detection gap.[1]
The Trick: Put "Nuclear Weapons" in the Comment Block
The "nuclear weapons" / "biological weapons" content is not in the malware's behavior. It is in a comment at the top of a JavaScript file. The malware still does what it does: it crawls developer workstations and CI runners for GitHub, npm, PyPI, RubyGems, JFrog, AWS, GCP, Kubernetes service-account material, SSH keys, Docker config, shell history, .env files, and AI developer tool configuration. The behavior is unchanged from the rest of the Hades pattern. What is new is the wrapping.[1]
According to Socket's June 8 analysis of the newer PyPI wave, the malicious JavaScript payload _index.js "begins with a large JavaScript block comment containing fake system instructions and policy-triggering content. Because it is inside a comment, it does not affect JavaScript execution. The runtime skips it. The real malware begins after the comment with a try{eval(...)} wrapper around a large character-code array and a ROT-style substitution function."[1] The comment is filler. The character-code array and the substitution function are the stealer.
The filler looks, to a language model reading the file in order, like instructions. The model is asked, in effect, to "triage this file." It sees something that resembles a system prompt telling it to refuse on policy grounds. If the model has aggressive refusals enabled (the default for closed-weights consumer endpoints, and increasingly the default for open-weights deployments shipping with safety-tuning recipes), the model refuses. The file gets dropped from the queue. The malware stays on disk.[2]
Socket named this category in the report: "LLM-Scanner Anti-Analysis." The analysis is explicit: "This header appears designed for AI-mediated analysis, not for Node, Bun, or Python. It attempts to derail scanners or analyst copilots that feed the beginning of a file to a language model without clearly isolating the content as untrusted data. In weak pipelines, this can cause refusal behavior, prompt confusion, context pollution, or premature classification before the scanner reaches the actual malware."[1]
Socket also notes the limits. YARA rules, entropy checks, AST parsing, string extraction, deobfuscation, and behavioral rules all still work. The bypass is for "naive LLM-first triage systems," the kind of pipeline that sends the first 4 KB of a file to a model and asks, "is this malware?" The malware authors are not trying to beat a YARA author. They are trying to beat the prompt.[1]
Scott-Railton: "Second-Order Blind Spots"
John Scott-Railton, a senior researcher at SentinelOne and one of the most cited Citizen Lab alumni in the commercial spyware space, surfaced the technique on X on June 10. The post, addressed to the security community, is the cleanest public write-up of the underlying failure mode.[2]
"NEW: malware developers added nuclear & biological weapons text to to their spyware. Goal? To trigger LLM safety refusals... so that their spyware wouldn't be analyzed by an AI security scanner. Cleanest practical example I can think of for why over-indexing on first order safety alignment is risky. When closed (and open) models ship with aggressive refusals, they will be sprinkled with second-order blindspots that attackers will discover... and exploit. We are only in the earliest days of attackers leveraging these features, and it wouldn't surprise me if users systems that need to handle complex cybersecurity issues demand that models be less safety-blunted."[2]
The point is not that safety alignment is wrong. The point is that the same safety tuning that stops a model from explaining how to build a dirty bomb is also the safety tuning that stops the model from triaging a file that contains the words "dirty bomb." The defender of the file and the attacker of the file are now in the same refusal bucket. The attacker knows it.[2]
Scott-Railton's earlier work, including the multi-year Citizen Lab investigations into NSO Group's Pegasus, Intellexa's Predator, and the broader commercial spyware market, has repeatedly shown that attackers weaponize the defender's stack, not just the defender's target. Prompt-injection-style refusal-evasion is the same pattern in a new medium. The defender's safety refusal is the attacker's perimeter.[2]
The Broader Campaign: Mini Shai-Hulud, Miasma, and Hades
The "nuclear weapons" trick is the LLM-era skin on a much larger worm. The weekend PyPI wave on June 6 seeded a self-replicating credential stealer into the developer ecosystem. Socket's tracking page now lists 471 affected artifacts across npm and PyPI: 411 npm artifacts across 106 packages, and 60 PyPI artifacts across 37 packages.[1] The npm side is the one our earlier coverage tracked under the GitHub TeamPCP / Nx Console supply chain attack: 3,800 internal GitHub repositories stolen, Mini Shai-Hulud worming through TanStack, OpenAI, Mistral AI, Grafana Labs, and others.[3] The PyPI side is what the June 8 report covers.[1]
The PyPI side has at least three delivery branches, and the newest one uses native code.[1]
The first branch is the .pth startup-hook pattern. A malicious wheel contains a *-setup.pth file and a bundled _index.js payload. The .pth hook runs during Python startup, downloads Bun if needed, and runs the JavaScript payload. The bioinformatic and MCP clusters in the older wave used this pattern.[1]
The second branch is the native-extension import trigger. A malicious wheel contains a Python source that looks normal, plus a compiled .abi3.so extension. The .py files are not the trigger. When Python imports the package and the extension loads through dlopen(), the native code launches the JavaScript payload. The socket.dev researchers flag this as "harder to catch with source-only Python review because the malicious trigger is not visible in the package's .py files."[1]
The third branch is the langchain-core-mcp loader variant. The wheel does not include _index.js. Its .pth hook searches sys.path for the payload, looking in every entry, then one directory below each entry. A scanner that expects the loader and payload to live together in the same wheel misses this class of package.[1]
All three branches land on the same Hades payload family: a heavily obfuscated JavaScript stealer staged through Bun, with a fake prompt-injection header at the top of _index.js to derail AI-assisted analysis, followed by credential theft and (where the target has publishing rights) the self-replicating follow-up publish step.[1][3]
The Bioinformatics Subcluster: Real Scientific Packages, Fake Extensions
The bioinformatics cluster is the part of the story that hits working scientists. The package names in the cluster are not typosquats. Socket names six: embiggen, ensmallen, gpsea, phenopacket-store-toolkit, ppkt2synergy, and pyphetools.[1] These are real packages used in graph learning, patient phenotyping, phenopacket tooling, and related scientific workflows. They have legitimate maintainers and real install bases.[1]
The newer subcluster hides its trigger inside compiled native extensions, not the .py source. The package's visible Python source can appear legitimate, while the compiled .abi3.so file executes the JavaScript payload at import time. Socket flags the specific files observed: ensmallen_haswell.abi3.so and ensmallen_core2.abi3.so.[1]
Many package review pipelines focus on Python source, setup scripts, metadata, and dependency declarations. Native extensions often receive less scrutiny, especially if the package normally ships compiled performance-sensitive code. In scientific computing, genomics, and machine learning packages, native extensions are common and often expected. The threat actors use that expectation as cover.[1]
For a bioinformatics lab running a CI runner with PyPI install permissions, a single pip install of a poisoned pyphetools or embiggen version drops a stealer onto the build host. The stealer reads the GitHub token, the cloud credentials, and the .env files. If the lab has publishing rights to any of these packages, the stealer publishes the next iteration of the worm under a legitimate maintainer account. The chain continues.[1][3]
The MCP Cluster: Targeting the AI Developer
The other half of the newer wave targets AI developer workflows. Socket names the cluster: instructor-mcp@1.15.2/3, openai-mcp@2.41.1/2, ray-mcp-server@0.2.1, tiktoken-mcp@0.13.1/2, and the loader/payload-split langchain-core-mcp@1.4.2/3.[1] MCP (Model Context Protocol) is the standard Anthropic, OpenAI, and most of the open-source agent ecosystem have settled on for connecting language models to tools, files, and APIs. The cluster targets the developers building the integrations.[1]
The langchain-core-mcp variant is the most interesting piece of code. Its langchain_core-setup.pth file searches every entry in sys.path, first for a direct _index.js, then one directory below each path entry. Socket notes this is a fix to a reliability problem in the earlier loader: "In standard CPython, that can be unreliable because .pth execution occurs through Python's site module and __file__ may not point to the .pth file itself. The langchain-core-mcp variant avoids that by scanning sys.path."[1]
Socket flags three plausible interpretations: the threat actors fixed a reliability issue; the wheel is a failed or incomplete publish where _index.js was accidentally omitted; or the threat actor is testing paired-package staging, where one package supplies the loader and another package, project file, or co-installed artifact supplies the payload. The first interpretation is the obvious one. The third is the worrying one. Paired-package staging is a category of attack that no current scanner handles well.[1]
The hash for the affected langchain_core_mcp-1.4.2 wheel is 6d332f814f15f19758d65026bbfd0a8c49671b319ec77b8fa1b27fc48afff7d9. The hash for the malicious langchain_core-setup.pth is 6506d31707a39949f89534bf9705bcf889f1ecae3dbc6f4ff88d67a8be3d01b2.[1]
The Typosquats: rsquests, tlask, rlask
Not every package in the newer wave is a real research package with a malicious version. Some of them are bait. Socket names three: rsquests@2.34.3, tlask@3.1.4, and rlask@3.1.7.[1] The targets are developers installing requests, flask, and flask (again). These are typosquats, designed to be installed by a developer who fat-fingered a name or pasted a snippet that had a typo.[1]
Other bait packages in the cluster include mem8@6.0.1, mflux-streamlit@0.0.3/4, orchestr8-platform@3.3.2, and dreamgen@1.8.1.[1] The pattern: real ecosystem names, real install commands, malicious versions. A security team that bans a package by name needs to ban the typosquats too, which means enumerating the typosquats, which means reading Socket's indicator list.[1]
What Defenders Should Do
Socket's defensive guidance is execution-path focused, not keyword focused. The nuclear-weapons strings are a side show. The actual attack is a stealer that runs as a side effect of package import. The defender's job is to catch the execution path, not the comment block.[1]
The first step is the obvious one: check for the 60 affected PyPI package-version artifacts in Socket's IOC list and the 411 affected npm artifacts in the same campaign tracker at socket.dev/supply-chain-attacks/miasma-mini-shai-hulud-supply-chain-attack.[1] Uninstall the affected versions. Rotate any token that may have been exposed. Preserve forensic artifacts before uninstalling if the package is on a build runner that may have been live during the window.[1]
The second step is environment-level. Review Python environments for executable .pth files, unexpected _index.js files, Bun download logic (Bun/1.3.14 was the observed User-Agent for langchain-core-mcp), and newly introduced .abi3.so extensions. The compiled native extension path means that scanning only .py source is no longer sufficient. A reviewer needs a tool that lists the .so files in a wheel and inspects them, not just the Python files.[1]
The third step is CI/CD. Inspect runners for unusual workflow changes, Docker socket abuse (the malware targets /var/run/docker.sock when accessible), poisoned /etc/hosts entries, unexpected privileged containers, and any new access to package publishing credentials. The malware specifically targets the StepSecurity defensive toolchain (the indicators list names harden-runner, step-security, agent.stepsecurity.io, api.stepsecurity.io, app.stepsecurity.io) by adding them to a block list, so a defender running that tooling will see it stop working and need to know why.[1]
The fourth step is on the LLM-scanner side, and this is the part Scott-Railton is pushing on. The fix is not to turn off safety alignment. The fix is to design the malware analysis pipeline so the file being analyzed is unambiguously marked as untrusted data, fed to the model in a context where refusal is suppressed for the analysis task, and the model output is treated as a triage signal that goes to a human reviewer or a YARA-style rules engine, not as a final verdict.[2] Socket's own writeup makes the same point: "intention matters in how you design a malware analysis pipeline to avoid prompt manipulation."[2]
The Bigger Problem: Safety Refusal as an Attack Surface
The "nuclear weapons" trick is a symptom, not the disease. The disease is the broader pattern: a defender builds a safety refusal into a model, an attacker puts the trigger phrase at the top of a malicious file, and the defender's model refuses the file instead of analyzing it. The defender loses information to the same safety tuning that is supposed to keep the defender safe.[2]
Scott-Railton's framing is the right one. The trick is "the cleanest practical example" of why safety tuning cannot be one-size-fits-all. A model deployed for general consumer use needs the "I will not help you build a weapon" refusal. A model deployed in a security operations center, asked to triage malware, needs the opposite: a refusal floor of zero, with the file marked as untrusted input and the model explicitly told to analyze it.[2]
The default shipping posture for closed-weights consumer models is too aggressive for the security use case. The default shipping posture for open-weights models is catching up. Either way, the deployment is the defender's problem. A model that refuses a malware sample is the model the attacker wanted.[2]
This is the first widely-circulated example of the steganography category in the wild. It will not be the last. Socket's researchers call it "an LLM-Scanner Anti-Analysis" technique.[1] Scott-Railton calls it the start of a pattern.[2] The defenders who build their pipeline around YARA, entropy, AST, and behavioral rules will catch the malware. The defenders who replaced those rules with a model and a refusal budget will not. The "nuclear weapons" string is the canary, not the payload.
Sources
- Socket: "Mini Shai-Hulud, Miasma, and Hades Worms Target Bioinformatics and MCP Developers via Malicious PyPI Wheels" (Kirill Boychenko, June 8, 2026, primary threat research)
- John Scott-Railton (@jsrailton) on X: "NEW: malware developers added nuclear & biological weapons text to to their spyware." (June 10, 2026, 10:51 AM UTC, primary statement on the LLM-safety-refusal attack surface)
- Socket: "Shai-Hulud Descends to Hades: The Miasma PyPI Wave" (June 6, 2026 weekend report, primary on the original 37 PyPI wheels and the npm supply chain attack cross-link)
- Hacker News: "Malware developers added nuclear and biological weapons text to to their spyware" (377 points, 207 comments, community discussion and additional IOCs)
- Socket: Mini Shai-Hulud / Miasma Supply Chain Attack Tracker (471 affected artifacts across 106 npm packages and 37 PyPI packages, updated continuously)