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?
Can I use Actix middleware to block introspection requests instead of modifying the GraphQL schema?
__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.