High severity7.5NVD Advisory· Published Oct 3, 2024· Updated Apr 15, 2026
CVE-2024-47614
CVE-2024-47614
Description
async-graphql is a GraphQL server library implemented in Rust. async-graphql before 7.0.10 does not limit the number of directives for a field. This can lead to Service Disruption, Resource Exhaustion, and User Experience Degradation. This vulnerability is fixed in 7.0.10.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
async-graphqlcrates.io | < 7.0.10 | 7.0.10 |
Patches
17f1791488463add `SchemeBuilder.limit_directives` method to set the maximum number of directives on a single field.
4 files changed · +107 −19
CHANGELOG.md+4 −0 modified@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [7.0.10] 2024-09-24 + +- add `SchemeBuilder.limit_directives` method to set the maximum number of directives on a single field. + # [7.0.9] 2024-09-02 - add `on_ping` callback to `WebSocket`
src/dynamic/schema.rs+13 −0 modified@@ -27,6 +27,7 @@ pub struct SchemaBuilder { extensions: Vec<Box<dyn ExtensionFactory>>, validation_mode: ValidationMode, recursive_depth: usize, + max_directives: Option<usize>, complexity: Option<usize>, depth: Option<usize>, enable_suggestions: bool, @@ -90,6 +91,13 @@ impl SchemaBuilder { self } + /// Set the maximum number of directives on a single field. (default: no + /// limit) + pub fn limit_directives(mut self, max_directives: usize) -> Self { + self.max_directives = Some(max_directives); + self + } + /// Set the validation mode, default is `ValidationMode::Strict`. #[must_use] pub fn validation_mode(mut self, validation_mode: ValidationMode) -> Self { @@ -204,6 +212,7 @@ impl SchemaBuilder { extensions: self.extensions, types: self.types, recursive_depth: self.recursive_depth, + max_directives: self.max_directives, complexity: self.complexity, depth: self.depth, validation_mode: self.validation_mode, @@ -231,6 +240,7 @@ pub struct SchemaInner { pub(crate) types: IndexMap<String, Type>, extensions: Vec<Box<dyn ExtensionFactory>>, recursive_depth: usize, + max_directives: Option<usize>, complexity: Option<usize>, depth: Option<usize>, validation_mode: ValidationMode, @@ -249,6 +259,7 @@ impl Schema { extensions: Default::default(), validation_mode: ValidationMode::Strict, recursive_depth: 32, + max_directives: None, complexity: None, depth: None, enable_suggestions: true, @@ -365,6 +376,7 @@ impl Schema { &self.0.env.registry, self.0.validation_mode, self.0.recursive_depth, + self.0.max_directives, self.0.complexity, self.0.depth, ) @@ -422,6 +434,7 @@ impl Schema { &schema.0.env.registry, schema.0.validation_mode, schema.0.recursive_depth, + schema.0.max_directives, schema.0.complexity, schema.0.depth, )
src/schema.rs+67 −14 modified@@ -50,6 +50,7 @@ pub struct SchemaBuilder<Query, Mutation, Subscription> { complexity: Option<usize>, depth: Option<usize>, recursive_depth: usize, + max_directives: Option<usize>, extensions: Vec<Box<dyn ExtensionFactory>>, custom_directives: HashMap<String, Box<dyn CustomDirectiveFactory>>, } @@ -115,6 +116,13 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription> self } + /// Set the maximum number of directives on a single field. (default: no + /// limit) + pub fn limit_directives(mut self, max_directives: usize) -> Self { + self.max_directives = Some(max_directives); + self + } + /// Add an extension to the schema. /// /// # Examples @@ -265,6 +273,7 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription> complexity: self.complexity, depth: self.depth, recursive_depth: self.recursive_depth, + max_directives: self.max_directives, extensions: self.extensions, env: SchemaEnv(Arc::new(SchemaEnvInner { registry: self.registry, @@ -303,6 +312,7 @@ pub struct SchemaInner<Query, Mutation, Subscription> { pub(crate) complexity: Option<usize>, pub(crate) depth: Option<usize>, pub(crate) recursive_depth: usize, + pub(crate) max_directives: Option<usize>, pub(crate) extensions: Vec<Box<dyn ExtensionFactory>>, pub(crate) env: SchemaEnv, } @@ -381,6 +391,7 @@ where complexity: None, depth: None, recursive_depth: 32, + max_directives: None, extensions: Default::default(), custom_directives: Default::default(), } @@ -518,6 +529,7 @@ where &self.0.env.registry, self.0.validation_mode, self.0.recursive_depth, + self.0.max_directives, self.0.complexity, self.0.depth, ) @@ -574,7 +586,8 @@ where async_stream::stream! { let (env, cache_control) = match prepare_request( extensions, request, session_data, &env.registry, - schema.0.validation_mode, schema.0.recursive_depth, schema.0.complexity, schema.0.depth + schema.0.validation_mode, schema.0.recursive_depth, + schema.0.max_directives, schema.0.complexity, schema.0.depth ).await { Ok(res) => res, Err(errors) => { @@ -658,6 +671,53 @@ where } } +fn check_max_directives(doc: &ExecutableDocument, max_directives: usize) -> ServerResult<()> { + fn check_selection_set( + doc: &ExecutableDocument, + selection_set: &Positioned<SelectionSet>, + limit_directives: usize, + ) -> ServerResult<()> { + for selection in &selection_set.node.items { + match &selection.node { + Selection::Field(field) => { + if field.node.directives.len() > limit_directives { + return Err(ServerError::new( + format!( + "The number of directives on the field `{}` cannot be greater than `{}`", + field.node.name.node, limit_directives + ), + Some(field.pos), + )); + } + check_selection_set(doc, &field.node.selection_set, limit_directives)?; + } + Selection::FragmentSpread(fragment_spread) => { + if let Some(fragment) = + doc.fragments.get(&fragment_spread.node.fragment_name.node) + { + check_selection_set(doc, &fragment.node.selection_set, limit_directives)?; + } + } + Selection::InlineFragment(inline_fragment) => { + check_selection_set( + doc, + &inline_fragment.node.selection_set, + limit_directives, + )?; + } + } + } + + Ok(()) + } + + for (_, operation) in doc.operations.iter() { + check_selection_set(doc, &operation.node.selection_set, max_directives)?; + } + + Ok(()) +} + fn check_recursive_depth(doc: &ExecutableDocument, max_depth: usize) -> ServerResult<()> { fn check_selection_set( doc: &ExecutableDocument, @@ -776,6 +836,7 @@ pub(crate) async fn prepare_request( registry: &Registry, validation_mode: ValidationMode, recursive_depth: usize, + max_directives: Option<usize>, complexity: Option<usize>, depth: Option<usize>, ) -> Result<(QueryEnv, CacheControl), Vec<ServerError>> { @@ -792,6 +853,9 @@ pub(crate) async fn prepare_request( None => parse_query(query)?, }; check_recursive_depth(&doc, recursive_depth)?; + if let Some(max_directives) = max_directives { + check_max_directives(&doc, max_directives)?; + } Ok(doc) }; futures_util::pin_mut!(fut_parse); @@ -808,25 +872,14 @@ pub(crate) async fn prepare_request( &document, Some(&request.variables), validation_mode, + complexity, + depth, ) }; futures_util::pin_mut!(validation_fut); extensions.validation(&mut validation_fut).await? }; - // check limit - if let Some(limit_complexity) = complexity { - if validation_result.complexity > limit_complexity { - return Err(vec![ServerError::new("Query is too complex.", None)]); - } - } - - if let Some(limit_depth) = depth { - if validation_result.depth > limit_depth { - return Err(vec![ServerError::new("Query is nested too deep.", None)]); - } - } - let operation = if let Some(operation_name) = &request.operation_name { match document.operations { DocumentOperations::Single(_) => None,
src/validation/mod.rs+23 −5 modified@@ -39,19 +39,21 @@ pub enum ValidationMode { Fast, } -pub fn check_rules( +pub(crate) fn check_rules( registry: &Registry, doc: &ExecutableDocument, variables: Option<&Variables>, mode: ValidationMode, + limit_complexity: Option<usize>, + limit_depth: Option<usize>, ) -> Result<ValidationResult, Vec<ServerError>> { - let mut ctx = VisitorContext::new(registry, doc, variables); let mut cache_control = CacheControl::default(); let mut complexity = 0; let mut depth = 0; - match mode { + let errors = match mode { ValidationMode::Strict => { + let mut ctx = VisitorContext::new(registry, doc, variables); let mut visitor = VisitorNil .with(rules::ArgumentsOfCorrectType::default()) .with(rules::DefaultValuesOfCorrectType) @@ -84,8 +86,10 @@ pub fn check_rules( .with(visitors::ComplexityCalculate::new(&mut complexity)) .with(visitors::DepthCalculate::new(&mut depth)); visit(&mut visitor, &mut ctx, doc); + ctx.errors } ValidationMode::Fast => { + let mut ctx = VisitorContext::new(registry, doc, variables); let mut visitor = VisitorNil .with(rules::NoFragmentCycles::default()) .with(rules::UploadFile) @@ -95,11 +99,25 @@ pub fn check_rules( .with(visitors::ComplexityCalculate::new(&mut complexity)) .with(visitors::DepthCalculate::new(&mut depth)); visit(&mut visitor, &mut ctx, doc); + ctx.errors + } + }; + + // check limit + if let Some(limit_complexity) = limit_complexity { + if complexity > limit_complexity { + return Err(vec![ServerError::new("Query is too complex.", None)]); + } + } + + if let Some(limit_depth) = limit_depth { + if depth > limit_depth { + return Err(vec![ServerError::new("Query is nested too deep.", None)]); } } - if !ctx.errors.is_empty() { - return Err(ctx.errors.into_iter().map(Into::into).collect()); + if !errors.is_empty() { + return Err(errors.into_iter().map(Into::into).collect()); } Ok(ValidationResult {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.