Skip to content

Commit 2d2f4ed

Browse files
authored
Merge commit from fork
[1.11] fix CVE 2025 27407
2 parents 53bc45b + de4d0c7 commit 2d2f4ed

10 files changed

Lines changed: 196 additions & 20 deletions

File tree

cop/development/no_eval_cop.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
require 'rubocop'
3+
4+
module Cop
5+
module Development
6+
class NoEvalCop < RuboCop::Cop::Base
7+
MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accept strings and may result in unexpected code being generated. Use `%{exec_method_name}` instead, and pass a block."
8+
9+
def on_send(node)
10+
case node.method_name
11+
when :module_eval, :class_eval, :instance_eval
12+
message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
13+
add_offense node, message: message
14+
end
15+
end
16+
end
17+
end
18+
end

lib/graphql/language/nodes.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ def merge!(new_options)
133133
end
134134

135135
class << self
136+
# rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
137+
136138
# Add a default `#visit_method` and `#children_method_name` using the class name
137139
def inherited(child_class)
138140
super
@@ -265,8 +267,11 @@ def initialize_node #{arguments.join(", ")}
265267
#{assignments.join("\n")}
266268
end
267269
RUBY
270+
271+
# rubocop:enable Development/NoEvalCop
268272
end
269273
end
274+
# rubocop:enable Development/NoEvalCop
270275
end
271276
end
272277

lib/graphql/language/visitor.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def visit
7373
@document
7474
end
7575
end
76+
# rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
7677

7778
# Call the user-defined handler for `node`.
7879
def visit_node(node, parent)

lib/graphql/schema/argument.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def from_resolver?
4949
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, deprecation_reason: nil, &definition_block)
5050
arg_name ||= name
5151
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
52+
NameValidator.validate!(@name)
5253
@type_expr = type_expr || type
5354
@description = desc || description
5455
@null = !required
@@ -64,11 +65,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil
6465
self.deprecation_reason = deprecation_reason
6566

6667
if definition_block
67-
if definition_block.arity == 1
68-
instance_exec(self, &definition_block)
69-
else
70-
instance_eval(&definition_block)
71-
end
68+
# `self` will still be self, it will also be the first argument to the block:
69+
instance_exec(self, &definition_block)
7270
end
7371
end
7472

lib/graphql/schema/build_from_definition.rb

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -342,13 +342,7 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
342342

343343
# Don't do this for interfaces
344344
if default_resolve
345-
owner.class_eval <<-RUBY, __FILE__, __LINE__
346-
# frozen_string_literal: true
347-
def #{resolve_method_name}(**args)
348-
field_instance = self.class.get_field("#{field_definition.name}")
349-
context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
350-
end
351-
RUBY
345+
define_field_resolve_method(owner, resolve_method_name, field_definition.name)
352346
end
353347
end
354348
end
@@ -367,6 +361,13 @@ def resolve_type(types, ast_node)
367361
end
368362
end
369363

364+
def define_field_resolve_method(owner, method_name, field_name)
365+
owner.define_method(method_name) { |**args|
366+
field_instance = self.class.get_field(field_name)
367+
context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
368+
}
369+
end
370+
370371
def resolve_type_name(type)
371372
case type
372373
when GraphQL::Language::Nodes::TypeName

lib/graphql/schema/enum_value.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil
4949
@ast_node = ast_node
5050

5151
if block_given?
52-
instance_eval(&block)
52+
instance_exec(self, &block)
5353
end
5454
end
5555

lib/graphql/schema/field.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function
222222
name_s = -name.to_s
223223
@underscored_name = -Member::BuildType.underscore(name_s)
224224
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
225+
NameValidator.validate!(@name)
225226
@description = description
226227
if field.is_a?(GraphQL::Schema::Field)
227228
raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."

