VYPR
Critical severity9.0NVD Advisory· Published Mar 12, 2025· Updated Apr 15, 2026

CVE-2025-27407

CVE-2025-27407

Description

graphql-ruby is a Ruby implementation of GraphQL. Starting in version 1.11.5 and prior to versions 1.11.8, 1.12.25, 1.13.24, 2.0.32, 2.1.14, 2.2.17, and 2.3.21, loading a malicious schema definition in GraphQL::Schema.from_introspection (or GraphQL::Schema::Loader.load) can result in remote code execution. Any system which loads a schema by JSON from an untrusted source is vulnerable, including those that use GraphQL::Client to load external schemas via GraphQL introspection. Versions 1.11.8, 1.12.25, 1.13.24, 2.0.32, 2.1.14, 2.2.17, and 2.3.21 contain a patch for the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
graphqlRubyGems
>= 2.4.0, < 2.4.132.4.13
graphqlRubyGems
>= 2.3.0, < 2.3.212.3.21
graphqlRubyGems
>= 2.2.0, < 2.2.172.2.17
graphqlRubyGems
>= 2.1.0, < 2.1.152.1.15
graphqlRubyGems
>= 2.0.0, < 2.0.322.0.32
graphqlRubyGems
>= 1.13.0, < 1.13.241.13.24
graphqlRubyGems
>= 1.12.0, < 1.12.251.12.25
graphqlRubyGems
>= 1.11.5, < 1.11.111.11.11

Patches

15
5c5a7b9a9bdc

Merge commit from fork

