Skip to navigation

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)

highACTIVE

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

IdentifiersTL-2026-5631
Also known asstrutil-core worm, testmetest worm, Miasma worm
SeverityHIGH
Affected productsRubyGems strutil-core (gem); PyPI pystr-codec
Weaknesses (CWE)CWE-506, CWE-829
AttributionMiasma worm operator (low confidence)
MalwareShai-Hulud, testmetest
MITRE ATT&CK techniquesT1003.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
MotivationFINANCIAL
Target sectorstechnology, software development, cloud services, open-source maintainers
Target regionsGlobal
Detection rules9 (1 shown free)
Sources8 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.

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 desc

Sigma

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: critical

Known 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.

8 more detection rules (Splunk SPL, Microsoft KQL, Sigma) · unlock with TL Creds. Sign up free to unlock the detection rules or query them with the Threadlinqs MCP server (Purple)
  • 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.

23 indicators of compromise · unlock with TL Creds. Compare plans

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

T1102.001 Dead Drop Resolver

Initial Access

T1195.002 Compromise Software Supply Chain

Impact

T1485 Data Destruction

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

T1685 Disable or Modify Tools

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

Further reading

Drafted by the Threadlinqs research pipeline from 8 sources; published ; last updated —.

Threadlinqs Intelligence — Real-Time Threat Detection Platform

// threat_feed
$ sort --newest
Showing all threats

Live intelligence console

Threat level

Latest Threats