lib/graphql/schema/input_object.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,8 @@ class << self
125125
def argument(*args, **kwargs, &block)
126126
argument_defn = super(*args, **kwargs, &block)
127127
# Add a method access
128-
method_name = argument_defn.keyword
129-
class_eval <<-RUBY, __FILE__, __LINE__
130-
def #{method_name}
131-
self[#{method_name.inspect}]
132-
end
133-
RUBY
128+
define_accessor_method(argument_defn.keyword)
129+
argument_defn
134130
end
135131

136132
def to_graphql
@@ -239,6 +235,13 @@ def coerce_result(value, ctx)
239235

240236
result
241237
end
238+
239+
private
240+
241+
def define_accessor_method(method_name)
242+
define_method(method_name) { self[method_name] }
243+
alias_method(method_name, method_name)
244+
end
242245
end
243246
end
244247
end

lib/graphql/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# frozen_string_literal: true
22
module GraphQL
3-
VERSION = "1.11.7"
3+
VERSION = "1.11.8"
44
end

spec/graphql/schema/loader_spec.rb

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,4 +389,153 @@ def assert_deep_equal(expected_type, actual_type)
389389
assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
390390
end
391391
end
392+
393+
it "validates field argument names" do
394+
json = {
395+
"data" => {
396+
"__schema" => {
397+
"queryType" => {
398+
"name" => "Query"
399+
},
400+
"mutationType" => nil,
401+
"subscriptionType" => nil,
402+
"types" => [
403+
{
404+
"kind" => "OBJECT",
405+
"name" => "Query",
406+
"description" => nil,
407+
"fields" => [
408+
{
409+
"name" => "int",
410+
"description" => nil,
411+
"type" => {
412+
"kind" => "SCALAR",
413+
"name" => "Int",
414+
"ofType" => nil,
415+
},
416+
"args" => [
417+
{
418+
"name" => "something-wrong",
419+
"description" => nil,
420+
"type" => {
421+
"kind" => "SCALAR",
422+
"name" => "Int",
423+
"ofType" => nil
424+
},
425+
"defaultValue" => nil
426+
}
427+
],
428+
}
429+
]
430+
}
431+
]
432+
}
433+
}
434+
}
435+
err = assert_raises GraphQL::InvalidNameError do
436+
GraphQL::Schema.from_introspection(json)
437+
end
438+
439+
assert_includes err.message, "something-wrong"
440+
end
441+
442+
it "validates field names" do
443+
json = {
444+
"data" => {
445+
"__schema" => {
446+
"queryType" => {
447+
"name" => "Query"
448+
},
449+
"mutationType" => nil,
450+
"subscriptionType" => nil,
451+
"types" => [
452+
{
453+
"kind" => "OBJECT",
454+
"name" => "Query",
455+
"description" => nil,
456+
"fields" => [
457+
{
458+
"name" => "bad.int",
459+
"description" => nil,
460+
"type" => {
461+
"kind" => "SCALAR",
462+
"name" => "Int",
463+
"ofType" => nil,
464+
},
465+
"args" => [],
466+
}
467+
]
468+
}
469+
]
470+
}
471+
}
472+
}
473+
err = assert_raises GraphQL::InvalidNameError do
474+
GraphQL::Schema.from_introspection(json)
475+
end
476+
477+
assert_includes err.message, "bad.int"
478+
end
479+
480+
it "validates input object argument names" do
481+
json = {
482+
"data" => {
483+
"__schema" => {
484+
"queryType" => {
485+
"name" => "Query"
486+
},
487+
"mutationType" => nil,
488+
"subscriptionType" => nil,
489+
"types" => [
490+
{
491+
"kind" => "OBJECT",
492+
"name" => "Query",
493+
"description" => nil,
494+
"fields" => [
495+
{
496+
"name" => "int",
497+
"description" => nil,
498+
"type" => {
499+
"kind" => "SCALAR",
500+
"name" => "Int",
501+
"ofType" => nil,
502+
},
503+
"args" => [
504+
{
505+
"name" => "inputObject",
506+
"description" => nil,
507+
"type" => {
508+
"kind" => "INPUT_OBJECT",
509+
"name" => "SomeInputObject",
510+
"ofType" => nil
511+
},
512+
"defaultValue" => nil
513+
}
514+
],
515+
}
516+
]
517+
},
518+
{
519+
"kind" => "INPUT_OBJECT",
520+
"name" => "SomeInputObject",
521+
"description" => nil,
522+
"inputFields" => [
523+
{
524+
"name"=>"bad, input",
525+
"type"=> { "kind" => "SCALAR", "name" => "String"},
526+
"defaultValue"=> nil,
527+
"description" => nil,
528+
},
529+
]
530+
}
531+
]
532+
}
533+
}
534+
}
535+
err = assert_raises GraphQL::InvalidNameError do
536+
GraphQL::Schema.from_introspection(json)
537+
end
538+
539+
assert_includes err.message, "bad, input"
540+
end
392541
end

0 commit comments

Comments
 (0)