https://github.com/rmosolgo/graphql-rubyRobert MosolgoMar 12, 2025via ghsa
30 files changed · +337 114
  • benchmark/run.rb+7 2 modified
    @@ -110,12 +110,16 @@ def self.build_large_schema
             end
     
             obj_ts = 100.times.map do |n|
    +          input_obj_t = Class.new(GraphQL::Schema::InputObject) do
    +            graphql_name("Input#{n}")
    +            argument :arg, String
    +          end
               obj_t = Class.new(GraphQL::Schema::Object) do
                 graphql_name("Object#{n}")
                 implements(*int_ts)
                 20.times do |n2|
                   field :"field#{n2}", String do
    -                argument :arg, String
    +                argument :input, input_obj_t
                   end
     
                 end
    @@ -152,8 +156,9 @@ def self.profile_boot
         end
         StackProf::Report.new(result).print_text
     
    +    retained_schema = nil
         report = MemoryProfiler.report do
    -      build_large_schema
    +      retained_schema = build_large_schema
         end
     
         report.pretty_print
    
  • cop/development/no_eval_cop.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'rubocop'
    +
    +module Cop
    +  module Development
    +    class NoEvalCop < RuboCop::Cop::Base
    +      MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accepts strings and may result evaluating unexpected code. Use `%{exec_method_name}` instead, and pass a block."
    +
    +      def on_send(node)
    +        case node.method_name
    +        when :module_eval, :class_eval, :instance_eval
    +          message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
    +          add_offense node, message: message
    +        end
    +      end
    +    end
    +  end
    +end
    
  • lib/graphql/language/nodes.rb+3 0 modified
    @@ -138,6 +138,8 @@ def merge!(new_options)
             end
     
             class << self
    +          # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
               # Add a default `#visit_method` and `#children_method_name` using the class name
               def inherited(child_class)
                 super
    @@ -316,6 +318,7 @@ def self.from_a(filename, line, col, #{(scalar_method_names + @children_methods.
                   RUBY
                 end
               end
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/language/static_visitor.rb+37 33 modified
    @@ -22,39 +22,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [void]
    -          def #{node_method}(node, parent)
    -            #{
    -              if method_defined?(child_visit_method)
    -                "#{child_visit_method}(node)"
    -              elsif children_of_type
    -                children_of_type.map do |child_accessor, child_class|
    -                  "node.#{child_accessor}.each do |child_node|
    -                    #{child_class.visit_method}(child_node, node)
    -                  end"
    -                end.join("\n")
    -              else
    -                ""
    -              end
    -            }
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             document_node.children.each do |child_node|
               visit_method = child_node.visit_method
    @@ -123,6 +90,41 @@ def on_argument_children(new_node)
             end
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      # We don't use `alias` here because it breaks `super`
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [void]
    +          def #{node_method}(node, parent)
    +            #{
    +              if method_defined?(child_visit_method)
    +                "#{child_visit_method}(node)"
    +              elsif children_of_type
    +                children_of_type.map do |child_accessor, child_class|
    +                  "node.#{child_accessor}.each do |child_node|
    +                    #{child_class.visit_method}(child_node, node)
    +                  end"
    +                end.join("\n")
    +              else
    +                ""
    +              end
    +            }
    +          end
    +        RUBY
    +      end
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -162,6 +164,8 @@ def on_argument_children(new_node)
           ].each do |ast_node_class|
             make_visit_methods(ast_node_class)
           end
    +
    +      # rubocop:disable Development/NoEvalCop
         end
       end
     end
    
  • lib/graphql/language/visitor.rb+59 55 modified
    @@ -61,61 +61,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    -          def #{node_method}(node, parent)
    -            if node.equal?(DELETE_NODE)
    -              # This might be passed to `super(DELETE_NODE, ...)`
    -              # by a user hook, don't want to keep visiting in that case.
    -              [node, parent]
    -            else
    -              new_node = node
    -              #{
    -                if method_defined?(child_visit_method)
    -                  "new_node = #{child_visit_method}(new_node)"
    -                elsif children_of_type
    -                  children_of_type.map do |child_accessor, child_class|
    -                    "node.#{child_accessor}.each do |child_node|
    -                      new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    -                      # Reassign `node` in case the child hook makes a modification
    -                      if new_child_and_node.is_a?(Array)
    -                        new_node = new_child_and_node[1]
    -                      end
    -                    end"
    -                  end.join("\n")
    -                else
    -                  ""
    -                end
    -              }
    -
    -              if new_node.equal?(node)
    -                [node, parent]
    -              else
    -                [new_node, parent]
    -              end
    -            end
    -          end
    -
    -          def #{node_method}_with_modifications(node, parent)
    -            new_node_and_new_parent = #{node_method}(node, parent)
    -            apply_modifications(node, parent, new_node_and_new_parent)
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             new_node = document_node
             document_node.children.each do |child_node|
    @@ -216,6 +161,63 @@ def on_argument_children(new_node)
             new_node
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      # We don't use `alias` here because it breaks `super`
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    +          def #{node_method}(node, parent)
    +            if node.equal?(DELETE_NODE)
    +              # This might be passed to `super(DELETE_NODE, ...)`
    +              # by a user hook, don't want to keep visiting in that case.
    +              [node, parent]
    +            else
    +              new_node = node
    +              #{
    +                if method_defined?(child_visit_method)
    +                  "new_node = #{child_visit_method}(new_node)"
    +                elsif children_of_type
    +                  children_of_type.map do |child_accessor, child_class|
    +                    "node.#{child_accessor}.each do |child_node|
    +                      new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    +                      # Reassign `node` in case the child hook makes a modification
    +                      if new_child_and_node.is_a?(Array)
    +                        new_node = new_child_and_node[1]
    +                      end
    +                    end"
    +                  end.join("\n")
    +                else
    +                  ""
    +                end
    +              }
    +
    +              if new_node.equal?(node)
    +                [node, parent]
    +              else
    +                [new_node, parent]
    +              end
    +            end
    +          end
    +
    +          def #{node_method}_with_modifications(node, parent)
    +            new_node_and_new_parent = #{node_method}(node, parent)
    +            apply_modifications(node, parent, new_node_and_new_parent)
    +          end
    +        RUBY
    +      end
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -256,6 +258,8 @@ def on_argument_children(new_node)
             make_visit_methods(ast_node_class)
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           private
     
           def apply_modifications(node, parent, new_node_and_new_parent)
    
  • lib/graphql/schema/argument.rb+3 5 modified
    @@ -53,6 +53,7 @@ def from_resolver?
           def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
             arg_name ||= name
             @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
    +        NameValidator.validate!(@name)
             @type_expr = type_expr || type
             @description = desc || description
             @null = required != true
    @@ -88,11 +89,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type
             end
     
             if definition_block
    -          if definition_block.arity == 1
    -            instance_exec(self, &definition_block)
    -          else
    -            instance_eval(&definition_block)
    -          end
    +          # `self` will still be self, it will also be the first argument to the block:
    +          instance_exec(self, &definition_block)
             end
           end
     
    
  • lib/graphql/schema/build_from_definition.rb+8 7 modified
    @@ -451,17 +451,18 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
     
                 # Don't do this for interfaces
                 if default_resolve
    -              owner.class_eval <<-RUBY, __FILE__, __LINE__
    -                # frozen_string_literal: true
    -                def #{resolve_method_name}(**args)
    -                  field_instance = self.class.get_field("#{field_definition.name}")
    -                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    -                end
    -              RUBY
    +              define_field_resolve_method(owner, resolve_method_name, field_definition.name)
                 end
               end
             end
     
    +        def define_field_resolve_method(owner, method_name, field_name)
    +          owner.define_method(method_name) { |**args|
    +            field_instance = self.class.get_field(field_name)
    +            context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    +          }
    +        end
    +
             def build_resolve_type(lookup_hash, directives, missing_type_handler)
               resolve_type_proc = nil
               resolve_type_proc = ->(ast_node) {
    
  • lib/graphql/schema/directive.rb+1 1 modified
    @@ -99,7 +99,7 @@ def repeatable(new_value)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @default_graphql_name ||= nil
               end
             end
    
  • lib/graphql/schema/enum_value.rb+1 1 modified
    @@ -47,7 +47,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil,
             end
     
             if block_given?
    -          instance_eval(&block)
    +          instance_exec(self, &block)
             end
           end
     
    
  • lib/graphql/schema/field.rb+1 1 modified
    @@ -233,7 +233,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CON
     
             @underscored_name = -Member::BuildType.underscore(name_s)
             @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
    -
    +        NameValidator.validate!(@name)
             @description = description
             @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
     
    
  • lib/graphql/schema/interface.rb+1 1 modified
    @@ -29,7 +29,7 @@ def definition_methods(&block)
                 const_set(:DefinitionMethods, defn_methods_module)
                 extend(self::DefinitionMethods)
               end
    -          self::DefinitionMethods.module_eval(&block)
    +          self::DefinitionMethods.module_exec(&block)
             end
     
             # @see {Schema::Warden} hides interfaces without visible implementations
    
  • lib/graphql/schema/member/has_directives.rb+1 1 modified
    @@ -6,7 +6,7 @@ class Member
           module HasDirectives
             def self.extended(child_cls)
               super
    -          child_cls.module_eval { self.own_directives = nil }
    +          child_cls.module_exec { self.own_directives = nil }
             end
     
             def inherited(child_cls)
    
  • lib/graphql/schema/member/has_fields.rb+1 1 modified
    @@ -183,7 +183,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_fields ||= nil
                 @field_class ||= nil
               end
    
  • lib/graphql/schema/member/has_interfaces.rb+1 1 modified
    @@ -133,7 +133,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_interface_type_memberships ||= nil
               end
             end
    
  • lib/graphql/schema/member/scoped.rb+1 1 modified
    @@ -30,7 +30,7 @@ def reauthorize_scoped_objects(new_value = nil)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @reauthorize_scoped_objects = nil
               end
             end
    
  • lib/graphql/schema/member/type_system_helpers.rb+1 1 modified
    @@ -43,7 +43,7 @@ def kind
             private
     
             def inherited(subclass)
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @to_non_null_type ||= nil
                 @to_list_type ||= nil
               end
    
  • lib/graphql/tracing/appoptics_trace.rb+4 0 modified
    @@ -28,6 +28,8 @@ def self.version
             Gem::Version.new('1.0.0')
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           [
             'lex',
             'parse',
    @@ -55,6 +57,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field(query:, field:, ast_node:, arguments:, object:)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/appsignal_trace.rb+4 0 modified
    @@ -13,6 +13,8 @@ def initialize(set_action_name: false, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -43,6 +45,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key)
             Appsignal.instrument(platform_key) do
               yield
    
  • lib/graphql/tracing/data_dog_trace.rb+4 0 modified
    @@ -20,6 +20,8 @@ def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => 'lex.graphql',
             'parse' => 'parse.graphql',
    @@ -69,6 +71,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field_span(span_key, query, field, ast_node, arguments, object)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/notifications_trace.rb+4 0 modified
    @@ -16,6 +16,8 @@ def initialize(engine:, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -39,6 +41,8 @@ def #{trace_method}(**metadata, &blk)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           include PlatformTrace
         end
       end
    
  • lib/graphql/tracing/platform_trace.rb+5 0 modified
    @@ -39,6 +39,9 @@ def self.included(child_class)
               include(BaseKeyCache)
             }
             child_class.const_set(:KeyCache, key_methods_class)
    +
    +        # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
             [:execute_field, :execute_field_lazy].each do |field_trace_method|
               if !child_class.method_defined?(field_trace_method)
                 child_class.module_eval <<-RUBY, __FILE__, __LINE__
    @@ -91,6 +94,8 @@ def #{rt_trace_method}(query:, type:, object:)
                   end
                 RUBY
               end
    +
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/tracing/prometheus_trace.rb+4 0 modified
    @@ -13,6 +13,8 @@ def initialize(client: PrometheusExporter::Client.default, keys_whitelist: ["exe
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -30,6 +32,8 @@ def #{trace_method}(**data, &block)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             instrument_execution(platform_key, "execute_field", &block)
           end
    
  • lib/graphql/tracing/scout_trace.rb+3 0 modified
    @@ -16,6 +16,8 @@ def initialize(set_transaction_name: false, **_rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -45,6 +47,7 @@ def #{trace_method}(**data)
             end
             RUBY
           end
    +      # rubocop:enable Development/NoEvalCop
     
           def platform_execute_field(platform_key, &block)
             self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS, &block)
    
  • lib/graphql/tracing/sentry_trace.rb+4 0 modified
    @@ -23,6 +23,8 @@ def execute_query(**data)
             instrument_execution("graphql.execute", "execute_query", data) { super }
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "graphql.lex",
             "parse" => "graphql.parse",
    @@ -39,6 +41,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             instrument_execution(platform_key, "execute_field", &block)
           end
    
  • lib/graphql/tracing/statsd_trace.rb+4 0 modified
    @@ -11,6 +11,8 @@ def initialize(statsd:, **rest)
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -30,6 +32,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             @statsd.time(platform_key, &block)
           end
    
  • lib/graphql/types/relay/connection_behaviors.rb+1 1 modified
    @@ -13,7 +13,7 @@ def self.included(child_class)
               child_class.node_nullable(true)
               child_class.edges_nullable(true)
               child_class.edge_nullable(true)
    -          child_class.module_eval {
    +          child_class.module_exec {
                 self.edge_type = nil
                 self.node_type = nil
                 self.edge_class = nil
    
  • lib/graphql/types/relay/edge_behaviors.rb+1 1 modified
    @@ -8,7 +8,7 @@ def self.included(child_class)
               child_class.description("An edge in a connection.")
               child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
               child_class.extend(ClassMethods)
    -          child_class.class_eval { self.node_type = nil }
    +          child_class.class_exec { self.node_type = nil }
               child_class.node_nullable(true)
             end
     
    
  • lib/graphql/version.rb+1 1 modified
    @@ -1,4 +1,4 @@
     # frozen_string_literal: true
     module GraphQL
    -  VERSION = "2.2.16"
    +  VERSION = "2.2.17"
     end
    
  • .rubocop.yml+5 0 modified
    @@ -1,5 +1,6 @@
     require:
       - ./cop/development/none_without_block_cop
    +  - ./cop/development/no_eval_cop
       - ./cop/development/no_focus_cop
       - ./lib/graphql/rubocop/graphql/default_null_true
       - ./lib/graphql/rubocop/graphql/default_required_true
    @@ -51,6 +52,10 @@ Development/NoneWithoutBlockCop:
         - "lib/**/*"
         - "spec/**/*"
     
    +Development/NoEvalCop:
    +  Include:
    +    - "lib/**/*"
    +
     Development/NoFocusCop:
       Include:
         - "spec/**/*"
    
  • spec/graphql/schema/loader_spec.rb+149 0 modified
    @@ -398,4 +398,153 @@ def assert_deep_equal(expected_type, actual_type)
           assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
         end
       end
    +
    +  it "validates field argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "something-wrong",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "SCALAR",
    +                        "name" => "Int",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "something-wrong"
    +  end
    +
    +  it "validates field names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "bad.int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad.int"
    +  end
    +
    +  it "validates input object argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "inputObject",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "INPUT_OBJECT",
    +                        "name" => "SomeInputObject",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            },
    +            {
    +              "kind" => "INPUT_OBJECT",
    +              "name" => "SomeInputObject",
    +              "description" => nil,
    +              "inputFields" => [
    +                {
    +                  "name"=>"bad, input",
    +                  "type"=> { "kind" => "SCALAR", "name" => "String"},
    +                  "defaultValue"=> nil,
    +                  "description" => nil,
    +                },
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad, input"
    +  end
     end
    
d1117ae0361d

Merge commit from fork

https://github.com/rmosolgo/graphql-rubyRobert MosolgoMar 12, 2025via ghsa
30 files changed · +354 120
  • benchmark/run.rb+7 2 modified
    @@ -110,12 +110,16 @@ def self.build_large_schema
             end
     
             obj_ts = 100.times.map do |n|
    +          input_obj_t = Class.new(GraphQL::Schema::InputObject) do
    +            graphql_name("Input#{n}")
    +            argument :arg, String
    +          end
               obj_t = Class.new(GraphQL::Schema::Object) do
                 graphql_name("Object#{n}")
                 implements(*int_ts)
                 20.times do |n2|
                   field :"field#{n2}", String do
    -                argument :arg, String
    +                argument :input, input_obj_t
                   end
     
                 end
    @@ -152,8 +156,9 @@ def self.profile_boot
         end
         StackProf::Report.new(result).print_text
     
    +    retained_schema = nil
         report = MemoryProfiler.report do
    -      build_large_schema
    +      retained_schema = build_large_schema
         end
     
         report.pretty_print
    
  • cop/development/no_eval_cop.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'rubocop'
    +
    +module Cop
    +  module Development
    +    class NoEvalCop < RuboCop::Cop::Base
    +      MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accepts strings and may result evaluating unexpected code. Use `%{exec_method_name}` instead, and pass a block."
    +
    +      def on_send(node)
    +        case node.method_name
    +        when :module_eval, :class_eval, :instance_eval
    +          message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
    +          add_offense node, message: message
    +        end
    +      end
    +    end
    +  end
    +end
    
  • lib/graphql/language/nodes.rb+3 0 modified
    @@ -138,6 +138,8 @@ def merge!(new_options)
             end
     
             class << self
    +          # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
               # Add a default `#visit_method` and `#children_method_name` using the class name
               def inherited(child_class)
                 super
    @@ -296,6 +298,7 @@ def self.from_a(filename, line, col, #{(scalar_method_names + @children_methods.
                   RUBY
                 end
               end
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/language/static_visitor.rb+37 33 modified
    @@ -22,39 +22,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [void]
    -          def #{node_method}(node, parent)
    -            #{
    -              if method_defined?(child_visit_method)
    -                "#{child_visit_method}(node)"
    -              elsif children_of_type
    -                children_of_type.map do |child_accessor, child_class|
    -                  "node.#{child_accessor}.each do |child_node|
    -                    #{child_class.visit_method}(child_node, node)
    -                  end"
    -                end.join("\n")
    -              else
    -                ""
    -              end
    -            }
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             document_node.children.each do |child_node|
               visit_method = child_node.visit_method
    @@ -123,6 +90,41 @@ def on_argument_children(new_node)
             end
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      # We don't use `alias` here because it breaks `super`
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [void]
    +          def #{node_method}(node, parent)
    +            #{
    +              if method_defined?(child_visit_method)
    +                "#{child_visit_method}(node)"
    +              elsif children_of_type
    +                children_of_type.map do |child_accessor, child_class|
    +                  "node.#{child_accessor}.each do |child_node|
    +                    #{child_class.visit_method}(child_node, node)
    +                  end"
    +                end.join("\n")
    +              else
    +                ""
    +              end
    +            }
    +          end
    +        RUBY
    +      end
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -162,6 +164,8 @@ def on_argument_children(new_node)
           ].each do |ast_node_class|
             make_visit_methods(ast_node_class)
           end
    +
    +      # rubocop:disable Development/NoEvalCop
         end
       end
     end
    
  • lib/graphql/language/visitor.rb+59 55 modified
    @@ -61,61 +61,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    -          def #{node_method}(node, parent)
    -            if node.equal?(DELETE_NODE)
    -              # This might be passed to `super(DELETE_NODE, ...)`
    -              # by a user hook, don't want to keep visiting in that case.
    -              [node, parent]
    -            else
    -              new_node = node
    -              #{
    -                if method_defined?(child_visit_method)
    -                  "new_node = #{child_visit_method}(new_node)"
    -                elsif children_of_type
    -                  children_of_type.map do |child_accessor, child_class|
    -                    "node.#{child_accessor}.each do |child_node|
    -                      new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    -                      # Reassign `node` in case the child hook makes a modification
    -                      if new_child_and_node.is_a?(Array)
    -                        new_node = new_child_and_node[1]
    -                      end
    -                    end"
    -                  end.join("\n")
    -                else
    -                  ""
    -                end
    -              }
    -
    -              if new_node.equal?(node)
    -                [node, parent]
    -              else
    -                [new_node, parent]
    -              end
    -            end
    -          end
    -
    -          def #{node_method}_with_modifications(node, parent)
    -            new_node_and_new_parent = #{node_method}(node, parent)
    -            apply_modifications(node, parent, new_node_and_new_parent)
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             new_node = document_node
             document_node.children.each do |child_node|
    @@ -216,6 +161,63 @@ def on_argument_children(new_node)
             new_node
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      # We don't use `alias` here because it breaks `super`
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    +          def #{node_method}(node, parent)
    +            if node.equal?(DELETE_NODE)
    +              # This might be passed to `super(DELETE_NODE, ...)`
    +              # by a user hook, don't want to keep visiting in that case.
    +              [node, parent]
    +            else
    +              new_node = node
    +              #{
    +                if method_defined?(child_visit_method)
    +                  "new_node = #{child_visit_method}(new_node)"
    +                elsif children_of_type
    +                  children_of_type.map do |child_accessor, child_class|
    +                    "node.#{child_accessor}.each do |child_node|
    +                      new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    +                      # Reassign `node` in case the child hook makes a modification
    +                      if new_child_and_node.is_a?(Array)
    +                        new_node = new_child_and_node[1]
    +                      end
    +                    end"
    +                  end.join("\n")
    +                else
    +                  ""
    +                end
    +              }
    +
    +              if new_node.equal?(node)
    +                [node, parent]
    +              else
    +                [new_node, parent]
    +              end
    +            end
    +          end
    +
    +          def #{node_method}_with_modifications(node, parent)
    +            new_node_and_new_parent = #{node_method}(node, parent)
    +            apply_modifications(node, parent, new_node_and_new_parent)
    +          end
    +        RUBY
    +      end
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -256,6 +258,8 @@ def on_argument_children(new_node)
             make_visit_methods(ast_node_class)
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           private
     
           def apply_modifications(node, parent, new_node_and_new_parent)
    
  • lib/graphql/schema/argument.rb+3 5 modified
    @@ -53,6 +53,7 @@ def from_resolver?
           def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
             arg_name ||= name
             @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
    +        NameValidator.validate!(@name)
             @type_expr = type_expr || type
             @description = desc || description
             @null = required != true
    @@ -88,11 +89,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type
             end
     
             if definition_block
    -          if definition_block.arity == 1
    -            instance_exec(self, &definition_block)
    -          else
    -            instance_eval(&definition_block)
    -          end
    +          # `self` will still be self, it will also be the first argument to the block:
    +          instance_exec(self, &definition_block)
             end
           end
     
    
  • lib/graphql/schema/build_from_definition.rb+8 7 modified
    @@ -451,17 +451,18 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
     
                 # Don't do this for interfaces
                 if default_resolve
    -              owner.class_eval <<-RUBY, __FILE__, __LINE__
    -                # frozen_string_literal: true
    -                def #{resolve_method_name}(**args)
    -                  field_instance = self.class.get_field("#{field_definition.name}")
    -                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    -                end
    -              RUBY
    +              define_field_resolve_method(owner, resolve_method_name, field_definition.name)
                 end
               end
             end
     
    +        def define_field_resolve_method(owner, method_name, field_name)
    +          owner.define_method(method_name) { |**args|
    +            field_instance = self.class.get_field(field_name)
    +            context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    +          }
    +        end
    +
             def build_resolve_type(lookup_hash, directives, missing_type_handler)
               resolve_type_proc = nil
               resolve_type_proc = ->(ast_node) {
    
  • lib/graphql/schema/directive.rb+1 1 modified
    @@ -99,7 +99,7 @@ def repeatable(new_value)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @default_graphql_name ||= nil
               end
             end
    
  • lib/graphql/schema/enum_value.rb+1 1 modified
    @@ -47,7 +47,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil,
             end
     
             if block_given?
    -          instance_eval(&block)
    +          instance_exec(self, &block)
             end
           end
     
    
  • lib/graphql/schema/field.rb+1 1 modified
    @@ -233,7 +233,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CON
     
             @underscored_name = -Member::BuildType.underscore(name_s)
             @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
    -
    +        NameValidator.validate!(@name)
             @description = description
             @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
     
    
  • lib/graphql/schema/input_object.rb+21 6 modified
    @@ -44,6 +44,19 @@ def to_hash
             to_h
           end
     
    +<<<<<<< HEAD
    +=======
    +      def deconstruct_keys(keys = nil)
    +        if keys.nil?
    +          @ruby_style_hash
    +        else
    +          new_h = {}
    +          keys.each { |k| @ruby_style_hash.key?(k) && new_h[k] = @ruby_style_hash[k] }
    +          new_h
    +        end
    +      end
    +
    +>>>>>>> d85e69690f (Disable development NoEvalCop for load-time eval calls)
           def prepare
             if @context
               object = @context[:current_object]
    @@ -131,12 +144,7 @@ def argument(*args, **kwargs, &block)
                 end
               end
               # Add a method access
    -          method_name = argument_defn.keyword
    -          class_eval <<-RUBY, __FILE__, __LINE__
    -            def #{method_name}
    -              self[#{method_name.inspect}]
    -            end
    -          RUBY
    +          define_accessor_method(argument_defn.keyword)
               argument_defn
             end
     
    @@ -242,6 +250,13 @@ def coerce_result(value, ctx)
     
               result
             end
    +
    +        private
    +
    +        def define_accessor_method(method_name)
    +          define_method(method_name) { self[method_name] }
    +          alias_method(method_name, method_name)
    +        end
           end
     
           private
    
  • lib/graphql/schema/interface.rb+1 1 modified
    @@ -29,7 +29,7 @@ def definition_methods(&block)
                 const_set(:DefinitionMethods, defn_methods_module)
                 extend(self::DefinitionMethods)
               end
    -          self::DefinitionMethods.module_eval(&block)
    +          self::DefinitionMethods.module_exec(&block)
             end
     
             # @see {Schema::Warden} hides interfaces without visible implementations
    
  • lib/graphql/schema/member/has_directives.rb+1 1 modified
    @@ -6,7 +6,7 @@ class Member
           module HasDirectives
             def self.extended(child_cls)
               super
    -          child_cls.module_eval { self.own_directives = nil }
    +          child_cls.module_exec { self.own_directives = nil }
             end
     
             def inherited(child_cls)
    
  • lib/graphql/schema/member/has_fields.rb+1 1 modified
    @@ -183,7 +183,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_fields ||= nil
                 @field_class ||= nil
               end
    
  • lib/graphql/schema/member/has_interfaces.rb+1 1 modified
    @@ -133,7 +133,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_interface_type_memberships ||= nil
               end
             end
    
  • lib/graphql/schema/member/scoped.rb+1 1 modified
    @@ -30,7 +30,7 @@ def reauthorize_scoped_objects(new_value = nil)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @reauthorize_scoped_objects = nil
               end
             end
    
  • lib/graphql/schema/member/type_system_helpers.rb+1 1 modified
    @@ -43,7 +43,7 @@ def kind
             private
     
             def inherited(subclass)
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @to_non_null_type ||= nil
                 @to_list_type ||= nil
               end
    
  • lib/graphql/tracing/appoptics_trace.rb+4 0 modified
    @@ -28,6 +28,8 @@ def self.version
             Gem::Version.new('1.0.0')
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           [
             'lex',
             'parse',
    @@ -55,6 +57,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field(query:, field:, ast_node:, arguments:, object:)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/appsignal_trace.rb+4 0 modified
    @@ -13,6 +13,8 @@ def initialize(set_action_name: false, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -43,6 +45,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key)
             Appsignal.instrument(platform_key) do
               yield
    
  • lib/graphql/tracing/data_dog_trace.rb+4 0 modified
    @@ -20,6 +20,8 @@ def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => 'lex.graphql',
             'parse' => 'parse.graphql',
    @@ -69,6 +71,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field_span(span_key, query, field, ast_node, arguments, object)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/notifications_trace.rb+4 0 modified
    @@ -16,6 +16,8 @@ def initialize(engine:, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -39,6 +41,8 @@ def #{trace_method}(**metadata, &blk)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           include PlatformTrace
         end
       end
    
  • lib/graphql/tracing/platform_trace.rb+5 0 modified
    @@ -39,6 +39,9 @@ def self.included(child_class)
               include(BaseKeyCache)
             }
             child_class.const_set(:KeyCache, key_methods_class)
    +
    +        # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
             [:execute_field, :execute_field_lazy].each do |field_trace_method|
               if !child_class.method_defined?(field_trace_method)
                 child_class.module_eval <<-RUBY, __FILE__, __LINE__
    @@ -91,6 +94,8 @@ def #{rt_trace_method}(query:, type:, object:)
                   end
                 RUBY
               end
    +
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/tracing/prometheus_trace.rb+4 0 modified
    @@ -13,6 +13,8 @@ def initialize(client: PrometheusExporter::Client.default, keys_whitelist: ["exe
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -30,6 +32,8 @@ def #{trace_method}(**data, &block)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             instrument_execution(platform_key, "execute_field", &block)
           end
    
  • lib/graphql/tracing/scout_trace.rb+3 0 modified
    @@ -16,6 +16,8 @@ def initialize(set_transaction_name: false, **_rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -45,6 +47,7 @@ def #{trace_method}(**data)
             end
             RUBY
           end
    +      # rubocop:enable Development/NoEvalCop
     
           def platform_execute_field(platform_key, &block)
             self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS, &block)
    
  • lib/graphql/tracing/statsd_trace.rb+4 0 modified
    @@ -11,6 +11,8 @@ def initialize(statsd:, **rest)
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -30,6 +32,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             @statsd.time(platform_key, &block)
           end
    
  • lib/graphql/types/relay/connection_behaviors.rb+1 1 modified
    @@ -13,7 +13,7 @@ def self.included(child_class)
               child_class.node_nullable(true)
               child_class.edges_nullable(true)
               child_class.edge_nullable(true)
    -          child_class.module_eval {
    +          child_class.module_exec {
                 self.edge_type = nil
                 self.node_type = nil
                 self.edge_class = nil
    
  • lib/graphql/types/relay/edge_behaviors.rb+1 1 modified
    @@ -8,7 +8,7 @@ def self.included(child_class)
               child_class.description("An edge in a connection.")
               child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
               child_class.extend(ClassMethods)
    -          child_class.class_eval { self.node_type = nil }
    +          child_class.class_exec { self.node_type = nil }
               child_class.node_nullable(true)
             end
     
    
  • lib/graphql/version.rb+1 1 modified
    @@ -1,4 +1,4 @@
     # frozen_string_literal: true
     module GraphQL
    -  VERSION = "2.1.13"
    +  VERSION = "2.1.14"
     end
    
  • .rubocop.yml+5 0 modified
    @@ -1,5 +1,6 @@
     require:
       - ./cop/development/none_without_block_cop
    +  - ./cop/development/no_eval_cop
       - ./cop/development/no_focus_cop
       - ./lib/graphql/rubocop/graphql/default_null_true
       - ./lib/graphql/rubocop/graphql/default_required_true
    @@ -51,6 +52,10 @@ Development/NoneWithoutBlockCop:
         - "lib/**/*"
         - "spec/**/*"
     
    +Development/NoEvalCop:
    +  Include:
    +    - "lib/**/*"
    +
     Development/NoFocusCop:
       Include:
         - "spec/**/*"
    
  • spec/graphql/schema/loader_spec.rb+149 0 modified
    @@ -398,4 +398,153 @@ def assert_deep_equal(expected_type, actual_type)
           assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
         end
       end
    +
    +  it "validates field argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "something-wrong",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "SCALAR",
    +                        "name" => "Int",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "something-wrong"
    +  end
    +
    +  it "validates field names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "bad.int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad.int"
    +  end
    +
    +  it "validates input object argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "inputObject",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "INPUT_OBJECT",
    +                        "name" => "SomeInputObject",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            },
    +            {
    +              "kind" => "INPUT_OBJECT",
    +              "name" => "SomeInputObject",
    +              "description" => nil,
    +              "inputFields" => [
    +                {
    +                  "name"=>"bad, input",
    +                  "type"=> { "kind" => "SCALAR", "name" => "String"},
    +                  "defaultValue"=> nil,
    +                  "description" => nil,
    +                },
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad, input"
    +  end
     end
    
e3b33ace0539

Merge commit from fork

https://github.com/rmosolgo/graphql-rubyRobert MosolgoMar 12, 2025via ghsa
33 files changed · +348 123
  • benchmark/run.rb+7 2 modified
    @@ -110,12 +110,16 @@ def self.build_large_schema
             end
     
             obj_ts = 100.times.map do |n|
    +          input_obj_t = Class.new(GraphQL::Schema::InputObject) do
    +            graphql_name("Input#{n}")
    +            argument :arg, String
    +          end
               obj_t = Class.new(GraphQL::Schema::Object) do
                 graphql_name("Object#{n}")
                 implements(*int_ts)
                 20.times do |n2|
                   field :"field#{n2}", String do
    -                argument :arg, String
    +                argument :input, input_obj_t
                   end
     
                 end
    @@ -152,8 +156,9 @@ def self.profile_boot
         end
         StackProf::Report.new(result).print_text
     
    +    retained_schema = nil
         report = MemoryProfiler.report do
    -      build_large_schema
    +      retained_schema = build_large_schema
         end
     
         report.pretty_print
    
  • cop/development/no_eval_cop.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'rubocop'
    +
    +module Cop
    +  module Development
    +    class NoEvalCop < RuboCop::Cop::Base
    +      MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accepts strings and may result evaluating unexpected code. Use `%{exec_method_name}` instead, and pass a block."
    +
    +      def on_send(node)
    +        case node.method_name
    +        when :module_eval, :class_eval, :instance_eval
    +          message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
    +          add_offense node, message: message
    +        end
    +      end
    +    end
    +  end
    +end
    
  • lib/graphql/analysis/analyzer.rb+2 1 modified
    @@ -42,6 +42,7 @@ def result
             raise GraphQL::RequiredImplementationMissingError
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
           class << self
             private
     
    @@ -72,7 +73,7 @@ def on_leave_#{member_name}(node, parent, visitor)
           build_visitor_hooks :variable_definition
           build_visitor_hooks :variable_identifier
           build_visitor_hooks :abstract_node
    -
    +      # rubocop:enable Development/NoEvalCop
           protected
     
           # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing
    
  • lib/graphql/analysis/visitor.rb+2 0 modified
    @@ -64,6 +64,7 @@ def response_path
             @response_path.dup
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
           # Visitor Hooks
           [
             :operation_definition, :fragment_definition,
    @@ -92,6 +93,7 @@ def call_on_leave_#{node_type}(node, parent)
     
             RUBY
           end
    +      # rubocop:enable Development/NoEvalCop
     
           def on_operation_definition(node, parent)
             object_type = @schema.root_type_for_operation(node.operation_type)
    
  • lib/graphql/language/nodes.rb+3 0 modified
    @@ -141,6 +141,8 @@ def merge!(new_options)
             end
     
             class << self
    +          # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
               # Add a default `#visit_method` and `#children_method_name` using the class name
               def inherited(child_class)
                 super
    @@ -343,6 +345,7 @@ def marshal_load(values)
                   RUBY
                 end
               end
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/language/static_visitor.rb+37 33 modified
    @@ -22,39 +22,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [void]
    -          def #{node_method}(node, parent)
    -            #{
    -              if method_defined?(child_visit_method)
    -                "#{child_visit_method}(node)"
    -              elsif children_of_type
    -                children_of_type.map do |child_accessor, child_class|
    -                  "node.#{child_accessor}.each do |child_node|
    -                    #{child_class.visit_method}(child_node, node)
    -                  end"
    -                end.join("\n")
    -              else
    -                ""
    -              end
    -            }
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             document_node.children.each do |child_node|
               visit_method = child_node.visit_method
    @@ -123,6 +90,41 @@ def on_argument_children(new_node)
             end
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      # We don't use `alias` here because it breaks `super`
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [void]
    +          def #{node_method}(node, parent)
    +            #{
    +              if method_defined?(child_visit_method)
    +                "#{child_visit_method}(node)"
    +              elsif children_of_type
    +                children_of_type.map do |child_accessor, child_class|
    +                  "node.#{child_accessor}.each do |child_node|
    +                    #{child_class.visit_method}(child_node, node)
    +                  end"
    +                end.join("\n")
    +              else
    +                ""
    +              end
    +            }
    +          end
    +        RUBY
    +      end
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -162,6 +164,8 @@ def on_argument_children(new_node)
           ].each do |ast_node_class|
             make_visit_methods(ast_node_class)
           end
    +
    +      # rubocop:disable Development/NoEvalCop
         end
       end
     end
    
  • lib/graphql/language/visitor.rb+59 55 modified
    @@ -61,61 +61,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    -          def #{node_method}(node, parent)
    -            if node.equal?(DELETE_NODE)
    -              # This might be passed to `super(DELETE_NODE, ...)`
    -              # by a user hook, don't want to keep visiting in that case.
    -              [node, parent]
    -            else
    -              new_node = node
    -              #{
    -                if method_defined?(child_visit_method)
    -                  "new_node = #{child_visit_method}(new_node)"
    -                elsif children_of_type
    -                  children_of_type.map do |child_accessor, child_class|
    -                    "node.#{child_accessor}.each do |child_node|
    -                      new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    -                      # Reassign `node` in case the child hook makes a modification
    -                      if new_child_and_node.is_a?(Array)
    -                        new_node = new_child_and_node[1]
    -                      end
    -                    end"
    -                  end.join("\n")
    -                else
    -                  ""
    -                end
    -              }
    -
    -              if new_node.equal?(node)
    -                [node, parent]
    -              else
    -                [new_node, parent]
    -              end
    -            end
    -          end
    -
    -          def #{node_method}_with_modifications(node, parent)
    -            new_node_and_new_parent = #{node_method}(node, parent)
    -            apply_modifications(node, parent, new_node_and_new_parent)
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             new_node = document_node
             document_node.children.each do |child_node|
    @@ -216,6 +161,63 @@ def on_argument_children(new_node)
             new_node
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      # We don't use `alias` here because it breaks `super`
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    +          def #{node_method}(node, parent)
    +            if node.equal?(DELETE_NODE)
    +              # This might be passed to `super(DELETE_NODE, ...)`
    +              # by a user hook, don't want to keep visiting in that case.
    +              [node, parent]
    +            else
    +              new_node = node
    +              #{
    +                if method_defined?(child_visit_method)
    +                  "new_node = #{child_visit_method}(new_node)"
    +                elsif children_of_type
    +                  children_of_type.map do |child_accessor, child_class|
    +                    "node.#{child_accessor}.each do |child_node|
    +                      new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    +                      # Reassign `node` in case the child hook makes a modification
    +                      if new_child_and_node.is_a?(Array)
    +                        new_node = new_child_and_node[1]
    +                      end
    +                    end"
    +                  end.join("\n")
    +                else
    +                  ""
    +                end
    +              }
    +
    +              if new_node.equal?(node)
    +                [node, parent]
    +              else
    +                [new_node, parent]
    +              end
    +            end
    +          end
    +
    +          def #{node_method}_with_modifications(node, parent)
    +            new_node_and_new_parent = #{node_method}(node, parent)
    +            apply_modifications(node, parent, new_node_and_new_parent)
    +          end
    +        RUBY
    +      end
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -256,6 +258,8 @@ def on_argument_children(new_node)
             make_visit_methods(ast_node_class)
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           private
     
           def apply_modifications(node, parent, new_node_and_new_parent)
    
  • lib/graphql/schema/argument.rb+3 5 modified
    @@ -53,6 +53,7 @@ def from_resolver?
           def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
             arg_name ||= name
             @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
    +        NameValidator.validate!(@name)
             @type_expr = type_expr || type
             @description = desc || description
             @comment = comment
    @@ -89,11 +90,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type
             end
     
             if definition_block
    -          if definition_block.arity == 1
    -            instance_exec(self, &definition_block)
    -          else
    -            instance_eval(&definition_block)
    -          end
    +          # `self` will still be self, it will also be the first argument to the block:
    +          instance_exec(self, &definition_block)
             end
           end
     
    
  • lib/graphql/schema/build_from_definition.rb+8 7 modified
    @@ -467,17 +467,18 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
     
                 # Don't do this for interfaces
                 if default_resolve
    -              owner.class_eval <<-RUBY, __FILE__, __LINE__
    -                # frozen_string_literal: true
    -                def #{resolve_method_name}(**args)
    -                  field_instance = self.class.get_field("#{field_definition.name}")
    -                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    -                end
    -              RUBY
    +              define_field_resolve_method(owner, resolve_method_name, field_definition.name)
                 end
               end
             end
     
    +        def define_field_resolve_method(owner, method_name, field_name)
    +          owner.define_method(method_name) { |**args|
    +            field_instance = self.class.get_field(field_name)
    +            context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    +          }
    +        end
    +
             def build_resolve_type(lookup_hash, directives, missing_type_handler)
               resolve_type_proc = nil
               resolve_type_proc = ->(ast_node) {
    
  • lib/graphql/schema/directive.rb+1 1 modified
    @@ -99,7 +99,7 @@ def repeatable(new_value)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @default_graphql_name ||= nil
               end
             end
    
  • lib/graphql/schema/enum_value.rb+1 1 modified
    @@ -48,7 +48,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil,
             end
     
             if block_given?
    -          instance_eval(&block)
    +          instance_exec(self, &block)
             end
           end
     
    
  • lib/graphql/schema/field.rb+2 2 modified
    @@ -255,7 +255,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CON
     
             @underscored_name = -Member::BuildType.underscore(name_s)
             @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
    -
    +        NameValidator.validate!(@name)
             @description = description
             @comment = comment
             @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
    @@ -369,7 +369,7 @@ def ensure_loaded
               if @definition_block.arity == 1
                 @definition_block.call(self)
               else
    -            instance_eval(&@definition_block)
    +            instance_exec(self, &@definition_block)
               end
               self.extensions.each(&:after_define_apply)
               @call_after_define = true
    
  • lib/graphql/schema/input_object.rb+6 7 modified
    @@ -132,14 +132,8 @@ def argument(*args, **kwargs, &block)
                 end
               end
               # Add a method access
    -          method_name = argument_defn.keyword
               suppress_redefinition_warning do
    -            class_eval <<-RUBY, __FILE__, __LINE__
    -              def #{method_name}
    -                self[#{method_name.inspect}]
    -              end
    -              alias_method :#{method_name}, :#{method_name}
    -            RUBY
    +            define_accessor_method(argument_defn.keyword)
               end
               argument_defn
             end
    @@ -256,6 +250,11 @@ def suppress_redefinition_warning
             ensure
               $VERBOSE = verbose
             end
    +
    +        def define_accessor_method(method_name)
    +          define_method(method_name) { self[method_name] }
    +          alias_method(method_name, method_name)
    +        end
           end
     
           private
    
  • lib/graphql/schema/interface.rb+1 1 modified
    @@ -29,7 +29,7 @@ def definition_methods(&block)
                 const_set(:DefinitionMethods, defn_methods_module)
                 extend(self::DefinitionMethods)
               end
    -          self::DefinitionMethods.module_eval(&block)
    +          self::DefinitionMethods.module_exec(&block)
             end
     
             # @see {Schema::Warden} hides interfaces without visible implementations
    
  • lib/graphql/schema/member/has_directives.rb+1 1 modified
    @@ -6,7 +6,7 @@ class Member
           module HasDirectives
             def self.extended(child_cls)
               super
    -          child_cls.module_eval { self.own_directives = nil }
    +          child_cls.module_exec { self.own_directives = nil }
             end
     
             def inherited(child_cls)
    
  • lib/graphql/schema/member/has_fields.rb+1 1 modified
    @@ -185,7 +185,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_fields ||= nil
                 @field_class ||= nil
               end
    
  • lib/graphql/schema/member/has_interfaces.rb+1 1 modified
    @@ -133,7 +133,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_interface_type_memberships ||= nil
               end
             end
    
  • lib/graphql/schema/member/scoped.rb+1 1 modified
    @@ -30,7 +30,7 @@ def reauthorize_scoped_objects(new_value = nil)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @reauthorize_scoped_objects = nil
               end
             end
    
  • lib/graphql/schema/member/type_system_helpers.rb+1 1 modified
    @@ -42,7 +42,7 @@ def kind
             private
     
             def inherited(subclass)
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @to_non_null_type ||= nil
                 @to_list_type ||= nil
               end
    
  • lib/graphql/tracing/appoptics_trace.rb+4 0 modified
    @@ -28,6 +28,8 @@ def self.version
             Gem::Version.new('1.0.0')
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           [
             'lex',
             'parse',
    @@ -55,6 +57,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field(query:, field:, ast_node:, arguments:, object:)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/appsignal_trace.rb+4 0 modified
    @@ -13,6 +13,8 @@ def initialize(set_action_name: false, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -43,6 +45,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key)
             Appsignal.instrument(platform_key) do
               yield
    
  • lib/graphql/tracing/data_dog_trace.rb+4 0 modified
    @@ -20,6 +20,8 @@ def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => 'lex.graphql',
             'parse' => 'parse.graphql',
    @@ -69,6 +71,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field_span(span_key, query, field, ast_node, arguments, object)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/notifications_trace.rb+4 0 modified
    @@ -16,6 +16,8 @@ def initialize(engine:, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -39,6 +41,8 @@ def #{trace_method}(**metadata, &block)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           include PlatformTrace
         end
       end
    
  • lib/graphql/tracing/platform_trace.rb+5 0 modified
    @@ -39,6 +39,9 @@ def self.included(child_class)
               include(BaseKeyCache)
             }
             child_class.const_set(:KeyCache, key_methods_class)
    +
    +        # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
             [:execute_field, :execute_field_lazy].each do |field_trace_method|
               if !child_class.method_defined?(field_trace_method)
                 child_class.module_eval <<-RUBY, __FILE__, __LINE__
    @@ -91,6 +94,8 @@ def #{rt_trace_method}(query:, type:, object:)
                   end
                 RUBY
               end
    +
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/tracing/prometheus_trace.rb+4 0 modified
    @@ -13,6 +13,8 @@ def initialize(client: PrometheusExporter::Client.default, keys_whitelist: ["exe
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -30,6 +32,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             instrument_prometheus_execution(platform_key, "execute_field", &block)
           end
    
  • lib/graphql/tracing/scout_trace.rb+3 0 modified
    @@ -16,6 +16,8 @@ def initialize(set_transaction_name: false, **_rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -45,6 +47,7 @@ def #{trace_method}(**data)
             end
             RUBY
           end
    +      # rubocop:enable Development/NoEvalCop
     
           def platform_execute_field(platform_key, &block)
             self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS, &block)
    
  • lib/graphql/tracing/sentry_trace.rb+4 0 modified
    @@ -23,6 +23,8 @@ def execute_query(**data)
             instrument_sentry_execution("graphql.execute", "execute_query", data) { super }
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "graphql.lex",
             "parse" => "graphql.parse",
    @@ -39,6 +41,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             instrument_sentry_execution(platform_key, "execute_field", &block)
           end
    
  • lib/graphql/tracing/statsd_trace.rb+4 0 modified
    @@ -11,6 +11,8 @@ def initialize(statsd:, **rest)
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -30,6 +32,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             @statsd.time(platform_key, &block)
           end
    
  • lib/graphql/types/relay/connection_behaviors.rb+1 1 modified
    @@ -13,7 +13,7 @@ def self.included(child_class)
               child_class.node_nullable(true)
               child_class.edges_nullable(true)
               child_class.edge_nullable(true)
    -          child_class.module_eval {
    +          child_class.module_exec {
                 self.edge_type = nil
                 self.node_type = nil
                 self.edge_class = nil
    
  • lib/graphql/types/relay/edge_behaviors.rb+1 1 modified
    @@ -8,7 +8,7 @@ def self.included(child_class)
               child_class.description("An edge in a connection.")
               child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
               child_class.extend(ClassMethods)
    -          child_class.class_eval { self.node_type = nil }
    +          child_class.class_exec { self.node_type = nil }
               child_class.node_nullable(true)
               child_class.default_broadcastable(nil)
             end
    
  • lib/graphql/version.rb+1 1 modified
    @@ -1,4 +1,4 @@
     # frozen_string_literal: true
     module GraphQL
    -  VERSION = "2.3.20"
    +  VERSION = "2.3.21"
     end
    
  • .rubocop.yml+5 0 modified
    @@ -1,5 +1,6 @@
     require:
       - ./cop/development/none_without_block_cop
    +  - ./cop/development/no_eval_cop
       - ./cop/development/no_focus_cop
       - ./lib/graphql/rubocop/graphql/default_null_true
       - ./lib/graphql/rubocop/graphql/default_required_true
    @@ -51,6 +52,10 @@ Development/NoneWithoutBlockCop:
         - "lib/**/*"
         - "spec/**/*"
     
    +Development/NoEvalCop:
    +  Include:
    +    - "lib/**/*"
    +
     Development/NoFocusCop:
       Include:
         - "spec/**/*"
    
  • spec/graphql/schema/loader_spec.rb+149 0 modified
    @@ -405,4 +405,153 @@ def assert_deep_equal(expected_type, actual_type)
           assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
         end
       end
    +
    +  it "validates field argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "something-wrong",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "SCALAR",
    +                        "name" => "Int",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "something-wrong"
    +  end
    +
    +  it "validates field names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "bad.int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad.int"
    +  end
    +
    +  it "validates input object argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "inputObject",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "INPUT_OBJECT",
    +                        "name" => "SomeInputObject",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            },
    +            {
    +              "kind" => "INPUT_OBJECT",
    +              "name" => "SomeInputObject",
    +              "description" => nil,
    +              "inputFields" => [
    +                {
    +                  "name"=>"bad, input",
    +                  "type"=> { "kind" => "SCALAR", "name" => "String"},
    +                  "defaultValue"=> nil,
    +                  "description" => nil,
    +                },
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad, input"
    +  end
     end
    
e58676c70aa6

Merge commit from fork

https://github.com/rmosolgo/graphql-rubyRobert MosolgoMar 12, 2025via ghsa
33 files changed · +350 125
  • benchmark/run.rb+7 2 modified
    @@ -112,12 +112,16 @@ def self.build_large_schema
             end
     
             obj_ts = 100.times.map do |n|
    +          input_obj_t = Class.new(GraphQL::Schema::InputObject) do
    +            graphql_name("Input#{n}")
    +            argument :arg, String
    +          end
               obj_t = Class.new(GraphQL::Schema::Object) do
                 graphql_name("Object#{n}")
                 implements(*int_ts)
                 20.times do |n2|
                   field :"field#{n2}", String do
    -                argument :arg, String
    +                argument :input, input_obj_t
                   end
     
                 end
    @@ -154,8 +158,9 @@ def self.profile_boot
         end
         StackProf::Report.new(result).print_text
     
    +    retained_schema = nil
         report = MemoryProfiler.report do
    -      build_large_schema
    +      retained_schema = build_large_schema
         end
     
         report.pretty_print
    
  • cop/development/no_eval_cop.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'rubocop'
    +
    +module Cop
    +  module Development
    +    class NoEvalCop < RuboCop::Cop::Base
    +      MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accepts strings and may result evaluating unexpected code. Use `%{exec_method_name}` instead, and pass a block."
    +
    +      def on_send(node)
    +        case node.method_name
    +        when :module_eval, :class_eval, :instance_eval
    +          message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
    +          add_offense node, message: message
    +        end
    +      end
    +    end
    +  end
    +end
    
  • lib/graphql/analysis/analyzer.rb+2 1 modified
    @@ -42,6 +42,7 @@ def result
             raise GraphQL::RequiredImplementationMissingError
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
           class << self
             private
     
    @@ -72,7 +73,7 @@ def on_leave_#{member_name}(node, parent, visitor)
           build_visitor_hooks :variable_definition
           build_visitor_hooks :variable_identifier
           build_visitor_hooks :abstract_node
    -
    +      # rubocop:enable Development/NoEvalCop
           protected
     
           # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing
    
  • lib/graphql/analysis/visitor.rb+2 0 modified
    @@ -69,6 +69,7 @@ def response_path
             @response_path.dup
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
           # Visitor Hooks
           [
             :operation_definition, :fragment_definition,
    @@ -93,6 +94,7 @@ def call_on_leave_#{node_type}(node, parent)
     
             RUBY
           end
    +      # rubocop:enable Development/NoEvalCop
     
           def on_operation_definition(node, parent)
             check_timeout
    
  • lib/graphql/language/nodes.rb+3 0 modified
    @@ -141,6 +141,8 @@ def merge!(new_options)
             end
     
             class << self
    +          # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
               # Add a default `#visit_method` and `#children_method_name` using the class name
               def inherited(child_class)
                 super
    @@ -343,6 +345,7 @@ def marshal_load(values)
                   RUBY
                 end
               end
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/language/static_visitor.rb+37 33 modified
    @@ -22,39 +22,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [void]
    -          def #{node_method}(node, parent)
    -            #{
    -              if method_defined?(child_visit_method)
    -                "#{child_visit_method}(node)"
    -              elsif children_of_type
    -                children_of_type.map do |child_accessor, child_class|
    -                  "node.#{child_accessor}.each do |child_node|
    -                    #{child_class.visit_method}(child_node, node)
    -                  end"
    -                end.join("\n")
    -              else
    -                ""
    -              end
    -            }
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             document_node.children.each do |child_node|
               visit_method = child_node.visit_method
    @@ -123,6 +90,41 @@ def on_argument_children(new_node)
             end
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      # We don't use `alias` here because it breaks `super`
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [void]
    +          def #{node_method}(node, parent)
    +            #{
    +              if method_defined?(child_visit_method)
    +                "#{child_visit_method}(node)"
    +              elsif children_of_type
    +                children_of_type.map do |child_accessor, child_class|
    +                  "node.#{child_accessor}.each do |child_node|
    +                    #{child_class.visit_method}(child_node, node)
    +                  end"
    +                end.join("\n")
    +              else
    +                ""
    +              end
    +            }
    +          end
    +        RUBY
    +      end
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -162,6 +164,8 @@ def on_argument_children(new_node)
           ].each do |ast_node_class|
             make_visit_methods(ast_node_class)
           end
    +
    +      # rubocop:disable Development/NoEvalCop
         end
       end
     end
    
  • lib/graphql/language/visitor.rb+59 55 modified
    @@ -61,61 +61,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    -          def #{node_method}(node, parent)
    -            if node.equal?(DELETE_NODE)
    -              # This might be passed to `super(DELETE_NODE, ...)`
    -              # by a user hook, don't want to keep visiting in that case.
    -              [node, parent]
    -            else
    -              new_node = node
    -              #{
    -                if method_defined?(child_visit_method)
    -                  "new_node = #{child_visit_method}(new_node)"
    -                elsif children_of_type
    -                  children_of_type.map do |child_accessor, child_class|
    -                    "node.#{child_accessor}.each do |child_node|
    -                      new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    -                      # Reassign `node` in case the child hook makes a modification
    -                      if new_child_and_node.is_a?(Array)
    -                        new_node = new_child_and_node[1]
    -                      end
    -                    end"
    -                  end.join("\n")
    -                else
    -                  ""
    -                end
    -              }
    -
    -              if new_node.equal?(node)
    -                [node, parent]
    -              else
    -                [new_node, parent]
    -              end
    -            end
    -          end
    -
    -          def #{node_method}_with_modifications(node, parent)
    -            new_node_and_new_parent = #{node_method}(node, parent)
    -            apply_modifications(node, parent, new_node_and_new_parent)
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             new_node = document_node
             document_node.children.each do |child_node|
    @@ -216,6 +161,63 @@ def on_argument_children(new_node)
             new_node
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      # We don't use `alias` here because it breaks `super`
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    +          def #{node_method}(node, parent)
    +            if node.equal?(DELETE_NODE)
    +              # This might be passed to `super(DELETE_NODE, ...)`
    +              # by a user hook, don't want to keep visiting in that case.
    +              [node, parent]
    +            else
    +              new_node = node
    +              #{
    +                if method_defined?(child_visit_method)
    +                  "new_node = #{child_visit_method}(new_node)"
    +                elsif children_of_type
    +                  children_of_type.map do |child_accessor, child_class|
    +                    "node.#{child_accessor}.each do |child_node|
    +                      new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    +                      # Reassign `node` in case the child hook makes a modification
    +                      if new_child_and_node.is_a?(Array)
    +                        new_node = new_child_and_node[1]
    +                      end
    +                    end"
    +                  end.join("\n")
    +                else
    +                  ""
    +                end
    +              }
    +
    +              if new_node.equal?(node)
    +                [node, parent]
    +              else
    +                [new_node, parent]
    +              end
    +            end
    +          end
    +
    +          def #{node_method}_with_modifications(node, parent)
    +            new_node_and_new_parent = #{node_method}(node, parent)
    +            apply_modifications(node, parent, new_node_and_new_parent)
    +          end
    +        RUBY
    +      end
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -256,6 +258,8 @@ def on_argument_children(new_node)
             make_visit_methods(ast_node_class)
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           private
     
           def apply_modifications(node, parent, new_node_and_new_parent)
    
  • lib/graphql/schema/argument.rb+3 5 modified
    @@ -53,6 +53,7 @@ def from_resolver?
           def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
             arg_name ||= name
             @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
    +        NameValidator.validate!(@name)
             @type_expr = type_expr || type
             @description = desc || description
             @comment = comment
    @@ -89,11 +90,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type
             end
     
             if definition_block
    -          if definition_block.arity == 1
    -            instance_exec(self, &definition_block)
    -          else
    -            instance_eval(&definition_block)
    -          end
    +          # `self` will still be self, it will also be the first argument to the block:
    +          instance_exec(self, &definition_block)
             end
           end
     
    
  • lib/graphql/schema/build_from_definition.rb+8 7 modified
    @@ -467,17 +467,18 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
     
                 # Don't do this for interfaces
                 if default_resolve
    -              owner.class_eval <<-RUBY, __FILE__, __LINE__
    -                # frozen_string_literal: true
    -                def #{resolve_method_name}(**args)
    -                  field_instance = self.class.get_field("#{field_definition.name}")
    -                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    -                end
    -              RUBY
    +              define_field_resolve_method(owner, resolve_method_name, field_definition.name)
                 end
               end
             end
     
    +        def define_field_resolve_method(owner, method_name, field_name)
    +          owner.define_method(method_name) { |**args|
    +            field_instance = self.class.get_field(field_name)
    +            context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    +          }
    +        end
    +
             def build_resolve_type(lookup_hash, directives, missing_type_handler)
               resolve_type_proc = nil
               resolve_type_proc = ->(ast_node) {
    
  • lib/graphql/schema/directive.rb+1 1 modified
    @@ -99,7 +99,7 @@ def repeatable(new_value)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @default_graphql_name ||= nil
               end
             end
    
  • lib/graphql/schema/enum.rb+2 2 modified
    @@ -234,7 +234,7 @@ def inherited(child_class)
                 # because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
                 child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
               end
    -          child_class.class_eval { @value_methods = nil }
    +          child_class.class_exec { @value_methods = nil }
               super
             end
     
    @@ -256,7 +256,7 @@ def generate_value_method(value, configured_value_method)
                 return
               end
     
    -          instance_eval("def #{value_method_name}; #{value.graphql_name.inspect}; end;", __FILE__, __LINE__)
    +          define_singleton_method(value_method_name) { value.graphql_name }
             end
           end
     
    
  • lib/graphql/schema/enum_value.rb+1 1 modified
    @@ -48,7 +48,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil,
             end
     
             if block_given?
    -          instance_eval(&block)
    +          instance_exec(self, &block)
             end
           end
     
    
  • lib/graphql/schema/field.rb+2 2 modified
    @@ -255,7 +255,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CON
     
             @underscored_name = -Member::BuildType.underscore(name_s)
             @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
    -
    +        NameValidator.validate!(@name)
             @description = description
             @comment = comment
             @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
    @@ -369,7 +369,7 @@ def ensure_loaded
               if @definition_block.arity == 1
                 @definition_block.call(self)
               else
    -            instance_eval(&@definition_block)
    +            instance_exec(self, &@definition_block)
               end
               self.extensions.each(&:after_define_apply)
               @call_after_define = true
    
  • lib/graphql/schema/input_object.rb+7 8 modified
    @@ -59,7 +59,7 @@ def deconstruct_keys(keys = nil)
             else
               new_h = {}
               keys.each { |k| @ruby_style_hash.key?(k) && new_h[k] = @ruby_style_hash[k] }
    -          new_h 
    +          new_h
             end
           end
     
    @@ -150,14 +150,8 @@ def argument(*args, **kwargs, &block)
                 end
               end
               # Add a method access
    -          method_name = argument_defn.keyword
               suppress_redefinition_warning do
    -            class_eval <<-RUBY, __FILE__, __LINE__
    -              def #{method_name}
    -                self[#{method_name.inspect}]
    -              end
    -              alias_method #{method_name.inspect}, #{method_name.inspect}
    -            RUBY
    +            define_accessor_method(argument_defn.keyword)
               end
               argument_defn
             end
    @@ -293,6 +287,11 @@ def suppress_redefinition_warning
             ensure
               $VERBOSE = verbose
             end
    +
    +        def define_accessor_method(method_name)
    +          define_method(method_name) { self[method_name] }
    +          alias_method(method_name, method_name)
    +        end
           end
     
           private
    
  • lib/graphql/schema/interface.rb+1 1 modified
    @@ -30,7 +30,7 @@ def definition_methods(&block)
                 const_set(:DefinitionMethods, defn_methods_module)
                 extend(self::DefinitionMethods)
               end
    -          self::DefinitionMethods.module_eval(&block)
    +          self::DefinitionMethods.module_exec(&block)
             end
     
             # @see {Schema::Warden} hides interfaces without visible implementations
    
  • lib/graphql/schema/member/has_directives.rb+1 1 modified
    @@ -6,7 +6,7 @@ class Member
           module HasDirectives
             def self.extended(child_cls)
               super
    -          child_cls.module_eval { self.own_directives = nil }
    +          child_cls.module_exec { self.own_directives = nil }
             end
     
             def inherited(child_cls)
    
  • lib/graphql/schema/member/has_fields.rb+1 1 modified
    @@ -202,7 +202,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_fields ||= nil
                 @field_class ||= nil
                 @has_no_fields ||= false
    
  • lib/graphql/schema/member/has_interfaces.rb+1 1 modified
    @@ -133,7 +133,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_interface_type_memberships ||= nil
               end
             end
    
  • lib/graphql/schema/member/scoped.rb+1 1 modified
    @@ -30,7 +30,7 @@ def reauthorize_scoped_objects(new_value = nil)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @reauthorize_scoped_objects = nil
               end
             end
    
  • lib/graphql/schema/member/type_system_helpers.rb+1 1 modified
    @@ -42,7 +42,7 @@ def kind
             private
     
             def inherited(subclass)
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @to_non_null_type ||= nil
                 @to_list_type ||= nil
               end
    
  • lib/graphql/tracing/appoptics_trace.rb+4 0 modified
    @@ -32,6 +32,8 @@ def self.version
             Gem::Version.new('1.0.0')
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           [
             'lex',
             'parse',
    @@ -59,6 +61,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field(query:, field:, ast_node:, arguments:, object:)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/appsignal_trace.rb+4 0 modified
    @@ -21,6 +21,8 @@ def initialize(set_action_name: false, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -51,6 +53,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key)
             Appsignal.instrument(platform_key) do
               yield
    
  • lib/graphql/tracing/data_dog_trace.rb+4 0 modified
    @@ -27,6 +27,8 @@ def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => 'lex.graphql',
             'parse' => 'parse.graphql',
    @@ -76,6 +78,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field_span(span_key, query, field, ast_node, arguments, object)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/notifications_trace.rb+4 0 modified
    @@ -16,6 +16,8 @@ def initialize(engine:, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -39,6 +41,8 @@ def #{trace_method}(**metadata, &block)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           include PlatformTrace
         end
       end
    
  • lib/graphql/tracing/platform_trace.rb+5 0 modified
    @@ -39,6 +39,9 @@ def self.included(child_class)
               include(BaseKeyCache)
             }
             child_class.const_set(:KeyCache, key_methods_class)
    +
    +        # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
             [:execute_field, :execute_field_lazy].each do |field_trace_method|
               if !child_class.method_defined?(field_trace_method)
                 child_class.module_eval <<-RUBY, __FILE__, __LINE__
    @@ -91,6 +94,8 @@ def #{rt_trace_method}(query:, type:, object:)
                   end
                 RUBY
               end
    +
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/tracing/prometheus_trace.rb+4 0 modified
    @@ -40,6 +40,8 @@ def initialize(client: PrometheusExporter::Client.default, keys_whitelist: ["exe
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -57,6 +59,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             instrument_prometheus_execution(platform_key, "execute_field", &block)
           end
    
  • lib/graphql/tracing/scout_trace.rb+3 0 modified
    @@ -24,6 +24,8 @@ def initialize(set_transaction_name: false, **_rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -53,6 +55,7 @@ def #{trace_method}(**data)
             end
             RUBY
           end
    +      # rubocop:enable Development/NoEvalCop
     
           def platform_execute_field(platform_key, &block)
             self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS, &block)
    
  • lib/graphql/tracing/sentry_trace.rb+4 0 modified
    @@ -30,6 +30,8 @@ def execute_query(**data)
             instrument_sentry_execution("graphql.execute", "execute_query", data) { super }
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "graphql.lex",
             "parse" => "graphql.parse",
    @@ -46,6 +48,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             instrument_sentry_execution(platform_key, "execute_field", &block)
           end
    
  • lib/graphql/tracing/statsd_trace.rb+4 0 modified
    @@ -22,6 +22,8 @@ def initialize(statsd:, **rest)
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -41,6 +43,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             @statsd.time(platform_key, &block)
           end
    
  • lib/graphql/types/relay/connection_behaviors.rb+1 1 modified
    @@ -13,7 +13,7 @@ def self.included(child_class)
               child_class.node_nullable(true)
               child_class.edges_nullable(true)
               child_class.edge_nullable(true)
    -          child_class.module_eval {
    +          child_class.module_exec {
                 self.edge_type = nil
                 self.node_type = nil
                 self.edge_class = nil
    
  • lib/graphql/types/relay/edge_behaviors.rb+1 1 modified
    @@ -8,7 +8,7 @@ def self.included(child_class)
               child_class.description("An edge in a connection.")
               child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
               child_class.extend(ClassMethods)
    -          child_class.class_eval { self.node_type = nil }
    +          child_class.class_exec { self.node_type = nil }
               child_class.node_nullable(true)
               child_class.default_broadcastable(nil)
             end
    
  • .rubocop.yml+5 0 modified
    @@ -1,5 +1,6 @@
     require:
       - ./cop/development/none_without_block_cop
    +  - ./cop/development/no_eval_cop
       - ./cop/development/no_focus_cop
       - ./lib/graphql/rubocop/graphql/default_null_true
       - ./lib/graphql/rubocop/graphql/default_required_true
    @@ -41,6 +42,10 @@ Development/NoneWithoutBlockCop:
         - "lib/**/*"
         - "spec/**/*"
     
    +Development/NoEvalCop:
    +  Include:
    +    - "lib/**/*"
    +
     Development/NoFocusCop:
       Include:
         - "spec/**/*"
    
  • spec/graphql/schema/loader_spec.rb+149 0 modified
    @@ -408,4 +408,153 @@ def assert_deep_equal(expected_type, actual_type)
           assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
         end
       end
    +
    +  it "validates field argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "something-wrong",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "SCALAR",
    +                        "name" => "Int",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "something-wrong"
    +  end
    +
    +  it "validates field names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "bad.int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad.int"
    +  end
    +
    +  it "validates input object argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "inputObject",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "INPUT_OBJECT",
    +                        "name" => "SomeInputObject",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            },
    +            {
    +              "kind" => "INPUT_OBJECT",
    +              "name" => "SomeInputObject",
    +              "description" => nil,
    +              "inputFields" => [
    +                {
    +                  "name"=>"bad, input",
    +                  "type"=> { "kind" => "SCALAR", "name" => "String"},
    +                  "defaultValue"=> nil,
    +                  "description" => nil,
    +                },
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad, input"
    +  end
     end
    
2d2f4ed1f794

Merge commit from fork

https://github.com/rmosolgo/graphql-rubyRobert MosolgoMar 12, 2025via ghsa
10 files changed · +196 20
  • cop/development/no_eval_cop.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'rubocop'
    +
    +module Cop
    +  module Development
    +    class NoEvalCop < RuboCop::Cop::Base
    +      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."
    +
    +      def on_send(node)
    +        case node.method_name
    +        when :module_eval, :class_eval, :instance_eval
    +          message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
    +          add_offense node, message: message
    +        end
    +      end
    +    end
    +  end
    +end
    
  • lib/graphql/language/nodes.rb+5 0 modified
    @@ -133,6 +133,8 @@ def merge!(new_options)
             end
     
             class << self
    +          # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
               # Add a default `#visit_method` and `#children_method_name` using the class name
               def inherited(child_class)
                 super
    @@ -265,8 +267,11 @@ def initialize_node #{arguments.join(", ")}
                       #{assignments.join("\n")}
                     end
                   RUBY
    +
    +              # rubocop:enable Development/NoEvalCop
                 end
               end
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/language/visitor.rb+1 0 modified
    @@ -73,6 +73,7 @@ def visit
               @document
             end
           end
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
     
           # Call the user-defined handler for `node`.
           def visit_node(node, parent)
    
  • lib/graphql/schema/argument.rb+3 5 modified
    @@ -49,6 +49,7 @@ def from_resolver?
           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)
             arg_name ||= name
             @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
    +        NameValidator.validate!(@name)
             @type_expr = type_expr || type
             @description = desc || description
             @null = !required
    @@ -64,11 +65,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil
             self.deprecation_reason = deprecation_reason
     
             if definition_block
    -          if definition_block.arity == 1
    -            instance_exec(self, &definition_block)
    -          else
    -            instance_eval(&definition_block)
    -          end
    +          # `self` will still be self, it will also be the first argument to the block:
    +          instance_exec(self, &definition_block)
             end
           end
     
    
  • lib/graphql/schema/build_from_definition.rb+8 7 modified
    @@ -342,13 +342,7 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
     
                 # Don't do this for interfaces
                 if default_resolve
    -              owner.class_eval <<-RUBY, __FILE__, __LINE__
    -                # frozen_string_literal: true
    -                def #{resolve_method_name}(**args)
    -                  field_instance = self.class.get_field("#{field_definition.name}")
    -                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    -                end
    -              RUBY
    +              define_field_resolve_method(owner, resolve_method_name, field_definition.name)
                 end
               end
             end
    @@ -367,6 +361,13 @@ def resolve_type(types, ast_node)
               end
             end
     
    +        def define_field_resolve_method(owner, method_name, field_name)
    +          owner.define_method(method_name) { |**args|
    +            field_instance = self.class.get_field(field_name)
    +            context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    +          }
    +        end
    +
             def resolve_type_name(type)
               case type
               when GraphQL::Language::Nodes::TypeName
    
  • lib/graphql/schema/enum_value.rb+1 1 modified
    @@ -49,7 +49,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil
             @ast_node = ast_node
     
             if block_given?
    -          instance_eval(&block)
    +          instance_exec(self, &block)
             end
           end
     
    
  • lib/graphql/schema/field.rb+1 0 modified
    @@ -222,6 +222,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function
             name_s = -name.to_s
             @underscored_name = -Member::BuildType.underscore(name_s)
             @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
    +        NameValidator.validate!(@name)
             @description = description
             if field.is_a?(GraphQL::Schema::Field)
               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+9 6 modified
    @@ -125,12 +125,8 @@ class << self
             def argument(*args, **kwargs, &block)
               argument_defn = super(*args, **kwargs, &block)
               # Add a method access
    -          method_name = argument_defn.keyword
    -          class_eval <<-RUBY, __FILE__, __LINE__
    -            def #{method_name}
    -              self[#{method_name.inspect}]
    -            end
    -          RUBY
    +          define_accessor_method(argument_defn.keyword)
    +          argument_defn
             end
     
             def to_graphql
    @@ -239,6 +235,13 @@ def coerce_result(value, ctx)
     
               result
             end
    +
    +        private
    +
    +        def define_accessor_method(method_name)
    +          define_method(method_name) { self[method_name] }
    +          alias_method(method_name, method_name)
    +        end
           end
         end
       end
    
  • lib/graphql/version.rb+1 1 modified
    @@ -1,4 +1,4 @@
     # frozen_string_literal: true
     module GraphQL
    -  VERSION = "1.11.7"
    +  VERSION = "1.11.8"
     end
    
  • spec/graphql/schema/loader_spec.rb+149 0 modified
    @@ -389,4 +389,153 @@ def assert_deep_equal(expected_type, actual_type)
           assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
         end
       end
    +
    +  it "validates field argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "something-wrong",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "SCALAR",
    +                        "name" => "Int",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "something-wrong"
    +  end
    +
    +  it "validates field names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "bad.int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad.int"
    +  end
    +
    +  it "validates input object argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "inputObject",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "INPUT_OBJECT",
    +                        "name" => "SomeInputObject",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            },
    +            {
    +              "kind" => "INPUT_OBJECT",
    +              "name" => "SomeInputObject",
    +              "description" => nil,
    +              "inputFields" => [
    +                {
    +                  "name"=>"bad, input",
    +                  "type"=> { "kind" => "SCALAR", "name" => "String"},
    +                  "defaultValue"=> nil,
    +                  "description" => nil,
    +                },
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad, input"
    +  end
     end
    
d0963289e0da

Merge commit from fork

https://github.com/rmosolgo/graphql-rubyRobert MosolgoMar 12, 2025via ghsa
27 files changed · +307 91
  • benchmark/run.rb+7 2 modified
    @@ -111,12 +111,16 @@ def self.build_large_schema
             end
     
             obj_ts = 100.times.map do |n|
    +          input_obj_t = Class.new(GraphQL::Schema::InputObject) do
    +            graphql_name("Input#{n}")
    +            argument :arg, String
    +          end
               obj_t = Class.new(GraphQL::Schema::Object) do
                 graphql_name("Object#{n}")
                 implements(*int_ts)
                 20.times do |n2|
                   field :"field#{n2}", String do
    -                argument :arg, String
    +                argument :input, input_obj_t
                   end
     
                 end
    @@ -153,8 +157,9 @@ def self.profile_boot
         end
         StackProf::Report.new(result).print_text
     
    +    retained_schema = nil
         report = MemoryProfiler.report do
    -      build_large_schema
    +      retained_schema = build_large_schema
         end
     
         report.pretty_print
    
  • cop/development/no_eval_cop.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'rubocop'
    +
    +module Cop
    +  module Development
    +    class NoEvalCop < RuboCop::Cop::Base
    +      MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accepts strings and may result evaluating unexpected code. Use `%{exec_method_name}` instead, and pass a block."
    +
    +      def on_send(node)
    +        case node.method_name
    +        when :module_eval, :class_eval, :instance_eval
    +          message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
    +          add_offense node, message: message
    +        end
    +      end
    +    end
    +  end
    +end
    
  • lib/graphql/language/nodes.rb+3 0 modified
    @@ -138,6 +138,8 @@ def merge!(new_options)
             end
     
             class << self
    +          # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
               # Add a default `#visit_method` and `#children_method_name` using the class name
               def inherited(child_class)
                 super
    @@ -296,6 +298,7 @@ def self.from_a(filename, line, col, #{(scalar_method_names + @children_methods.
                   RUBY
                 end
               end
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/language/visitor.rb+64 61 modified
    @@ -76,67 +76,6 @@ def visit
             end
           end
     
    -      # We don't use `alias` here because it breaks `super`
    -      def self.make_visit_methods(ast_node_class)
    -        node_method = ast_node_class.visit_method
    -        children_of_type = ast_node_class.children_of_type
    -        child_visit_method = :"#{node_method}_children"
    -
    -        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    -          # The default implementation for visiting an AST node.
    -          # It doesn't _do_ anything, but it continues to visiting the node's children.
    -          # To customize this hook, override one of its make_visit_methods (or the base method?)
    -          # in your subclasses.
    -          #
    -          # For compatibility, it calls hook procs, too.
    -          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    -          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    -          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    -          def #{node_method}(node, parent)
    -            if node.equal?(DELETE_NODE)
    -              # This might be passed to `super(DELETE_NODE, ...)`
    -              # by a user hook, don't want to keep visiting in that case.
    -              [node, parent]
    -            else
    -              # Run hooks if there are any
    -              new_node = node
    -              no_hooks = !@visitors.key?(node.class)
    -              if no_hooks || begin_visit(new_node, parent)
    -                #{
    -                  if method_defined?(child_visit_method)
    -                    "new_node = #{child_visit_method}(new_node)"
    -                  elsif children_of_type
    -                    children_of_type.map do |child_accessor, child_class|
    -                      "node.#{child_accessor}.each do |child_node|
    -                        new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    -                        # Reassign `node` in case the child hook makes a modification
    -                        if new_child_and_node.is_a?(Array)
    -                          new_node = new_child_and_node[1]
    -                        end
    -                      end"
    -                    end.join("\n")
    -                  else
    -                    ""
    -                  end
    -                }
    -              end
    -              end_visit(new_node, parent) unless no_hooks
    -
    -              if new_node.equal?(node)
    -                [node, parent]
    -              else
    -                [new_node, parent]
    -              end
    -            end
    -          end
    -
    -          def #{node_method}_with_modifications(node, parent)
    -            new_node_and_new_parent = #{node_method}(node, parent)
    -            apply_modifications(node, parent, new_node_and_new_parent)
    -          end
    -        RUBY
    -      end
    -
           def on_document_children(document_node)
             new_node = document_node
             document_node.children.each do |child_node|
    @@ -237,6 +176,68 @@ def on_argument_children(new_node)
             new_node
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
    +      def self.make_visit_methods(ast_node_class)
    +        node_method = ast_node_class.visit_method
    +        children_of_type = ast_node_class.children_of_type
    +        child_visit_method = :"#{node_method}_children"
    +
    +        class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
    +          # The default implementation for visiting an AST node.
    +          # It doesn't _do_ anything, but it continues to visiting the node's children.
    +          # To customize this hook, override one of its make_visit_methods (or the base method?)
    +          # in your subclasses.
    +          #
    +          # For compatibility, it calls hook procs, too.
    +          # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
    +          # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
    +          # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
    +          def #{node_method}(node, parent)
    +            if node.equal?(DELETE_NODE)
    +              # This might be passed to `super(DELETE_NODE, ...)`
    +              # by a user hook, don't want to keep visiting in that case.
    +              [node, parent]
    +            else
    +              # Run hooks if there are any
    +              new_node = node
    +              no_hooks = !@visitors.key?(node.class)
    +              if no_hooks || begin_visit(new_node, parent)
    +                #{
    +                  if method_defined?(child_visit_method)
    +                    "new_node = #{child_visit_method}(new_node)"
    +                  elsif children_of_type
    +                    children_of_type.map do |child_accessor, child_class|
    +                      "node.#{child_accessor}.each do |child_node|
    +                        new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
    +                        # Reassign `node` in case the child hook makes a modification
    +                        if new_child_and_node.is_a?(Array)
    +                          new_node = new_child_and_node[1]
    +                        end
    +                      end"
    +                    end.join("\n")
    +                  else
    +                    ""
    +                  end
    +                }
    +              end
    +              end_visit(new_node, parent) unless no_hooks
    +              if new_node.equal?(node)
    +                [node, parent]
    +              else
    +                [new_node, parent]
    +              end
    +            end
    +          end
    +          def #{node_method}_with_modifications(node, parent)
    +            new_node_and_new_parent = #{node_method}(node, parent)
    +            apply_modifications(node, parent, new_node_and_new_parent)
    +          end
    +        RUBY
    +      end
    +
    +
    +
           [
             Language::Nodes::Argument,
             Language::Nodes::Directive,
    @@ -277,6 +278,8 @@ def on_argument_children(new_node)
             make_visit_methods(ast_node_class)
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           private
     
           def apply_modifications(node, parent, new_node_and_new_parent)
    
  • lib/graphql/schema/argument.rb+3 5 modified
    @@ -53,6 +53,7 @@ def from_resolver?
           def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
             arg_name ||= name
             @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
    +        NameValidator.validate!(@name)
             @type_expr = type_expr || type
             @description = desc || description
             @null = required != true
    @@ -88,11 +89,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type
             end
     
             if definition_block
    -          if definition_block.arity == 1
    -            instance_exec(self, &definition_block)
    -          else
    -            instance_eval(&definition_block)
    -          end
    +          # `self` will still be self, it will also be the first argument to the block:
    +          instance_exec(self, &definition_block)
             end
           end
     
    
  • lib/graphql/schema/build_from_definition.rb+8 7 modified
    @@ -453,17 +453,18 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
     
                 # Don't do this for interfaces
                 if default_resolve
    -              owner.class_eval <<-RUBY, __FILE__, __LINE__
    -                # frozen_string_literal: true
    -                def #{resolve_method_name}(**args)
    -                  field_instance = self.class.get_field("#{field_definition.name}")
    -                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    -                end
    -              RUBY
    +              define_field_resolve_method(owner, resolve_method_name, field_definition.name)
                 end
               end
             end
     
    +        def define_field_resolve_method(owner, method_name, field_name)
    +          owner.define_method(method_name) { |**args|
    +            field_instance = self.class.get_field(field_name)
    +            context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    +          }
    +        end
    +
             def build_resolve_type(lookup_hash, directives, missing_type_handler)
               resolve_type_proc = nil
               resolve_type_proc = ->(ast_node) {
    
  • lib/graphql/schema/directive.rb+1 1 modified
    @@ -99,7 +99,7 @@ def repeatable(new_value)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @default_graphql_name ||= nil
               end
             end
    
  • lib/graphql/schema/enum_value.rb+1 1 modified
    @@ -47,7 +47,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil,
             end
     
             if block_given?
    -          instance_eval(&block)
    +          instance_exec(self, &block)
             end
           end
     
    
  • lib/graphql/schema/field.rb+1 1 modified
    @@ -233,7 +233,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CON
     
             @underscored_name = -Member::BuildType.underscore(name_s)
             @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
    -
    +        NameValidator.validate!(@name)
             @description = description
             @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
     
    
  • lib/graphql/schema/input_object.rb+8 6 modified
    @@ -131,12 +131,7 @@ def argument(*args, **kwargs, &block)
                 end
               end
               # Add a method access
    -          method_name = argument_defn.keyword
    -          class_eval <<-RUBY, __FILE__, __LINE__
    -            def #{method_name}
    -              self[#{method_name.inspect}]
    -            end
    -          RUBY
    +          define_accessor_method(argument_defn.keyword)
               argument_defn
             end
     
    @@ -242,6 +237,13 @@ def coerce_result(value, ctx)
     
               result
             end
    +
    +        private
    +
    +        def define_accessor_method(method_name)
    +          define_method(method_name) { self[method_name] }
    +          alias_method(method_name, method_name)
    +        end
           end
     
           private
    
  • lib/graphql/schema/member/has_directives.rb+1 1 modified
    @@ -6,7 +6,7 @@ class Member
           module HasDirectives
             def self.extended(child_cls)
               super
    -          child_cls.module_eval { self.own_directives = nil }
    +          child_cls.module_exec { self.own_directives = nil }
             end
     
             def inherited(child_cls)
    
  • lib/graphql/schema/member/has_fields.rb+1 1 modified
    @@ -180,7 +180,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_fields ||= nil
                 @field_class ||= nil
               end
    
  • lib/graphql/schema/member/has_interfaces.rb+1 1 modified
    @@ -119,7 +119,7 @@ def self.extended(child_class)
     
             def inherited(subclass)
               super
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @own_interface_type_memberships ||= nil
               end
             end
    
  • lib/graphql/schema/member/type_system_helpers.rb+1 1 modified
    @@ -43,7 +43,7 @@ def kind
             private
     
             def inherited(subclass)
    -          subclass.class_eval do
    +          subclass.class_exec do
                 @to_non_null_type ||= nil
                 @to_list_type ||= nil
               end
    
  • lib/graphql/tracing/appoptics_trace.rb+4 0 modified
    @@ -28,6 +28,8 @@ def self.version
             Gem::Version.new('1.0.0')
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           [
             'lex',
             'parse',
    @@ -55,6 +57,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field(query:, field:, ast_node:, arguments:, object:)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/appsignal_trace.rb+4 0 modified
    @@ -13,6 +13,8 @@ def initialize(set_action_name: false, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -43,6 +45,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key)
             Appsignal.instrument(platform_key) do
               yield
    
  • lib/graphql/tracing/data_dog_trace.rb+4 0 modified
    @@ -20,6 +20,8 @@ def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => 'lex.graphql',
             'parse' => 'parse.graphql',
    @@ -69,6 +71,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def execute_field_span(span_key, query, field, ast_node, arguments, object)
             return_type = field.type.unwrap
             trace_field = if return_type.kind.scalar? || return_type.kind.enum?
    
  • lib/graphql/tracing/notifications_trace.rb+4 0 modified
    @@ -16,6 +16,8 @@ def initialize(engine:, **rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -39,6 +41,8 @@ def #{trace_method}(**metadata, &blk)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           include PlatformTrace
         end
       end
    
  • lib/graphql/tracing/platform_trace.rb+5 0 modified
    @@ -39,6 +39,9 @@ def self.included(child_class)
               include(BaseKeyCache)
             }
             child_class.const_set(:KeyCache, key_methods_class)
    +
    +        # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
             [:execute_field, :execute_field_lazy].each do |field_trace_method|
               if !child_class.method_defined?(field_trace_method)
                 child_class.module_eval <<-RUBY, __FILE__, __LINE__
    @@ -91,6 +94,8 @@ def #{rt_trace_method}(query:, type:, object:)
                   end
                 RUBY
               end
    +
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/tracing/prometheus_trace.rb+4 0 modified
    @@ -13,6 +13,8 @@ def initialize(client: PrometheusExporter::Client.default, keys_whitelist: ["exe
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -30,6 +32,8 @@ def #{trace_method}(**data, &block)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             instrument_execution(platform_key, "execute_field", &block)
           end
    
  • lib/graphql/tracing/scout_trace.rb+3 0 modified
    @@ -16,6 +16,8 @@ def initialize(set_transaction_name: false, **_rest)
             super
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             "lex" => "lex.graphql",
             "parse" => "parse.graphql",
    @@ -45,6 +47,7 @@ def #{trace_method}(**data)
             end
             RUBY
           end
    +      # rubocop:enable Development/NoEvalCop
     
           def platform_execute_field(platform_key, &block)
             self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS, &block)
    
  • lib/graphql/tracing/statsd_trace.rb+4 0 modified
    @@ -11,6 +11,8 @@ def initialize(statsd:, **rest)
             super(**rest)
           end
     
    +      # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
           {
             'lex' => "graphql.lex",
             'parse' => "graphql.parse",
    @@ -30,6 +32,8 @@ def #{trace_method}(**data)
             RUBY
           end
     
    +      # rubocop:enable Development/NoEvalCop
    +
           def platform_execute_field(platform_key, &block)
             @statsd.time(platform_key, &block)
           end
    
  • lib/graphql/types/relay/connection_behaviors.rb+1 1 modified
    @@ -13,7 +13,7 @@ def self.included(child_class)
               child_class.node_nullable(true)
               child_class.edges_nullable(true)
               child_class.edge_nullable(true)
    -          child_class.module_eval {
    +          child_class.module_exec {
                 self.edge_type = nil
                 self.node_type = nil
                 self.edge_class = nil
    
  • lib/graphql/types/relay/edge_behaviors.rb+1 1 modified
    @@ -8,7 +8,7 @@ def self.included(child_class)
               child_class.description("An edge in a connection.")
               child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
               child_class.extend(ClassMethods)
    -          child_class.class_eval { self.node_type = nil }
    +          child_class.class_exec { self.node_type = nil }
               child_class.node_nullable(true)
             end
     
    
  • lib/graphql/version.rb+1 1 modified
    @@ -1,4 +1,4 @@
     # frozen_string_literal: true
     module GraphQL
    -  VERSION = "2.0.31"
    +  VERSION = "2.0.32"
     end
    
  • .rubocop.yml+5 0 modified
    @@ -1,5 +1,6 @@
     require:
       - ./cop/development/none_without_block_cop
    +  - ./cop/development/no_eval_cop
       - ./cop/development/no_focus_cop
       - ./lib/graphql/rubocop/graphql/default_null_true
       - ./lib/graphql/rubocop/graphql/default_required_true
    @@ -52,6 +53,10 @@ Development/NoneWithoutBlockCop:
         - "lib/**/*"
         - "spec/**/*"
     
    +Development/NoEvalCop:
    +  Include:
    +    - "lib/**/*"
    +
     Development/NoFocusCop:
       Include:
         - "spec/**/*"
    
  • spec/graphql/schema/loader_spec.rb+149 0 modified
    @@ -398,4 +398,153 @@ def assert_deep_equal(expected_type, actual_type)
           assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
         end
       end
    +
    +  it "validates field argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "something-wrong",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "SCALAR",
    +                        "name" => "Int",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "something-wrong"
    +  end
    +
    +  it "validates field names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "bad.int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad.int"
    +  end
    +
    +  it "validates input object argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "inputObject",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "INPUT_OBJECT",
    +                        "name" => "SomeInputObject",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            },
    +            {
    +              "kind" => "INPUT_OBJECT",
    +              "name" => "SomeInputObject",
    +              "description" => nil,
    +              "inputFields" => [
    +                {
    +                  "name"=>"bad, input",
    +                  "type"=> { "kind" => "SCALAR", "name" => "String"},
    +                  "defaultValue"=> nil,
    +                  "description" => nil,
    +                },
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad, input"
    +  end
     end
    
6eca16b9fa55

Merge commit from fork

https://github.com/rmosolgo/graphql-rubyRobert MosolgoMar 12, 2025via ghsa
11 files changed · +198 21
  • benchmark/run.rb+1 1 modified
    @@ -82,7 +82,7 @@ def self.profile_large_introspection
                 graphql_name("Object#{n}")
                 20.times do |n2|
                   field :"field#{n2}", String do
    -                argument :arg, String
    +                argument :input, input_obj_t
                   end
                 end
               end
    
  • cop/development/no_eval_cop.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'rubocop'
    +
    +module Cop
    +  module Development
    +    class NoEvalCop < RuboCop::Cop::Base
    +      MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accepts strings and may result evaluating unexpected code. Use `%{exec_method_name}` instead, and pass a block."
    +
    +      def on_send(node)
    +        case node.method_name
    +        when :module_eval, :class_eval, :instance_eval
    +          message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
    +          add_offense node, message: message
    +        end
    +      end
    +    end
    +  end
    +end
    
  • lib/graphql/language/nodes.rb+3 0 modified
    @@ -133,6 +133,8 @@ def merge!(new_options)
             end
     
             class << self
    +          # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
               # Add a default `#visit_method` and `#children_method_name` using the class name
               def inherited(child_class)
                 super
    @@ -276,6 +278,7 @@ def initialize_node #{arguments.join(", ")}
                   RUBY
                 end
               end
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/schema/argument.rb+3 5 modified
    @@ -52,6 +52,7 @@ def from_resolver?
           def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, 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:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
             arg_name ||= name
             @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
    +        NameValidator.validate!(@name)
             @type_expr = type_expr || type
             @description = desc || description
             @null = required != true
    @@ -85,11 +86,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type
             end
     
             if definition_block
    -          if definition_block.arity == 1
    -            instance_exec(self, &definition_block)
    -          else
    -            instance_eval(&definition_block)
    -          end
    +          # `self` will still be self, it will also be the first argument to the block:
    +          instance_exec(self, &definition_block)
             end
           end
     
    
  • lib/graphql/schema/build_from_definition.rb+8 7 modified
    @@ -427,17 +427,18 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
     
                 # Don't do this for interfaces
                 if default_resolve
    -              owner.class_eval <<-RUBY, __FILE__, __LINE__
    -                # frozen_string_literal: true
    -                def #{resolve_method_name}(**args)
    -                  field_instance = self.class.get_field("#{field_definition.name}")
    -                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    -                end
    -              RUBY
    +              define_field_resolve_method(owner, resolve_method_name, field_definition.name)
                 end
               end
             end
     
    +        def define_field_resolve_method(owner, method_name, field_name)
    +          owner.define_method(method_name) { |**args|
    +            field_instance = self.class.get_field(field_name)
    +            context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    +          }
    +        end
    +
             def build_resolve_type(lookup_hash, directives, missing_type_handler)
               resolve_type_proc = nil
               resolve_type_proc = ->(ast_node) {
    
  • lib/graphql/schema/enum_value.rb+1 1 modified
    @@ -55,7 +55,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil,
             end
     
             if block_given?
    -          instance_eval(&block)
    +          instance_exec(self, &block)
             end
           end
     
    
  • lib/graphql/schema/field.rb+1 0 modified
    @@ -232,6 +232,7 @@ def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, functio
     
             @underscored_name = -Member::BuildType.underscore(name_s)
             @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
    +        NameValidator.validate!(@name)
             @description = description
             if field.is_a?(GraphQL::Schema::Field)
               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+8 6 modified
    @@ -138,12 +138,7 @@ class << self
             def argument(*args, **kwargs, &block)
               argument_defn = super(*args, **kwargs, &block)
               # Add a method access
    -          method_name = argument_defn.keyword
    -          class_eval <<-RUBY, __FILE__, __LINE__
    -            def #{method_name}
    -              self[#{method_name.inspect}]
    -            end
    -          RUBY
    +          define_accessor_method(argument_defn.keyword)
               argument_defn
             end
     
    @@ -253,6 +248,13 @@ def coerce_result(value, ctx)
     
               result
             end
    +
    +        private
    +
    +        def define_accessor_method(method_name)
    +          define_method(method_name) { self[method_name] }
    +          alias_method(method_name, method_name)
    +        end
           end
     
           private
    
  • lib/graphql/version.rb+1 1 modified
    @@ -1,4 +1,4 @@
     # frozen_string_literal: true
     module GraphQL
    -  VERSION = "1.13.23"
    +  VERSION = "1.13.24"
     end
    
  • .rubocop.yml+5 0 modified
    @@ -1,5 +1,6 @@
     require:
       - ./cop/development/none_without_block_cop
    +  - ./cop/development/no_eval_cop
       - ./cop/development/no_focus_cop
       - ./lib/graphql/rubocop/graphql/default_null_true
       - ./lib/graphql/rubocop/graphql/default_required_true
    @@ -52,6 +53,10 @@ Development/NoneWithoutBlockCop:
         - "lib/**/*"
         - "spec/**/*"
     
    +Development/NoEvalCop:
    +  Include:
    +    - "lib/**/*"
    +
     Development/NoFocusCop:
       Include:
         - "spec/**/*"
    
  • spec/graphql/schema/loader_spec.rb+149 0 modified
    @@ -398,4 +398,153 @@ def assert_deep_equal(expected_type, actual_type)
           assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
         end
       end
    +
    +  it "validates field argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "something-wrong",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "SCALAR",
    +                        "name" => "Int",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "something-wrong"
    +  end
    +
    +  it "validates field names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "bad.int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad.int"
    +  end
    +
    +  it "validates input object argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "inputObject",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "INPUT_OBJECT",
    +                        "name" => "SomeInputObject",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            },
    +            {
    +              "kind" => "INPUT_OBJECT",
    +              "name" => "SomeInputObject",
    +              "description" => nil,
    +              "inputFields" => [
    +                {
    +                  "name"=>"bad, input",
    +                  "type"=> { "kind" => "SCALAR", "name" => "String"},
    +                  "defaultValue"=> nil,
    +                  "description" => nil,
    +                },
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad, input"
    +  end
     end
    
28233b16c0eb

Merge commit from fork

https://github.com/rmosolgo/graphql-rubyRobert MosolgoMar 12, 2025via ghsa
9 files changed · +193 20
  • cop/development/no_eval_cop.rb+18 0 added
    @@ -0,0 +1,18 @@
    +# frozen_string_literal: true
    +require 'rubocop'
    +
    +module Cop
    +  module Development
    +    class NoEvalCop < RuboCop::Cop::Base
    +      MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accepts strings and may result evaluating unexpected code. Use `%{exec_method_name}` instead, and pass a block."
    +
    +      def on_send(node)
    +        case node.method_name
    +        when :module_eval, :class_eval, :instance_eval
    +          message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym }
    +          add_offense node, message: message
    +        end
    +      end
    +    end
    +  end
    +end
    
  • lib/graphql/language/nodes.rb+3 0 modified
    @@ -133,6 +133,8 @@ def merge!(new_options)
             end
     
             class << self
    +          # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
    +
               # Add a default `#visit_method` and `#children_method_name` using the class name
               def inherited(child_class)
                 super
    @@ -276,6 +278,7 @@ def initialize_node #{arguments.join(", ")}
                   RUBY
                 end
               end
    +          # rubocop:enable Development/NoEvalCop
             end
           end
     
    
  • lib/graphql/schema/argument.rb+3 5 modified
    @@ -55,6 +55,7 @@ def from_resolver?
           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:, validates: nil, directives: nil, deprecation_reason: nil, &definition_block)
             arg_name ||= name
             @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
    +        NameValidator.validate!(@name)
             @type_expr = type_expr || type
             @description = desc || description
             @null = !required
    @@ -78,11 +79,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil
             self.validates(validates)
     
             if definition_block
    -          if definition_block.arity == 1
    -            instance_exec(self, &definition_block)
    -          else
    -            instance_eval(&definition_block)
    -          end
    +          # `self` will still be self, it will also be the first argument to the block:
    +          instance_exec(self, &definition_block)
             end
           end
     
    
  • lib/graphql/schema/build_from_definition.rb+8 7 modified
    @@ -426,17 +426,18 @@ def build_fields(owner, field_definitions, type_resolver, default_resolve:)
     
                 # Don't do this for interfaces
                 if default_resolve
    -              owner.class_eval <<-RUBY, __FILE__, __LINE__
    -                # frozen_string_literal: true
    -                def #{resolve_method_name}(**args)
    -                  field_instance = self.class.get_field("#{field_definition.name}")
    -                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    -                end
    -              RUBY
    +              define_field_resolve_method(owner, resolve_method_name, field_definition.name)
                 end
               end
             end
     
    +        def define_field_resolve_method(owner, method_name, field_name)
    +          owner.define_method(method_name) { |**args|
    +            field_instance = self.class.get_field(field_name)
    +            context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
    +          }
    +        end
    +
             def build_resolve_type(lookup_hash, directives, missing_type_handler)
               resolve_type_proc = nil
               resolve_type_proc = ->(ast_node) {
    
  • lib/graphql/schema/enum_value.rb+1 1 modified
    @@ -55,7 +55,7 @@ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil,
             end
     
             if block_given?
    -          instance_eval(&block)
    +          instance_exec(self, &block)
             end
           end
     
    
  • lib/graphql/schema/field.rb+1 0 modified
    @@ -231,6 +231,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function
             name_s = -name.to_s
             @underscored_name = -Member::BuildType.underscore(name_s)
             @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
    +        NameValidator.validate!(@name)
             @description = description
             if field.is_a?(GraphQL::Schema::Field)
               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+9 6 modified
    @@ -123,12 +123,8 @@ class << self
             def argument(*args, **kwargs, &block)
               argument_defn = super(*args, **kwargs, &block)
               # Add a method access
    -          method_name = argument_defn.keyword
    -          class_eval <<-RUBY, __FILE__, __LINE__
    -            def #{method_name}
    -              self[#{method_name.inspect}]
    -            end
    -          RUBY
    +          define_accessor_method(argument_defn.keyword)
    +          argument_defn
             end
     
             def to_graphql
    @@ -236,6 +232,13 @@ def coerce_result(value, ctx)
     
               result
             end
    +
    +        private
    +
    +        def define_accessor_method(method_name)
    +          define_method(method_name) { self[method_name] }
    +          alias_method(method_name, method_name)
    +        end
           end
     
           private
    
  • lib/graphql/version.rb+1 1 modified
    @@ -1,4 +1,4 @@
     # frozen_string_literal: true
     module GraphQL
    -  VERSION = "1.12.24"
    +  VERSION = "1.12.25"
     end
    
  • spec/graphql/schema/loader_spec.rb+149 0 modified
    @@ -389,4 +389,153 @@ def assert_deep_equal(expected_type, actual_type)
           assert_equal arg.default_value, { 'id' => nil, 'int' => nil, 'float' => nil, 'enum' => nil, 'sub' => nil, 'bool' => nil, 'bigint' => nil }
         end
       end
    +
    +  it "validates field argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "something-wrong",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "SCALAR",
    +                        "name" => "Int",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "something-wrong"
    +  end
    +
    +  it "validates field names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "bad.int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [],
    +                }
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad.int"
    +  end
    +
    +  it "validates input object argument names" do
    +    json = {
    +      "data" => {
    +        "__schema" => {
    +          "queryType" => {
    +            "name" => "Query"
    +          },
    +          "mutationType" => nil,
    +          "subscriptionType" => nil,
    +          "types" => [
    +            {
    +              "kind" => "OBJECT",
    +              "name" => "Query",
    +              "description" => nil,
    +              "fields" => [
    +                {
    +                  "name" => "int",
    +                  "description" => nil,
    +                  "type" => {
    +                    "kind" => "SCALAR",
    +                    "name" => "Int",
    +                    "ofType" => nil,
    +                  },
    +                  "args" => [
    +                    {
    +                      "name" => "inputObject",
    +                      "description" => nil,
    +                      "type" => {
    +                        "kind" => "INPUT_OBJECT",
    +                        "name" => "SomeInputObject",
    +                        "ofType" => nil
    +                      },
    +                      "defaultValue" => nil
    +                    }
    +                  ],
    +                }
    +              ]
    +            },
    +            {
    +              "kind" => "INPUT_OBJECT",
    +              "name" => "SomeInputObject",
    +              "description" => nil,
    +              "inputFields" => [
    +                {
    +                  "name"=>"bad, input",
    +                  "type"=> { "kind" => "SCALAR", "name" => "String"},
    +                  "defaultValue"=> nil,
    +                  "description" => nil,
    +                },
    +              ]
    +            }
    +          ]
    +        }
    +      }
    +    }
    +    err = assert_raises GraphQL::InvalidNameError do
    +      GraphQL::Schema.from_introspection(json)
    +    end
    +
    +    assert_includes err.message, "bad, input"
    +  end
     end
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

14

News mentions

0

No linked articles in our index yet.