From db5a9bd9c20a452ef4139b2bc3cbc93722c50c0e Mon Sep 17 00:00:00 2001 From: Vithusha Ravirajan Date: Wed, 5 Mar 2025 17:06:10 -0500 Subject: [PATCH] Set default validate_timeout value Currently, the gem does not enforce a validate_timeout default. Maliciously crafted queries can lead to resource exhaustion without a timeout. This implementation sets a 3 second default validate_timeout. Most complex GraphQL requests at Shopify, a gem consumer with large GraphQL APIs, are parsed in under 1 second, therefore a 3s timeout seems appropriate. --- guides/queries/timeout.md | 8 +++++++- lib/graphql/schema.rb | 6 +++--- spec/graphql/schema_spec.rb | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/guides/queries/timeout.md b/guides/queries/timeout.md index 2d52189947..d72c9a7d89 100644 --- a/guides/queries/timeout.md +++ b/guides/queries/timeout.md @@ -67,15 +67,21 @@ end Queries can originate from a user, and may be crafted in a manner to take a long time to validate against the schema. -It is possible to limit how many seconds the static validation rules and analysers are allowed to run before returning a validation timeout error. The default is no timeout. +It is possible to limit how many seconds the static validation rules and analysers are allowed to run before returning a validation timeout error. By default, validation and query analysis have a 3-second timeout. You can customize this timeout or disable it completely: For example: ```ruby +# Customize timeout (in seconds) class MySchema < GraphQL::Schema # Applies to static validation and query analysis validate_timeout 10 end + +# OR disable timeout completely +class MySchema < GraphQL::Schema + validate_timeout nil +end ``` **Note:** This configuration uses Ruby's built-in `Timeout` API, which can interrupt IO calls mid-flight, resulting in [very weird bugs](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). None of GraphQL-Ruby's validators make IO calls but if you want to use this configuration and you have custom static validators that make IO calls, open an issue to discuss implementing this in an IO-safe way. diff --git a/lib/graphql/schema.rb b/lib/graphql/schema.rb index a5ada61b33..2108290bfa 100644 --- a/lib/graphql/schema.rb +++ b/lib/graphql/schema.rb @@ -821,13 +821,13 @@ def subscription_execution_strategy(new_subscription_execution_strategy = nil, d attr_writer :validate_timeout - def validate_timeout(new_validate_timeout = nil) - if new_validate_timeout + def validate_timeout(new_validate_timeout = :not_provided) + if new_validate_timeout != :not_provided @validate_timeout = new_validate_timeout elsif defined?(@validate_timeout) @validate_timeout else - find_inherited_value(:validate_timeout) + find_inherited_value(:validate_timeout) || 3 end end diff --git a/spec/graphql/schema_spec.rb b/spec/graphql/schema_spec.rb index 9fa783c5fe..469b714047 100644 --- a/spec/graphql/schema_spec.rb +++ b/spec/graphql/schema_spec.rb @@ -604,4 +604,24 @@ def test assert_equal expected_errors, schema.execute(query_str).to_h['errors'] end end + describe ".validate_timeout" do + it "provides a default timeout when not explicitly set" do + schema = Class.new(GraphQL::Schema) + assert_equal 3, schema.validate_timeout + end + + it "allows overriding the default timeout" do + schema = Class.new(GraphQL::Schema) do + validate_timeout 15 + end + assert_equal 15, schema.validate_timeout + end + + it "allows disabling the timeout" do + schema = Class.new(GraphQL::Schema) do + validate_timeout nil + end + assert_nil schema.validate_timeout + end + end end