VYPR
High severityNVD Advisory· Published Oct 18, 2023· Updated Sep 13, 2024

Improper Check or Handling of Exceptional Conditions in apollo-router

CVE-2023-45812

Description

The Apollo Router is a configurable, high-performance graph router written in Rust to run a federated supergraph that uses Apollo Federation. Affected versions are subject to a Denial-of-Service (DoS) type vulnerability which causes the Router to panic and terminate when a multi-part response is sent. When users send queries to the router that uses the @defer or Subscriptions, the Router will panic. To be vulnerable, users of Router must have a coprocessor with coprocessor.supergraph.response configured in their router.yaml and also to support either @defer or Subscriptions. Apollo Router version 1.33.0 has a fix for this vulnerability which was introduced in PR #4014. Users are advised to upgrade. Users unable to upgrade should avoid using the coprocessor supergraph response or disable defer and subscriptions support and continue to use the coprocessor supergraph response.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Apollo Router panics and terminates on multi-part responses when a coprocessor is configured with @defer or subscriptions, leading to DoS.

Vulnerability

Overview

CVE-2023-45812 is a denial-of-service (DoS) vulnerability in the Apollo Router, a high-performance graph router for Apollo Federation. The bug causes the router to panic and terminate when it processes a multi-part response—specifically, responses generated by @defer directives or GraphQL subscriptions—while a coprocessor is configured with coprocessor.supergraph.response in the router's YAML configuration [1][2]. The root cause lies in how the router's coprocessor handling code interacts with streaming responses; the panic occurs because the router attempts to process a multi-part response in a way that is not compatible with the coprocessor's expected data format [4].

Exploitation

Conditions

To be vulnerable, an attacker must be able to send queries to the router that trigger either @defer or subscriptions. Additionally, the router must have a coprocessor configured with the coprocessor.supergraph.response stage enabled. No authentication is required if the router is publicly accessible; any user who can send GraphQL queries can trigger the panic. The attack does not require special network position beyond the ability to reach the router's endpoint [2].

Impact

Successful exploitation results in the router process panicking and terminating, causing a denial of service. This disrupts all GraphQL traffic served by that router instance until it is restarted. The vulnerability does not lead to data leakage or privilege escalation; the sole impact is service unavailability [2].

Mitigation

Apollo Router version 1.33.0 includes a fix introduced in pull request #4014 [1][4]. Users are advised to upgrade immediately. For those unable to upgrade, the recommended workaround is to either disable the coprocessor supergraph response configuration or disable @defer and subscription support entirely [2]. The fix ensures that the coprocessor correctly handles multi-part responses without panicking [4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
apollo-routercrates.io
>= 1.31.0, < 1.33.01.33.0

Affected products

2

Patches

1
b917b8c117b4

