Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions app/graphql/sagittarius_schema.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# frozen_string_literal: true

# rubocop:disable GraphQL/MaxComplexitySchema
# rubocop:disable GraphQL/MaxDepthSchema
class SagittariusSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)

default_max_page_size 50
max_depth 20
connections.add(ActiveRecord::Relation, Sagittarius::Graphql::StableConnection)

# For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
Expand Down Expand Up @@ -54,8 +53,6 @@ def self.object_from_id(global_id, query_ctx = nil)

# rubocop:enable Lint/UnusedMethodArgument
end
# rubocop:enable GraphQL/MaxDepthSchema
# rubocop:enable GraphQL/MaxComplexitySchema

if Types::BaseObject.instance_variable_defined?(:@user_ability_types)
Types::BaseObject.remove_instance_variable(:@user_ability_types) # release temporary type map
Expand Down
88 changes: 88 additions & 0 deletions spec/requests/graphql/query/recursive_query_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Recursive Query Protection' do
include GraphqlHelpers

let(:current_user) { create(:user) }
let(:organization) { create(:organization) }

before do
create(:namespace_member, namespace: organization.ensure_namespace, user: current_user)
end

context 'with deeply nested recursive query' do
let(:query) do
# Create a query with deep nesting that would cause recursion
# Organization -> Namespace -> Parent (Organization) -> Namespace -> Parent...
nested_levels = 25
nested_query = 'id name'

nested_levels.times do
nested_query = <<~NESTED
id
name
namespace {
id
parent {
... on Organization {
#{nested_query}
}
}
}
NESTED
end

<<~QUERY
query($organizationId: OrganizationID!) {
organization(id: $organizationId) {
#{nested_query}
}
}
QUERY
end

let(:variables) { { organizationId: organization.to_global_id.to_s } }

it 'blocks the query and returns an error' do
post_graphql query, variables: variables, current_user: current_user

expect(graphql_errors).not_to be_nil
expect(graphql_errors).not_to be_empty
expect(graphql_errors.first['message']).to include('depth')
end
end

context 'with reasonably nested query' do
let(:query) do
# A reasonable query with moderate nesting (depth ~5)
<<~QUERY
query($organizationId: OrganizationID!) {
organization(id: $organizationId) {
id
name
namespace {
id
parent {
... on Organization {
id
name
}
}
}
}
}
QUERY
end

let(:variables) { { organizationId: organization.to_global_id.to_s } }

it 'allows the query to execute' do
post_graphql query, variables: variables, current_user: current_user

expect(graphql_data_at(:organization)).not_to be_nil
expect(graphql_data_at(:organization, :id)).to eq(organization.to_global_id.to_s)
end
end
end
Loading