VYPR
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.

PackageAffected versionsPatched versions
async-graphqlcrates.io
< 7.0.107.0.10

Patches

1
7f1791488463

add `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

4

News mentions

0

No linked articles in our index yet.