Fix and add test for co-processors handling of streaming responses. (#4014)

https://github.com/apollographql/routerBryn CookeOct 11, 2023via ghsa
4 files changed · +132 5
  • apollo-router/src/plugins/coprocessor/supergraph.rs+88 5 modified
    @@ -428,7 +428,7 @@ where
                     // Note: We deliberately DO NOT send headers or status_code even if the user has
                     // requested them. That's because they are meaningless on a deferred response and
                     // providing them will be a source of confusion.
    -                let payload = Externalizable::router_builder()
    +                let payload = Externalizable::supergraph_builder()
                         .stage(PipelineStep::SupergraphResponse)
                         .and_id(TraceId::maybe_new().map(|id| id.to_string()))
                         .and_body(body_to_send)
    @@ -753,13 +753,13 @@ mod tests {
     
             let value = response.headers().get("aheader").unwrap();
     
    -        assert_eq!("a value", value);
    +        assert_eq!(value, "a value");
     
             assert_eq!(
    -            "my error message",
                 response.body_mut().next().await.unwrap().errors[0]
                     .message
    -                .as_str()
    +                .as_str(),
    +            "my error message"
             );
         }
     
    @@ -852,7 +852,7 @@ mod tests {
                           "this-is-a-test-context": 42
                         }
                       },
    -                  "sdl": "the sdl shouldnt change"
    +                  "sdl": "the sdl shouldn't change"
                     });
                     Ok(hyper::Response::builder()
                         .body(Body::from(serde_json::to_string(&input).unwrap()))
    @@ -889,8 +889,91 @@ mod tests {
             let body = res.response.body_mut().next().await.unwrap();
             // the body should have changed:
             assert_eq!(
    +            serde_json::to_value(&body).unwrap(),
                 json!({ "data": { "test": 42_u32 } }),
    +        );
    +    }
    +
    +    #[tokio::test]
    +    async fn defer() {
    +        let supergraph_stage = SupergraphStage {
    +            response: SupergraphResponseConf {
    +                headers: true,
    +                context: true,
    +                body: true,
    +                sdl: true,
    +                status_code: false,
    +            },
    +            request: Default::default(),
    +        };
    +
    +        let mut mock_supergraph_service = MockSupergraphService::new();
    +
    +        mock_supergraph_service
    +            .expect_call()
    +            .returning(|req: supergraph::Request| {
    +                Ok(supergraph::Response::fake_stream_builder()
    +                    .response(
    +                        graphql::Response::builder()
    +                            .data(json!({ "test": 1 }))
    +                            .has_next(true)
    +                            .build(),
    +                    )
    +                    .response(
    +                        graphql::Response::builder()
    +                            .data(json!({ "test": 2 }))
    +                            .has_next(false)
    +                            .build(),
    +                    )
    +                    .context(req.context)
    +                    .build()
    +                    .unwrap())
    +            });
    +
    +        let mock_http_client = mock_with_deferred_callback(move |res: hyper::Request<Body>| {
    +            Box::pin(async {
    +                let deserialized_response: Externalizable<serde_json::Value> =
    +                    serde_json::from_slice(&hyper::body::to_bytes(res.into_body()).await.unwrap())
    +                        .unwrap();
    +                assert_eq!(EXTERNALIZABLE_VERSION, deserialized_response.version);
    +                assert_eq!(
    +                    PipelineStep::SupergraphResponse.to_string(),
    +                    deserialized_response.stage
    +                );
    +
    +                Ok(hyper::Response::builder()
    +                    .body(Body::from(
    +                        serde_json::to_string(&deserialized_response).unwrap(),
    +                    ))
    +                    .unwrap())
    +            })
    +        });
    +
    +        let service = supergraph_stage.as_service(
    +            mock_http_client,
    +            mock_supergraph_service.boxed(),
    +            "http://test".to_string(),
    +            Arc::new("".to_string()),
    +        );
    +
    +        let request = supergraph::Request::canned_builder()
    +            .query("foo")
    +            .build()
    +            .unwrap();
    +
    +        let mut res = service.oneshot(request).await.unwrap();
    +
    +        let body = res.response.body_mut().next().await.unwrap();
    +        // the body should have changed:
    +        assert_eq!(
    +            serde_json::to_value(&body).unwrap(),
    +            json!({ "data": { "test": 1 }, "hasNext": true }),
    +        );
    +        let body = res.response.body_mut().next().await.unwrap();
    +        // the body should have changed:
    +        assert_eq!(
                 serde_json::to_value(&body).unwrap(),
    +            json!({ "data": { "test": 2 }, "hasNext": false }),
             );
         }
     }
    
  • apollo-router/src/services/external.rs+11 0 modified
    @@ -261,6 +261,17 @@ mod test {
                 .build();
         }
     
    +    #[test]
    +    #[should_panic]
    +    fn it_will_not_build_router_externalizable_incorrectl_supergraph() {
    +        Externalizable::<String>::router_builder()
    +            .stage(PipelineStep::SupergraphRequest)
    +            .build();
    +        Externalizable::<String>::router_builder()
    +            .stage(PipelineStep::SupergraphResponse)
    +            .build();
    +    }
    +
         #[test]
         fn it_will_build_subgraph_externalizable_correctly() {
             Externalizable::<String>::subgraph_builder()
    
  • apollo-router/src/services/supergraph.rs+28 0 modified
    @@ -251,6 +251,34 @@ impl Response {
             )
         }
     
    +    /// This is the constructor (or builder) to use when constructing a "fake" Response stream.
    +    ///
    +    /// This does not enforce the provision of the data that is required for a fully functional
    +    /// Response. It's usually enough for testing, when a fully constructed Response is
    +    /// difficult to construct and not required for the purposes of the test.
    +    ///
    +    /// In addition, fake responses are expected to be valid, and will panic if given invalid values.
    +    #[builder(visibility = "pub")]
    +    fn fake_stream_new(
    +        responses: Vec<graphql::Response>,
    +        status_code: Option<StatusCode>,
    +        headers: MultiMap<TryIntoHeaderName, TryIntoHeaderValue>,
    +        context: Context,
    +    ) -> Result<Self, BoxError> {
    +        let mut builder = http::Response::builder().status(status_code.unwrap_or(StatusCode::OK));
    +        for (key, values) in headers {
    +            let header_name: HeaderName = key.try_into()?;
    +            for value in values {
    +                let header_value: HeaderValue = value.try_into()?;
    +                builder = builder.header(header_name.clone(), header_value);
    +            }
    +        }
    +
    +        let stream = futures::stream::iter(responses);
    +        let response = builder.body(stream.boxed())?;
    +        Ok(Self { response, context })
    +    }
    +
         /// This is the constructor (or builder) to use when constructing a Response that represents a global error.
         /// It has no path and no response data.
         /// This is useful for things such as authentication errors.
    
  • .changesets/fix_bryn_fix_coprocessor_stream.md+5 0 added
    @@ -0,0 +1,5 @@
    +### Fix panic when streaming responses to co-processor ([Issue #4013](https://github.com/apollographql/router/issues/4013))
    +
    +Streamed responses will no longer cause a panic in the co-processor plugin. This affected defer and stream queries.
    +
    +By [@BrynCooke](https://github.com/BrynCooke) in https://github.com/apollographql/router/pull/4014
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.