Threat reportSupply ChainTL-2026-5631
Malicious RubyGem strutil-core 1.0.1 Steals 51 Credential Types and Backdoors AI Coding Tools (testmetest / Miasma worm, Shai-Hulud descendant)
Malicious RubyGem strutil-core 1.0.1, published by RubyGems account jamnajiea on 2026-11-01, executes a 5.48 MB obfuscated Bun-based worm at install time via extconf.rb. It harvests credentials from 51 stores, exfiltrates to paleasbest.top (fallback: public GitHub/GitLab repos), and plants persistence including a fake MCP server and SessionStart hooks in AI coding tool configs.
Key facts
| Identifiers | TL-2026-5631 |
|---|---|
| Also known as | strutil-core worm, testmetest worm, Miasma worm |
| Severity | HIGH |
| Affected products | RubyGems strutil-core (gem); PyPI pystr-codec |
| Weaknesses (CWE) | CWE-506, CWE-829 |
| Attribution | Miasma worm operator (low confidence) |
| Malware | Shai-Hulud, testmetest |
| MITRE ATT&CK techniques | T1003.007 Proc Filesystem, T1027 Obfuscated Files or Information, T1059.004 Unix Shell, T1059.006 Python, T1059.007 JavaScript, T1102.001 Dead Drop Resolver, T1140 Deobfuscate/Decode Files or Information, T1195.002 Compromise Software Supply Chain, T1485 Data Destruction, T1528 Steal Application Access Token, T1543.001 Launch Agent, T1543.002 Systemd Service and 6 more in the attack flow |
| Motivation | FINANCIAL |
| Target sectors | technology, software development, cloud services, open-source maintainers |
| Target regions | Global |
| Detection rules | 9 (1 shown free) |
| Sources | 8 cited sources |
Detection coverage
As of 2026-11-10, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-5631 across Splunk SPL, Microsoft KQL and Sigma, covering 23 indicator(s) of compromise. The whole corpus is readable without an account. Every threat comes with one complete detection in Splunk SPL, Microsoft KQL and Sigma, free for everyone; a free account adds daily TL Creds to unlock the rest. Paid plans add a larger daily allowance, correlation and the MCP server. Threadlinqs MCP server · View plans.
Free detection: pkg-runtime Backdoor and net-health-checker Wiper Service Persistence — Post-Exploitation
Detects creation of the persistence artifacts planted by the strutil-core worm: the pkg-runtime systemd user service or LaunchAgent com.user.pkg-runtime that runs ~/.local/share/runtimes/sync.py, the net-health-checker service and script, and the plaintext GitHub token at ~/.config/net-health-checker/token that arms the rm -rf ~/ wiper. Do NOT revoke the stolen GitHub token before removing these services.
- Languages: Splunk SPL, Microsoft KQL, Sigma
- ATT&CK: T1543.002, T1543.001, T1485, T1552.001, T1059.006
- Data sources: sysmon_for_linux, DeviceFileEvents, DeviceProcessEvents
- Severity: critical
- Confidence: high
Splunk SPL
index=linux_sysmon sourcetype="sysmon:linux" earliest=-24h (EventCode=1 OR EventCode=11)
| eval cmd=coalesce(CommandLine,""), tgt=coalesce(TargetFilename,"")
| where (EventCode=11 AND (like(tgt,"%/.config/systemd/user/pkg-runtime.service") OR like(tgt,"%/.config/systemd/user/net-health-checker.service") OR like(tgt,"%/.local/share/runtimes/sync.py") OR like(tgt,"%/.local/bin/net-health-checker.sh") OR like(tgt,"%/.config/net-health-checker/token") OR like(tgt,"%/Library/LaunchAgents/com.user.pkg-runtime.plist") OR like(tgt,"/var/tmp/.fontconfig_state"))) OR (EventCode=1 AND (like(cmd,"%systemctl%--user%pkg-runtime%") OR like(cmd,"%systemctl%--user%net-health-checker%") OR like(cmd,"%launchctl%com.user.pkg-runtime%")))
| eval artifact=if(EventCode=11, tgt, cmd)
| stats count min(_time) as first_seen max(_time) as last_seen values(artifact) as artifacts by host, EventCode
| convert ctime(first_seen) ctime(last_seen)Microsoft KQL
let persistPaths = dynamic(["/.config/systemd/user/pkg-runtime.service", "/.config/systemd/user/net-health-checker.service", "/.local/share/runtimes/sync.py", "/.local/bin/net-health-checker.sh", "/.config/net-health-checker/token", "LaunchAgents/com.user.pkg-runtime.plist", "/var/tmp/.fontconfig_state"]);
let fileHits = DeviceFileEvents
| where Timestamp > ago(24h)
| where ActionType in ("FileCreated", "FileModified")
| where FolderPath has_any (persistPaths)
| project Timestamp, DeviceName, AccountName=InitiatingProcessAccountName, Source="file", Detail=FolderPath, Actor=InitiatingProcessCommandLine;
let svcHits = DeviceProcessEvents
| where Timestamp > ago(24h)
| where ProcessCommandLine has_any ("pkg-runtime", "net-health-checker") and ProcessCommandLine has_any ("systemctl", "launchctl", "loginctl")
| project Timestamp, DeviceName, AccountName, Source="process", Detail=ProcessCommandLine, Actor=InitiatingProcessCommandLine;
union fileHits, svcHits
| order by Timestamp descSigma
title: strutil-core Worm Persistence - pkg-runtime Backdoor and net-health-checker Wiper Files
id: e1d07b94-5c3f-4a28-9b6e-8f2a1c4d5e44
status: experimental
description: Detects creation of files used by the testmetest / Miasma worm for persistence and impact - the pkg-runtime systemd user unit and LaunchAgent, the sync.py Python backdoor, the net-health-checker wiper monitor and its plaintext GitHub token file, and the sync.py processed-URL log. Remove the services BEFORE revoking tokens.
references:
- https://socket.dev/blog/shai-hulud-descendant-malicious-ruby-gem
author: Threadlinqs Team
date: 2026/11/10
tags:
- attack.persistence
- attack.t1543.002
- attack.t1543.001
- attack.credential-access
- attack.t1552.001
- attack.impact
- attack.t1485
logsource:
category: file_event
product: linux
detection:
selection:
TargetFilename|endswith:
- '/.config/systemd/user/pkg-runtime.service'
- '/.config/systemd/user/net-health-checker.service'
- '/.local/share/runtimes/sync.py'
- '/.local/bin/net-health-checker.sh'
- '/.config/net-health-checker/token'
- '/Library/LaunchAgents/com.user.pkg-runtime.plist'
- '/var/tmp/.fontconfig_state'
condition: selection
falsepositives:
- Unlikely; a legitimate product reusing the names pkg-runtime or net-health-checker would be a coincidence and should be verified
level: criticalKnown false positives:
- An internal tool that coincidentally names a service pkg-runtime or net-health-checker
- Authorised IR or red-team simulation of the worm persistence
Tuning notes: The path set is highly specific, so low tuning is expected. Confirm macOS LaunchAgent telemetry exists in your pipeline (Sysmon-for-Linux does not cover macOS; use EDR file events). IMPORTANT for responders: on a hit, remove net-health-checker and pkg-runtime first, then rotate GitHub tokens.
- strutil-core / testmetest Worm Artifact Hash and Gem Path Match — Exploitation · unlock with TL Creds
- CI Runner Secret Scraping via /proc/<pid>/mem and Harden-Runner Tampering — Post-Exploitation · unlock with TL Creds
- sync.py Dead-Drop Resolver Polling and AutoSyncComplete / msync-3f7a Git Exfiltration Markers — C2/Exfiltration · unlock with TL Creds
- Gem Native-Extension Build (extconf.rb) Spawning Downloader or JavaScript Runtime — Exploitation · unlock with TL Creds
- Fake workspace-tools MCP Server and Payload Execution via AI Coding Tool Hooks — Post-Exploitation · unlock with TL Creds
- DNS Resolution of strutil-core Worm Domains [indicator] and [indicator] — C2/Exfiltration · unlock with TL Creds
- strutil-core 1.0.1 Install-Time Bun Execution Chain (Ruby launching Bun from /tmp/.b_<pid>) — Exploitation · unlock with TL Creds
- Egress to strutil-core Worm Collection Infrastructure [indicator]/v1/api and [indicator] — C2/Exfiltration · unlock with TL Creds
Indicators of compromise
As of 2026-11-10, Threadlinqs Intelligence tracks 23 indicators of compromise for TL-2026-5631.
Sample indicators (3 of 23)
- Domain:
paleasbest[.]top - URL:
hxxps://github[.]com/oven-sh/bun/releases/download/bun-v1.2.18/ - File hash:
3b3f6e1bde0b28b015030a81a6d805b5b5306dc3f021e1102123971cfd83acc0
Defanged, TLP:CLEAR. Full set of 23 indicators: unlock with TL Creds.
Technical analysis
Socket assesses it as a later build of the testmetest worm (Miasma family, Shai-Hulud lineage); no victims were confirmed and the gem was removed after about 5 hours.
strutil-core is a RubyGem disguised as 'lightweight string manipulation helpers: slug, truncate, normalize'; the library actually implements only slugify. Version 1.0.0 (published 2026-11-01 02:19:56 UTC) was a clean seed release. Version 1.0.1 (04:05:04 UTC) added a 5,475,376-byte obfuscated Bun 1.2.18 bundle at ext/strutil-core/index.js and a malicious ext/strutil-core/extconf.rb. RubyGems executes extconf.rb during 'gem install' or 'bundle install', so the payload runs without the gem ever being required. On Linux and macOS the script downloads the Bun runtime from the official oven-sh GitHub releases into /tmp/.b_<pid>, runs the bundle, and removes the staging files. The gem's publisher account (jamnajiea) has no matching GitHub account, and the gemspec author 'Mark Tillman' points to a non-existent repository (github.com/marktillman/strutil-core). Socket's AI scanner flagged index.js 70 seconds after publication; RubyGems removed both versions within hours (about 5 hours of exposure). A tracker showed 0 downloads of 1.0.1 shortly after publication, and Socket found no public dependents and confirmed no victims.
The payload reads credentials from 51 stores (AWS, Azure, Google Cloud, Kubernetes, HashiCorp Vault, npm, PyPI, RubyGems, Docker, SSH keys, JFrog Artifactory, Git hosts, password managers, OS keychains, crypto wallets, and AI coding tools such as ~/.claude/.credentials.json and ~/.codex/auth.json). With AWS keys it calls STS, then reads Secrets Manager and SSM values across 17 regions and ECR tokens across 11 regions. On GitHub Actions runners it locates the Runner.Worker process and reads secrets from /proc/<pid>/mem via sudo python3 (the technique seen in the March 2025 tj-actions compromise), and it attempts to interfere with the StepSecurity Harden-Runner agent by redirecting stepsecurity.io hostnames to loopback, rewriting /etc/resolv.conf, and launching a privileged Alpine container through the Docker socket to edit /etc/sudoers.d; StepSecurity states these attempts do not disable the agent. The payload exits on Russian, Belarusian, Kazakh, Uzbek, Kyrgyz, Tajik, Armenian, Azerbaijani, Ukrainian, Georgian, Turkmen and Moldovan locales. Strings are obfuscated with a rotated 4,069-entry table and PBKDF2-HMAC-SHA256-derived keys; embedded components use gzip plus AES-256-GCM, and exfiltrated data is encrypted with AES-256-GCM under an RSA-OAEP-SHA256-wrapped key.
Exfiltration goes first to https://paleasbest.top/v1/api (after an empty-JSON health probe), then to public GitHub repositories (description 'project-utils', adjective-plural-noun-number names such as smart-workers-48213, commits prefixed AutoSyncComplete), then GitLab. Persistence survives gem uninstall: a 'pkg-runtime' systemd user service (Linux) or LaunchAgent com.user.pkg-runtime (macOS) runs ~/.local/share/runtimes/sync.py, a Python backdoor that polls public GitHub commits hourly for the marker msync-3f7a, extracts a hidden URL from the commit message, and executes the remote script only if the URL carries a valid RSA-PSS signature from the operator. The payload also searches the home directory for settings.json, config.json, mcp.json and .aider.conf.yml and injects a fake MCP server named 'workspace-tools' (runs the payload with node) and SessionStart hooks. If working Git credentials are found it writes .claude/settings.json, .cursor/mcp.json, .gemini settings, CLAUDE.md, AGENTS.md (instructing coding agents to run a setup script before their first command), .vscode/tasks.json (run on folder open) and .github/init.js into repositories, pushes chore/ci-* branches with skip-checks:true commits, adds a fake CodeQL workflow that uploads a scan-output artifact, and a GitLab CI job named cache_sync that requests OIDC tokens. Propagation routines exist to republish infected packages to RubyGems, npm and PyPI, and to spread through JFrog Artifactory, GitHub, GitLab and SSH hosts from known_hosts; Socket confirmed the routines exist but observed no successful spread from this gem.
A destructive component is tied to stolen GitHub tokens: the worm installs a 'net-health-checker' monitor (systemd user service with loginctl enable-linger, or LaunchAgent) that stores the token in plaintext at ~/.config/net-health-checker/token and tests it against api.github.com/user every 45 seconds. On an HTTP 401 or 403 (for example after the victim revokes the token) it runs rm -rf ~/ and rm -rf ~/Documents. Responders must therefore remove net-health-checker and pkg-runtime BEFORE revoking tokens.
Attribution: Socket links this build to the testmetest worm, part of the Miasma family derived from the Shai-Hulud toolkit whose source has been public since May 2026. sync.py, the msync-3f7a marker, net-health-checker, pkg-runtime, Bun 1.2.18, the cache_sync job, the CodeQL decoy, Runner memory scraping, SSH spreading and the locale exit are shared with testmetest; the RubyGems injector matches the June 2026 Miasma payload (33 of 36 non-empty installer lines verbatim). The C2 differs (paleasbest.top versus aiarmor.website) and delivery differs (RubyGems extconf.rb versus npm binding.gyp/postinstall). Because the Shai-Hulud source is public, shared code does not prove a single operator. A related PyPI package, pystr-codec 0.1.0-0.1.2 (published 2026-10-31), uses the same collection endpoint with a .pth startup trigger, though its bundle does not match strutil-core. BeaconBeagle was unreachable (HTTP 502) at research time, so no C2 correlation was recorded for paleasbest.top.
MITRE ATT&CK techniques by tactic:
Credential Access
T1003.007 Proc Filesystem; T1528 Steal Application Access Token; T1552.001 Credentials In Files; T1555 Credentials from Password Stores
Defense Evasion
T1027 Obfuscated Files or Information; T1140 Deobfuscate/Decode Files or Information
Execution
T1059.004 Unix Shell; T1059.006 Python; T1059.007 JavaScript
Command and Control
Initial Access
T1195.002 Compromise Software Supply Chain
Impact
Persistence
T1543.001 Launch Agent; T1543.002 Systemd Service
Privilege Escalation
T1548.003 Sudo and Sudo Caching
Exfiltration
T1567.001 Exfiltration to Code Repository
Discovery
T1614.001 System Language Discovery
defense-impairment
Remediation
Patches
- No CVE or vendor patch; both malicious gem versions were removed by RubyGems (1.0.0 was a clean seed release)
Immediate actions
- Isolate any host or CI runner where strutil-core 1.0.1 or pystr-codec 0.1.0-0.1.2 was installed and treat it and all reachable credentials as compromised
- Stop and remove the net-health-checker and pkg-runtime services (systemd user units / LaunchAgents) BEFORE revoking any GitHub token, to avoid triggering the home-directory wipe
- Block egress to paleasbest.top and alert on connections to https://paleasbest.top/v1/api
- Rotate all cloud, registry, SSH, Git and AI-tool credentials from a clean device, then rebuild the affected host
Workarounds
- Remove unknown workspace-tools entries and SessionStart hooks from AI tool settings
- Pin dependencies via lockfiles and use registry cooldown/age policies for new package versions
Longer-term hardening
- Search Gemfile.lock files, CI caches and container images for strutil-core 1.0.1
- Search GitHub/GitLab orgs for repositories described 'project-utils', chore/ci-* branches, skip-checks:true commits, AutoSyncComplete commit messages and CodeQL workflows uploading a scan-output artifact
- Review unexpected patch releases and new extensions: entries in gemspecs; consider disabling native-extension builds in CI where not required
- Audit AI coding tool configs (settings.json, mcp.json, .aider.conf.yml, .claude, .cursor) and repositories for unrecognized MCP servers, SessionStart hooks, CLAUDE.md/AGENTS.md instructions and .vscode/tasks.json entries
- Hunt for bun launched from /tmp/.b_<pid> by Ruby and for sudo python3 reading /proc/<pid>/mem on CI runners
Affected and fixed versions
- RubyGems — strutil-core (gem)
Vulnerable versions: 1.0.1
Fixed in: Removed from registry; 1.0.0 was a clean seed release also removed - PyPI — pystr-codec
Vulnerable versions: 0.1.0; 0.1.1; 0.1.2
Timeline
- Shai-Hulud worm source becomes public (since May 2026), enabling derivative families such as Miasma; shared code does not prove a single operator.
- Miasma worm compromises 20 LeoPlatform/LeoInsights npm packages published within a 3-second window (SafeDep); four more confirmed on 2026-06-26.
- PyPI packages pystr-codec 0.1.0-0.1.2 appear (22:21-23:32 UTC), using the same locked-ioc-17 collection endpoint and a .pth startup trigger.
- OpenSSF malicious-packages PR 1701 opened at 19:16:47 UTC.
- RubyGems removes both versions (about 08:52-09:13 UTC per Bitbison sandbox); public exposure window about 5 hours, no confirmed victims.
- strutil-core 1.0.1 with the 5.48 MB Bun worm payload published at 04:05:04 UTC; Socket's AI scanner flags index.js 70 seconds later.
- strutil-core 1.0.0 (clean seed release) published by RubyGems account jamnajiea at 02:19:56 UTC.
- Cyber Security News publishes coverage of the Socket analysis of strutil-core and the testmetest/Miasma lineage.
Sources cited
- OSV MAL-2026-18432: pystr-codec osv.dev · Advisory
- OSV MAL-2026-6433: rstreams-shard-util 1.0.1 (Miasma npm) api.osv.dev · Advisory · 2026-06-24
- Socket: Shai-Hulud Descendant Surfaces in Malicious Ruby Gem That Steals Credentials and Poisons AI Tools socket.dev · Research
- Socket package page: strutil-core socket.dev · Research
- SafeDep: Miasma worm hits LeoPlatform 20 npm packages safedep.io · Research · 2026-06-24
- OpenSSF malicious-packages PR 1701 (strutil-core) GitHub · Code repository · 2026-11-01
- Deobfuscated June Miasma payload (defensive research) GitHub · Code repository
- Malicious Ruby Gem Steals 51 Types of Credentials and Plants Backdoors in AI Coding Tools cybersecuritynews.com · News · 2026-11-10
Further reading
Drafted by the Threadlinqs research pipeline from 8 sources; published ; last updated —.