In attacks that exploit package registries, the typical method involves attackers uploading packages containing malware, which unsuspecting developers then install. However, the RubyGems supply chain attack recently observed by researchers used the opposite approach. Instead of using the registry as a distribution point for malware, it was used as a storage location for stolen data.
In June 2026, 14 .gem files were uploaded to RubyGems.org within a 6.5-hour period. None of these gems distributed malware. Instead, they contained all the data from 1Password vaults that had been stolen by a malicious browser extension, stored as .gem files. Moreover, these .gem files were created using only JavaScript within the victim's browser.
The stolen data included plaintext passwords, SSH private keys, AWS credentials, cryptocurrency wallet seed phrases, social security numbers, credit card numbers, and bank account information. A total of 63 items from the vault were affected.
In this attack, RubyGems was not used as a mechanism to exfiltrate data. Instead, it was used as a dead drop—a place to store stolen data for the attackers to retrieve later. Because it's a highly trusted and frequently accessed domain, it was possible to blend it in with regular uploads from developers.
How RubyGems supply chain attacks work
The target in this attack was not the real 1Password app. The actual 1Password uses native messaging and IPC, not HTTP, for communication with browser extensions. The target of the attack was a mock password management app built with Next.js and running on a local .local hostname. Synthetic data was registered in it, including GitHub and Gmail login information, AWS IAM keys, SSH keys, credit card information, bank account information, and cryptocurrency seed phrases.
It's impossible to determine from the remaining evidence alone whether this incident was a security study or a dry run (rehearsal) for an actual attack on a target. However, it can be confirmed that the tools used in the attack were fully functional.
No special access rights were required to extract the data. This was because a browser extension with broad permissions could access localhost and .local addresses. In reality, the data was retrieved in three steps.
First, we used an item index to list all the items in the vault and understand the types of data it contained. Next, we retrieved each of the 87 items one by one. This allowed us to extract the complete plaintext data, the same as what is displayed on the application screen.
Furthermore, we accessed the database endpoint and directly dumped the state of the underlying SQLite. This also retrieved 19 items that the user had already archived or deleted.
All the retrieved data is compressed in memory before being passed to helper.js. This compressed file, reduced to about 10 lines, uses only standard browser APIs such as CompressionStream, TextEncoder, and fetch to generate a valid tar-based .gem file. Then, using the attacker's pre-registered API key, it is directly POSTed to the RubyGems public API.
There are no server-side components or native tools here. The generated gem contains no Ruby source code and cannot be required. This gem existed solely as a container to store the stolen data.
Anatomy of the Attack: A Step-by-Step Timeline
By arranging the timestamps embedded in the 14 gems in chronological order, we can see how the attack unfolded.
Phase 1: Test the route.
Hello, webmaster
It started with infrastructure testing. We used three disposable gems to confirm that the upload pipeline was working correctly, and then, after four minutes of trial and error, we confirmed that uploads could be done without any problems.
Phase 2: Exploring the Interior
Next, the reconnaissance begins. Using five gems, we retrieved the vault index, application chunks, route map, and rendered HTML. We first understood the target's structure before touching on the credentials themselves.
Phase 3: Targeting
Next came gems that retrieve security dashboards. This allowed attackers to prioritize targeting vulnerable or reused credentials.
Phase 4: Stealing data
After a few hours, the full-scale data acquisition began. A complete database dump and a bulk acquisition targeting the API were performed. The same data was acquired using two independent methods, which is likely a redundancy measure to confirm that the data extraction was successful.
Phase 5: Verifying Results
Finally, the 13-page administration screen was rendered and retrieved. This allowed us to visually confirm that the data extraction was successful.
Why RubyGems would be an ideal dead drop
RubyGems has three features that command and control (C2) servers lack.
- It's indistinguishable from a normal gem push:
A POST request from a developer's machine to RubyGems.org is indistinguishable from a normal gem push. Therefore, DLP rules and SIEM detection systems that target communication to unfamiliar hosts will not detect the attack. - The attacker's infrastructure is missing:
There is no infrastructure on the attacker's side that needs to be shut down. This is because the infrastructure they are using is RubyGems itself. - You can retrieve published gems later:
Once a gem is published, it remains available. Therefore, even weeks later, you can retrieve it with a single command using `gem fetch` from any machine.
In other words, they weren't hiding the stolen data inside a package; they were uploading the stolen data itself to RubyGems as a package.
Why this supply chain attack is difficult to detect
The fact that package registries are essential for developers directly translates to their appeal as a distribution channel. Blocking POST requests to RubyGems.org would halt the legitimate operations of organizations that publish software using Ruby. Therefore, it is not practical for security teams to uniformly block all POST requests.
Examining the payload doesn't necessarily lead to detection. This particular gem fits within the same domain, endpoint, content type, format, and size range as legitimate uploads. Furthermore, because it contains no Ruby source code whatsoever, it slips right through malicious gem scanners designed to detect code containing backdoors.
Even more importantly, this technique isn't unique to Ruby. npm's .tgz, PyPI's .whl, NuGet's .nupkg, and Docker Hub's tar layers can all be assembled in the browser in the same way. Any registry that accepts HTTPS uploads could potentially be used as a similar dead drop.
Realistic defenses need to be implemented at both the endpoint and the registry. For example, EDR can be used to detect communications where a browser process is POSTing to the registry API with an authentication header.
In addition, the registry could potentially perform anomaly detection based on factors such as publisher behavior, package structure, and key usage period. However, none of these mechanisms are currently implemented on a large scale.
Key points for security and AppSec teams
The technique of generating .gem files within the browser using the Web Compression API and utilizing RubyGems as anonymous dead drops represents a new advancement in credential theft. It does not require a server managed by the attacker and does not generate any suspicious outbound communications. The only trace left for forensic analysis is a gem that looks like a normal developer's work.
Furthermore, this technique can be directly applied to other registries that accept HTTPS uploads, such as npm, PyPI, and NuGet. Defenses that focus solely on infrastructure owned by the attacker may miss this type of attack.
Please note that the gem name, hash, and the attacker's full API key have not been made public. This information will only be provided upon request from verified security researchers and affected vendors.
