MEDIUM actixgraphql introspection abuse

Graphql Introspection Abuse in Actix

Actix-Specific Remediation

To remediate GraphQL introspection abuse in an Actix-based service, you must configure the GraphQL layer (async-graphql) to disable or restrict introspection, as Actix itself does not manage GraphQL behavior. The fix involves modifying the Schema build process or using middleware to block introspection fields.

Recommended approach: Disable introspection when not in development. Using async-graphql, you can conditionally enable introspection based on an environment variable:

use async_graphql::{Schema, ObjectType, EmptyMutation, EmptySubscription};
use std::env;

struct Query;

#[Object]
impl Query {
    async fn user(&self, id: i32) -> Option { /* ... */ }
}

fn build_schema() -> Schema {
    let enable_introspection = env::var("ENABLE_GRAPHQL_INTROSPECTION")
        .unwrap_or_else(|_| "false".to_string()) == "true";
    
    let mut schema_builder = Schema::build(Query, EmptyMutation, EmptySubscription);
    
    if !enable_introspection {
        // Disable introspection by removing __schema and __type
        schema_builder = schema_builder.limit_depth(Some(50)).limit_complexity(Some(100));
        // Note: async-graphql doesn't have a direct flag, but we can use a plugin or middleware
        // For simplicity in this example, we rely on environment-controlled exposure
        // In practice, use a custom extension or middleware to filter introspection
    }
    
    schema_builder.finish()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let schema = web::Data::new(build_schema());
    HttpServer::new(move || {
        App::new()
            .app_data(schema.clone())
            .service(web::resource("/graphql").to(async_graphql_actix::GraphQL))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

A more robust method uses a custom async-graphql extension to block introspection fields:

use async_graphql::{Extension, Context, Next, Field, ObjectType};

struct DisableIntrospection;

#[async_trait::async_trait]
impl Extension for DisableIntrospection {
    async fn field(&self, ctx: &Context<'_>, next: Next<'_>) -> Field {
        let field = next.run(ctx).await;
        if field.name.as_str() == "__schema" || field.name.as_str() == "__type" {
            // Return an error for introspection fields
            Field::new(Box::pin(async move {
                Err(async_graphql::Error::new("Introspection is disabled").into())
            }))
        } else {
            field
        }
    }
}

// Then build schema with extension
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
    .extension(DisableIntrospection)
    .finish();

This extension runs during field resolution in the GraphQL layer, blocking introspection before it reaches Actix. Since Actix only handles HTTP transport, the fix stays within async-graphql—no changes to Actix routing or middleware are needed. Deploy with ENABLE_GRAPHQL_INTROSPECTION=false in production to ensure introspection remains off.

Frequently Asked Questions

Does disabling GraphQL introspection in Actix affect tools like GraphiQL or Apollo Studio?
Yes, disabling introspection will break schema-dependent tools such as GraphiQL, Apollo Studio, or Voyager, as they rely on introspection to display the API schema. To maintain developer experience, enable introspection only in non-production environments (e.g., via an environment variable) or restrict it to internal networks using Actix-based IP filtering before the GraphQL handler.
Can I use Actix middleware to block introspection requests instead of modifying the GraphQL schema?
While possible, blocking introspection via Actix middleware (e.g., inspecting the POST body for __schema) is less reliable than handling it in the GraphQL layer. A determined attacker could obfuscate the query (e.g., using whitespace, comments, or aliases) to bypass simple string matching. The async-graphql extension method field-level blocking is more robust because it operates after parsing, ensuring introspection fields are rejected regardless of formatting.