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

- **Published:** 2026-11-10T09:24:08Z
- **Last reviewed:** 2026-11-10T09:24:08Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-5631
- **Card image:** https://intel.threadlinqs.com/og/threat/TL-2026-5631.png
- **ID:** TL-2026-5631
- **Severity:** HIGH
- **Category:** SUPPLY_CHAIN
- **Status:** ACTIVE
- **Actor:** Miasma worm operator
- **Detections:** 9 (1 shown free below) · **IOCs:** 23 (3 shown free below)

## Description

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

- T1195.002 Compromise Software Supply Chain
- T1059.007 JavaScript
- T1059.004 Unix Shell
- T1059.006 Python
- T1543.002 Systemd Service
- T1543.001 Launch Agent
- T1548.003 Sudo and Sudo Caching
- T1027 Obfuscated Files or Information
- T1140 Deobfuscate/Decode Files or Information
- T1685 Disable or Modify Tools
- T1552.001 Credentials In Files
- T1555 Credentials from Password Stores
- T1528 Steal Application Access Token
- T1003.007 Proc Filesystem
- T1614.001 System Language Discovery
- T1102.001 Dead Drop Resolver
- T1567.001 Exfiltration to Code Repository
- T1485 Data Destruction

## Free detection (Splunk SPL, Microsoft KQL, Sigma)

**pkg-runtime Backdoor and net-health-checker Wiper Service Persistence — Post-Exploitation** (`TL-2026-5631-SPL-002`)

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

```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

```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

```yaml
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

- strutil-core / testmetest Worm Artifact Hash and Gem Path Match — Exploitation (locked: unlock with TL Creds)
- CI Runner Secret Scraping via /proc/<pid>/mem and Harden-Runner Tampering — Post-Exploitation (locked: unlock with TL Creds)
- sync.py Dead-Drop Resolver Polling and AutoSyncComplete / msync-3f7a Git Exfiltration Markers — C2/Exfiltration (locked: unlock with TL Creds)
- Gem Native-Extension Build (extconf.rb) Spawning Downloader or JavaScript Runtime — Exploitation (locked: unlock with TL Creds)
- Fake workspace-tools MCP Server and Payload Execution via AI Coding Tool Hooks — Post-Exploitation (locked: unlock with TL Creds)
- DNS Resolution of strutil-core Worm Domains [indicator] and [indicator] — C2/Exfiltration (locked: unlock with TL Creds)
- strutil-core 1.0.1 Install-Time Bun Execution Chain (Ruby launching Bun from /tmp/.b_<pid>) — Exploitation (locked: unlock with TL Creds)
- Egress to strutil-core Worm Collection Infrastructure [indicator]/v1/api and [indicator] — C2/Exfiltration (locked: unlock with TL Creds)

Sign up free to unlock the detection rules: https://threadlinqs.com/?modal=signup

## Sample indicators (3 of 23)

Defanged, TLP:CLEAR.

- Domain: `paleasbest[.]top`
- URL: `hxxps://github[.]com/oven-sh/bun/releases/download/bun-v1.2.18/`
- File hash: `3b3f6e1bde0b28b015030a81a6d805b5b5306dc3f021e1102123971cfd83acc0`

Full set of 23 indicators unlocks with TL Creds: https://threadlinqs.com/tl-creds

## Sources

- [Malicious Ruby Gem Steals 51 Types of Credentials and Plants Backdoors in AI Coding Tools](https://cybersecuritynews.com/malicious-ruby-gem/)
- [Socket: Shai-Hulud Descendant Surfaces in Malicious Ruby Gem That Steals Credentials and Poisons AI Tools](https://socket.dev/blog/shai-hulud-descendant-malicious-ruby-gem)
- [Socket package page: strutil-core](https://socket.dev/rubygems/package/strutil-core)
- [OpenSSF malicious-packages PR 1701 (strutil-core)](https://github.com/ossf/malicious-packages/pull/1701)
- [OSV MAL-2026-18432: pystr-codec](https://osv.dev/vulnerability/MAL-2026-18432)
- [SafeDep: Miasma worm hits LeoPlatform 20 npm packages](https://safedep.io/miasma-worm-hits-leoplatform-20-npm-packages/)
- [OSV MAL-2026-6433: rstreams-shard-util 1.0.1 (Miasma npm)](https://api.osv.dev/v1/vulns/MAL-2026-6433)
- [Deobfuscated June Miasma payload (defensive research)](https://github.com/geliuz/miasma-azure-durabletask)

## Full data

The other 8 detection rules (Splunk SPL / Microsoft KQL / Sigma) unlock with TL Creds; a free account includes daily creds: https://threadlinqs.com/?modal=signup
Indicator values unlock with TL Creds, at a lower price on Red and Purple; AI agents can query rules and IOCs through the Threadlinqs MCP server (Purple): https://intel.threadlinqs.com/mcp

Canonical: https://intel.threadlinqs.com/threat/TL-2026-5631
