Graphql-ruby: Disable Introspection

Created on 24 Jan 2018  路  4Comments  路  Source: rmosolgo/graphql-ruby

Is there a way to disable Introspection from the schema?

Most helpful comment

In case anyone else was looking for a simple example, I went ahead and implemented this IntrospectionAnalyzer to block introspection queries in Rails production environments:

class IntrospectionAnalyzer
  def initial_value(query)
    {
      query: query,
      is_introspection: false
    }
  end

  def call(memo, visit_type, irep_node)
    if irep_node.definition && irep_node.definition.introspection?
      memo[:is_introspection] = true
    end

    memo
  end

  def final_value(memo)
    if memo[:is_introspection] && Rails.env.production?
      unauthorized_introspection_query_error
    else
      memo
    end
  end

  private

  def unauthorized_introspection_query_error
    GraphQL::AnalysisError.new(
      'Introspection queries are not allowed in production.'
    )
  end
end

Schema:

class ApiSchema < GraphQL::Schema
  use GraphQL::Tracing::NewRelicTracing
  use GraphQL::Batch
  max_depth 8

  mutation Types::Mutation
  query Types::Query

  query_analyzer IntrospectionAnalyzer.new
end

Hope this helps, any improvements are welcome.

All 4 comments

I don't have an answer for you, but I'm curious what the reason for that would be. I'm only guessing, but I doubt there is a way to do that, since it's not part of the GraphQL spec to be able to disable introspection.

It's not built-in, but you could implement it with a query analyzer. You could check each irep_node.definition.introspection?, and if it returns true, return an AnalysisError in final_value. Good luck, and feel free to follow up here if you have any trouble in the process!

In case anyone else was looking for a simple example, I went ahead and implemented this IntrospectionAnalyzer to block introspection queries in Rails production environments:

class IntrospectionAnalyzer
  def initial_value(query)
    {
      query: query,
      is_introspection: false
    }
  end

  def call(memo, visit_type, irep_node)
    if irep_node.definition && irep_node.definition.introspection?
      memo[:is_introspection] = true
    end

    memo
  end

  def final_value(memo)
    if memo[:is_introspection] && Rails.env.production?
      unauthorized_introspection_query_error
    else
      memo
    end
  end

  private

  def unauthorized_introspection_query_error
    GraphQL::AnalysisError.new(
      'Introspection queries are not allowed in production.'
    )
  end
end

Schema:

class ApiSchema < GraphQL::Schema
  use GraphQL::Tracing::NewRelicTracing
  use GraphQL::Batch
  max_depth 8

  mutation Types::Mutation
  query Types::Query

  query_analyzer IntrospectionAnalyzer.new
end

Hope this helps, any improvements are welcome.

Security tips 馃憤

query_analyzer.rb

class QueryAnalyzer < GraphQL::Analysis::AST::Analyzer
  QUERY_ALLOWED_DEPTH = 5

  def initialize(query)
    super
    @max_depth = 0
    @current_depth = 0
  end

  def on_enter_field(node, parent, visitor)
    @current_depth += 1
  end

  def on_leave_field(node, parent, visitor)
    # NOTE: https://graphql-ruby.org/schema/introspection
    introspection_field_names = %w[__schema __type]
    field_def = visitor.field_definition
    if field_def.introspection? && introspection_field_names.include?(field_def.graphql_name)
      @introspection_present = true
    end

    # TODO: Check depth per field

    if @max_depth < @current_depth
      @max_depth = @current_depth
    end
    @current_depth -= 1
  end

  def result
    if @introspection_present
      GraphQL::AnalysisError.new("Not authorized to query schema internals") unless authorized?
    elsif depth_exceeded?
      GraphQL::AnalysisError.new("Query depth #{@max_depth} exceeds allowed #{query_allowed_depth}")
    end
  end

  private def authorized?
    Rails.env.development? ||
    ENV["GRAPHQL_SCHEMA_PASSWORD"].present? &&
    ENV["GRAPHQL_SCHEMA_PASSWORD"] == query.context[:request].headers["Schema-Token"]
  end

  private def depth_exceeded?
    @max_depth > query_allowed_depth
  end

  private def query_allowed_depth
    QUERY_ALLOWED_DEPTH
  end
end

api_schema.rb

class ApiSchema < GraphQL::Schema
  # NOTE: Do not enable `max_depth`, use custom analyser
  # max_depth 8

  mutation(Types::MutationType)
  query(Types::QueryType)

  use(GraphQL::Analysis::AST)
  query_analyzer(QueryAnalyzer)
end

Schema dump using apollo service

apollo service:download --endpoint=https://my-api-endpoint/graphql --header="Schema-Token: XXX"

Query sample

query {
  __schema {
    queryType {
      fields {
        name
        type {
          kind
          ofType {
            kind
            name
          }
        }
      }
    }
  }
}
Was this page helpful?
0 / 5 - 0 ratings