JRuby is a curious artifact in the Ruby ecosystem. It is a full implementation of the Ruby language running on the JVM, maintained continuously since the 1.0 release in 2007, and as of November 2024 shipping the 9.4.9.0 version targeting Ruby 3.1 semantics. Most of the Ruby supply-chain discussion focuses on MRI, the C-based reference implementation, because MRI is what the vast majority of production Ruby runs on. JRuby users are a smaller population, but they inherit a meaningfully different supply-chain surface because every JRuby application is simultaneously a Ruby application and a JVM application. This post is for the teams who run JRuby in production and want to understand the security implications of that dual nature.
The headline observation: JRuby's supply chain is the union of the Ruby gem supply chain and the Maven supply chain, with an additional layer for the JRuby runtime itself. Each of those layers has its own threat model, its own advisory feeds, and its own tooling. Treating a JRuby application as just a Ruby application misses a significant portion of the real attack surface.
What parts of a JRuby application come from where?
A typical JRuby application has three dependency sources. First, pure-Ruby gems from RubyGems.org, which work identically to how they work on MRI. Second, Java-native gems or gems with JVM-specific variants, which ship .jar files alongside or instead of .rb files and run on the JVM rather than compiling native extensions. Third, direct Maven dependencies pulled in through the jar-dependencies gem or directly through Jars.lock.
The third category is where things diverge from MRI. JRuby applications routinely pull in JVM libraries directly from Maven Central, for example using Apache POI for Excel processing, Netty for high-performance networking, or Log4j for logging. These are declared in the application's Jarfile or Gemfile and resolved through Maven coordinates, and they live on the JVM side of the boundary.
The resulting dependency graph is bimodal. A typical mid-sized JRuby application might have 140 gems in its Gemfile.lock and 35 direct Maven dependencies that transitively pull in another 200 or so Maven artifacts. The two graphs are largely independent; a CVE in a Maven dependency does not show up in bundler-audit, and a CVE in a Ruby gem does not show up in a standard Java SCA tool.
Which advisory feeds cover JRuby applications?
For the Ruby side, the Ruby Advisory Database is the primary feed, and bundler-audit consumes it normally. JRuby-specific gems are covered to the extent that the advisory database entries include them, which varies. Popular gems like bcrypt-ruby have JRuby variants (jruby-openssl, bcrypt-pbkdf) that sometimes have their own advisories and sometimes are lumped in with the MRI versions.
For the Maven side, the GitHub Advisory Database, Sonatype's OSS Index, and the CVE/NVD feeds all cover Maven artifacts. The coverage is extensive because the Java ecosystem has mature SCA tooling. Tools like OWASP Dependency-Check, Snyk, and Grype all consume these feeds and can scan JAR files directly. For a JRuby application, you want to run both bundler-audit against the gems and a Maven-aware scanner against the JARs.
A gap worth knowing about: Jars.lock is not as comprehensive as Gemfile.lock in capturing the full dependency tree. Some JRuby applications declare Maven dependencies but resolve them loosely, leaving the lockfile underspecified. Scanning tools that rely on the lockfile miss transitive dependencies in these cases. Running jbundle install and then scanning the resolved vendor/jruby/*/gems/**/*.jar directly, rather than trusting the lockfile, catches the full graph.
What about the JRuby runtime itself?
The JRuby runtime has its own CVE stream, though less active than either Ruby or the JVM. The most recent meaningful advisory was CVE-2024-37079 disclosed in June 2024, which affected JRuby's handling of specific YAML structures and could lead to arbitrary code execution when deserializing untrusted YAML. The fix shipped in JRuby 9.4.7.0, and the advisory applied to all earlier 9.4.x versions.
Historically, JRuby has had roughly 2 to 4 advisories per year, which is lower than MRI's rate. The smaller user base and the narrower surface of JRuby's Ruby implementation on top of the JVM keep the count down. That said, a JRuby CVE tends to affect every application running on the vulnerable version, which makes keeping the runtime current important.
The JVM underneath JRuby has its own stream. JRuby 9.4 officially supports Java 8 through Java 21, and each JVM has its own CVE cadence. OpenJDK's April 2024 CPU included several CVEs affecting common JDK versions, including CVE-2024-21085 in the network layer. Teams running JRuby typically pin the JVM version in their container image or deployment configuration, and that pin needs to be maintained alongside the JRuby version.
How does native-extension monitoring change on JRuby?
One of the cleaner aspects of JRuby is that gems with native C extensions do not work the way they do on MRI. A gem that ships a C extension for MRI typically has a Java-based alternative on JRuby. For example, mysql2 on MRI is replaced by jdbc-mysql on JRuby, which is a JDBC driver wrapped in a gem. The C supply-chain risk of MRI native extensions, vulnerable linked libraries, unreviewed precompiled binaries, does not apply.
In exchange, the JDBC drivers and other Java-based replacements bring their own supply-chain risk from the Maven side. The MySQL JDBC driver has had CVEs (CVE-2023-22102, disclosed October 2023, affected connector-j versions before 8.0.34) that are invisible to a Ruby-only scanner. The risk is not smaller, it is just on a different graph.
The Nokogiri situation is a useful illustration. Nokogiri on MRI ships precompiled native extensions linking libxml2 and libxslt. Nokogiri on JRuby ships Java code that uses the JDK's built-in XML parsers (Xerces and Xalan indirectly). When a CVE hits libxml2, MRI users need a nokogiri upgrade; JRuby users are unaffected by that specific CVE. When a CVE hits Xerces, the reverse is true.
Log4Shell and JRuby
Log4Shell (CVE-2021-44228) is a useful case study for how JVM CVEs reach JRuby applications. Most Ruby applications had no exposure because they did not use Log4j. JRuby applications sometimes did, either directly through a gem that wrapped Log4j or through a Maven dependency that used it. The remediation story for JRuby teams was to audit their full JAR classpath for vulnerable Log4j versions, the same as for any Java application.
The incident highlighted that JRuby-specific SBOM generation needs to enumerate JAR files, not just gems. CycloneDX-Ruby and similar tools that stopped at the gem boundary produced incomplete SBOMs for JRuby applications. The newer generation of SBOM tools that walk the JVM classpath too, for example syft with its Java cataloger enabled, produces usable JRuby SBOMs.
What should a JRuby team's scanning pipeline look like?
A production-grade JRuby scanning pipeline has three passes. First, bundler-audit against the Gemfile.lock for Ruby-side CVEs. Second, a Java-aware SCA tool against the resolved JAR classpath, either inspecting the installed vendor/jruby tree or inspecting the packaged WAR/JAR if the application ships as one. Third, a runtime and JVM check that confirms the JRuby version is current and the JVM version is within its security-supported window.
For the second pass, the specific tool matters less than coverage. OWASP Dependency-Check is free and well-understood; commercial tools like Snyk and Sonatype offer richer data. Pick one and make sure it is wired into CI. The cost of running it is low, typically under a minute for a mid-sized JRuby classpath, and the coverage benefit is substantial.
For the third pass, a simple check of the installed JRuby version against the latest patch release catches most issues. The JRuby project maintains a relatively clear security-supported version policy: the current minor release (9.4.x) gets all patches, the previous minor (9.3.x) gets critical patches, and older minors are EOL. As of November 2024, 9.3.x entered limited-support mode and 9.2.x is EOL, so any production JRuby on 9.2 has a known remediation path.
Container images and JRuby distribution
JRuby is typically deployed in a container with a specific JVM version. The container image supply chain applies here too: the base image (commonly eclipse-temurin or amazoncorretto) has its own CVE stream, and the layering of JRuby on top introduces another layer to audit. Keeping base images current is a standard container hygiene practice that applies equally to JRuby workloads.
The DockerHub jruby image, maintained by the JRuby team, is a reasonable starting point but bundles specific JVM and JRuby versions that lag slightly behind the independent releases. Teams with strict patch-currency requirements often build their own JRuby images from a more recent base. The tradeoff is image-maintenance effort versus patch latency.
How Safeguard Helps
Safeguard scans JRuby applications on both supply-chain axes, running bundler-audit against your Gemfile.lock and a Java-aware SCA pass against your resolved JAR classpath in a single pipeline. We cross-reference JRuby runtime and JVM CVEs against your deployed versions, alerting when a JRuby patch release addresses an advisory that affects your running applications. Our SBOM generation walks both the gem tree and the JVM classpath, producing a complete dependency graph that single-ecosystem tools miss. For teams running JRuby in production, this means you are not stitching together two separate security programs to cover one runtime.