From 482d1a7f77ef87ac0e79ec0c01df7ad6c4ce6fb1 Mon Sep 17 00:00:00 2001 From: o0Ignition0o Date: Tue, 10 May 2022 16:50:53 +0200 Subject: [PATCH 01/19] wip Co-authored-by: Simon Sapin --- Cargo.lock | 1 + apollo-router-core/Cargo.toml | 1 + apollo-router-core/src/plugins/csrf.rs | 289 ++++++++++++++++++ apollo-router-core/src/plugins/mod.rs | 1 + apollo-router/src/configuration/mod.rs | 2 + ...nfiguration__tests__schema_generation.snap | 15 +- 6 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 apollo-router-core/src/plugins/csrf.rs diff --git a/Cargo.lock b/Cargo.lock index 8ddcb7cadb..e3b7de9e3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,6 +207,7 @@ dependencies = [ "lazy_static", "lru", "miette", + "mime", "mockall", "moka", "multimap", diff --git a/apollo-router-core/Cargo.toml b/apollo-router-core/Cargo.toml index 78488548cd..b16c70881f 100644 --- a/apollo-router-core/Cargo.toml +++ b/apollo-router-core/Cargo.toml @@ -60,6 +60,7 @@ tracing = "0.1.34" tracing-opentelemetry = "0.17.2" typed-builder = "0.10.0" urlencoding = "2.1.0" +mime = "0.3.16" [dev-dependencies] insta = "1.12.0" diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs new file mode 100644 index 0000000000..4698b7a4b5 --- /dev/null +++ b/apollo-router-core/src/plugins/csrf.rs @@ -0,0 +1,289 @@ +use crate::{register_plugin, Plugin, RouterRequest, RouterResponse, ServiceBuilderExt}; +use http::header::{self, HeaderName}; +use http::{HeaderMap, StatusCode}; +use schemars::JsonSchema; +use serde::Deserialize; +use std::ops::ControlFlow; +use tower::util::BoxService; +use tower::{BoxError, ServiceBuilder, ServiceExt}; + +#[derive(Deserialize, Debug, Clone, JsonSchema)] +#[serde(deny_unknown_fields)] +struct CSRFConfig { + #[serde(default)] + disabled: bool, +} + +static NON_PREFLIGHTED_HEADER_NAMES: &[HeaderName] = &[ + header::ACCEPT, + header::ACCEPT_LANGUAGE, + header::CONTENT_LANGUAGE, + header::CONTENT_TYPE, + header::RANGE, +]; + +static NON_PREFLIGHTED_CONTENT_TYPES: &[&str] = &[ + "application/x-www-form-urlencoded", + "multipart/form-data", + "text/plain", +]; + +#[derive(Debug, Clone)] +struct Csrf { + config: CSRFConfig, +} + +#[async_trait::async_trait] +impl Plugin for Csrf { + type Config = CSRFConfig; + + async fn new(config: Self::Config) -> Result { + Ok(Csrf { config }) + } + + fn router_service( + &mut self, + service: BoxService, + ) -> BoxService { + if !self.config.disabled { + ServiceBuilder::new() + .checkpoint(move |req: RouterRequest| { + if should_accept(&req) { + Ok(ControlFlow::Continue(req)) + } else { + let error = crate::Error { + message: format!("This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \ + Please either specify a 'content-type' header (with a mime-type that is not one of {}) \ + or provide a header such that the request is preflighted: \ + https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests", + NON_PREFLIGHTED_CONTENT_TYPES.join(",")), + locations: Default::default(), + path: Default::default(), + extensions: Default::default(), + }; + let res = RouterResponse::builder() + .error(error) + .status_code(StatusCode::BAD_REQUEST) + .context(req.context) + .build()?; + Ok(ControlFlow::Break(res)) + } + }) + .service(service) + .boxed() + } else { + service + } + } +} + +fn should_accept(req: &RouterRequest) -> bool { + let headers = req.originating_request.headers(); + headers_require_preflight(headers) || content_type_requires_preflight(headers) +} + +fn headers_require_preflight(headers: &HeaderMap) -> bool { + headers + .keys() + .any(|header_name| !NON_PREFLIGHTED_HEADER_NAMES.contains(header_name)) +} + +fn content_type_requires_preflight(headers: &HeaderMap) -> bool { + headers + .get(header::CONTENT_TYPE) + .map(|content_type| { + if let Ok(as_str) = content_type.to_str() { + if let Ok(mime_type) = as_str.parse::() { + return !NON_PREFLIGHTED_CONTENT_TYPES.contains(&mime_type.essence_str()); + } + } + // If we get here, this means that either turning the content-type header value + // into a string failed (ie it's not valid UTF-8), or we couldn't parse it into + // a valid mime type... which is actually *ok* because that would lead to a preflight. + // (That said, it would also be reasonable to reject such requests with provided + // yet unparsable Content-Type here.) + true + }) + .unwrap_or(false) +} + +register_plugin!("apollo", "csrf", Csrf); + +#[cfg(test)] +mod csrf_tests { + + #[tokio::test] + async fn plugin_registered() { + crate::plugins() + .get("apollo.csrf") + .expect("Plugin not found") + .create_instance(&serde_json::json!({ "disabled": true })) + .await + .unwrap(); + + crate::plugins() + .get("apollo.csrf") + .expect("Plugin not found") + .create_instance(&serde_json::json!({})) + .await + .unwrap(); + } + + use super::*; + use crate::{plugin::utils::test::MockRouterService, ResponseBody}; + use serde_json_bytes::json; + use tower::{Service, ServiceExt}; + + #[tokio::test] + async fn it_lets_preflighted_request_pass_through() { + let expected_response_data = json!({ "test": 1234 }); + let expected_response_data2 = expected_response_data.clone(); + let mut mock_service = MockRouterService::new(); + mock_service.expect_call().times(2).returning(move |_| { + RouterResponse::fake_builder() + .data(expected_response_data2.clone()) + .build() + }); + + let mock = mock_service.build(); + + let mut service_stack = Csrf::new(CSRFConfig { disabled: false }) + .await + .unwrap() + .router_service(mock.boxed()); + + let with_preflight_content_type = RouterRequest::fake_builder() + .headers( + [("content-type".into(), "application/json".into())] + .into_iter() + .collect(), + ) + .build() + .unwrap(); + + let res = service_stack + .ready() + .await + .unwrap() + .call(with_preflight_content_type) + .await + .unwrap(); + + match res.response.into_body() { + ResponseBody::GraphQL(res) => { + assert_eq!(res.data.unwrap(), expected_response_data); + } + other => panic!("expected graphql response, found {:?}", other), + } + + let with_preflight_header = RouterRequest::fake_builder() + .headers( + [("x-this-is-a-custom-header".into(), "this-is-a-test".into())] + .into_iter() + .collect(), + ) + .build() + .unwrap(); + + let res = service_stack.oneshot(with_preflight_header).await.unwrap(); + + match res.response.into_body() { + ResponseBody::GraphQL(res) => { + assert_eq!(res.data.unwrap(), expected_response_data); + } + other => panic!("expected graphql response, found {:?}", other), + } + } + + #[tokio::test] + async fn it_rejects_non_preflighted_headers_request() { + let mock = MockRouterService::new().build(); + + let service_stack = Csrf::new(CSRFConfig { disabled: false }) + .await + .unwrap() + .router_service(mock.boxed()); + + let non_preflighted_request = RouterRequest::fake_builder().build().unwrap(); + + let res = service_stack + .oneshot(non_preflighted_request) + .await + .unwrap(); + + match res.response.into_body() { + ResponseBody::GraphQL(res) => { + assert_eq!(res.errors[0].message, "This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \ + Please either specify a 'content-type' header (with a mime-type that is not one of application/x-www-form-urlencoded,multipart/form-data,text/plain) \ + or provide a header such that the request is preflighted: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests"); + } + other => panic!("expected graphql response, found {:?}", other), + } + } + + #[tokio::test] + async fn it_rejects_non_preflighted_content_type_request() { + let mock = MockRouterService::new().build(); + + let service_stack = Csrf::new(CSRFConfig { disabled: false }) + .await + .unwrap() + .router_service(mock.boxed()); + + let non_preflighted_request = RouterRequest::fake_builder() + .headers( + [("content-type".into(), "text/plain".into())] + .into_iter() + .collect(), + ) + .build() + .unwrap(); + + let res = service_stack + .oneshot(non_preflighted_request) + .await + .unwrap(); + + match res.response.into_body() { + ResponseBody::GraphQL(res) => { + assert_eq!(res.errors[0].message, "This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \ + Please either specify a 'content-type' header (with a mime-type that is not one of application/x-www-form-urlencoded,multipart/form-data,text/plain) \ + or provide a header such that the request is preflighted: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests"); + } + other => panic!("expected graphql response, found {:?}", other), + } + } + + #[tokio::test] + async fn it_accepts_non_preflighted_headers_request_when_plugin_is_disabled() { + let expected_response_data = json!({ "test": 1234 }); + let expected_response_data2 = expected_response_data.clone(); + let mut mock_service = MockRouterService::new(); + mock_service.expect_call().times(1).returning(move |_| { + RouterResponse::fake_builder() + .data(expected_response_data2.clone()) + .build() + }); + + let mock = mock_service.build(); + + let service_stack = Csrf::new(CSRFConfig { disabled: true }) + .await + .unwrap() + .router_service(mock.boxed()); + + let non_preflighted_request = RouterRequest::fake_builder().build().unwrap(); + + let res = service_stack + .oneshot(non_preflighted_request) + .await + .unwrap(); + + match res.response.into_body() { + ResponseBody::GraphQL(res) => { + assert_eq!(res.data.unwrap(), expected_response_data); + } + other => panic!("expected graphql response, found {:?}", other), + } + } +} diff --git a/apollo-router-core/src/plugins/mod.rs b/apollo-router-core/src/plugins/mod.rs index 5dd14fbf83..85655d1456 100644 --- a/apollo-router-core/src/plugins/mod.rs +++ b/apollo-router-core/src/plugins/mod.rs @@ -2,6 +2,7 @@ //! //! These plugins are compiled into the router and configured via YAML configuration. +mod csrf; mod forbid_mutations; mod headers; mod include_subgraph_errors; diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index b5ee5e9e7c..4b0998df64 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -353,6 +353,8 @@ fn default_cors_headers() -> Vec { "Content-Type".into(), "apollographql-client-name".into(), "apollographql-client-version".into(), + "x-apollo-operation-name".into(), + "apollo-require-preflight".into(), ] } diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index f553223160..23e0b97f9c 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -1,6 +1,5 @@ --- source: apollo-router/src/configuration/mod.rs -assertion_line: 621 expression: "&schema" --- { @@ -9,6 +8,16 @@ expression: "&schema" "description": "The configuration for the router. Currently maintains a mapping of subgraphs.", "type": "object", "properties": { + "csrf": { + "type": "object", + "properties": { + "disabled": { + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, "forbid_mutations": { "type": "boolean" }, @@ -352,7 +361,9 @@ expression: "&schema" "default": [ "Content-Type", "apollographql-client-name", - "apollographql-client-version" + "apollographql-client-version", + "x-apollo-operation-name", + "apollo-require-preflight" ], "type": "array", "items": { From 331a5e18e6cc20858b0d0ffd28719e930d178d6b Mon Sep 17 00:00:00 2001 From: o0Ignition0o Date: Wed, 11 May 2022 18:32:15 +0200 Subject: [PATCH 02/19] wip: address review issues + add a bit of documentation. todo: add more documentation + actually test the yml + update the cors docs tomorrow with simon --- Cargo.lock | 28 +-- apollo-router-core/src/plugins/csrf.rs | 179 ++++++++++-------- apollo-router/Cargo.toml | 2 +- apollo-router/src/axum_http_server_factory.rs | 10 +- apollo-router/src/configuration/mod.rs | 83 ++++---- ...nfiguration__tests__schema_generation.snap | 25 ++- .../cors-and-csrf/custom-headers.router.yaml | 25 +++ 7 files changed, 207 insertions(+), 145 deletions(-) create mode 100644 examples/cors-and-csrf/custom-headers.router.yaml diff --git a/Cargo.lock b/Cargo.lock index e3b7de9e3b..5da5175347 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,7 +149,7 @@ dependencies = [ "tokio-util 0.7.1", "tonic", "tower", - "tower-http 0.2.5", + "tower-http", "tower-service", "tower-test", "tracing", @@ -443,7 +443,7 @@ dependencies = [ "sync_wrapper", "tokio", "tower", - "tower-http 0.3.0", + "tower-http", "tower-layer", "tower-service", ] @@ -4857,28 +4857,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba3f3efabf7fb41fae8534fc20a817013dd1c12cb45441efb6c82e6556b4cd8" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79dd37121c38240c4b4fe6520332406218bbf876f2f690fe9e406020189366fd" +checksum = "7d342c6d58709c0a6d48d48dabbb62d4ef955cf5f0f3bbfd845838e7ae88dbae" dependencies = [ "bitflags", "bytes", @@ -4891,6 +4872,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs index 4698b7a4b5..9a59145f52 100644 --- a/apollo-router-core/src/plugins/csrf.rs +++ b/apollo-router-core/src/plugins/csrf.rs @@ -1,5 +1,5 @@ use crate::{register_plugin, Plugin, RouterRequest, RouterResponse, ServiceBuilderExt}; -use http::header::{self, HeaderName}; +use http::header; use http::{HeaderMap, StatusCode}; use schemars::JsonSchema; use serde::Deserialize; @@ -10,17 +10,28 @@ use tower::{BoxError, ServiceBuilder, ServiceExt}; #[derive(Deserialize, Debug, Clone, JsonSchema)] #[serde(deny_unknown_fields)] struct CSRFConfig { + /// CSRFConfig is enabled by default; + /// set disabled = true to disable the plugin behavior #[serde(default)] disabled: bool, + /// Override the headers to check for by setting + /// custom_headers + /// Note that if you set recommended_headers here, + /// you may also want to have a look at your `CORS` configuration, + /// and make sure you either: + /// - did not set any `allow_headers` list (so it defaults to `mirror_request`) + /// - added your recommended headers to the allow_headers list, as shown in the + /// `examples/cors-and-csrf/*.router.yaml` files. + #[serde(default = "apollo_custom_preflight_headers")] + recommended_headers: Vec, } -static NON_PREFLIGHTED_HEADER_NAMES: &[HeaderName] = &[ - header::ACCEPT, - header::ACCEPT_LANGUAGE, - header::CONTENT_LANGUAGE, - header::CONTENT_TYPE, - header::RANGE, -]; +fn apollo_custom_preflight_headers() -> Vec { + vec![ + "x-apollo-operation-name".to_string(), + "apollo-require-preflight".to_string(), + ] +} static NON_PREFLIGHTED_CONTENT_TYPES: &[&str] = &[ "application/x-www-form-urlencoded", @@ -46,17 +57,18 @@ impl Plugin for Csrf { service: BoxService, ) -> BoxService { if !self.config.disabled { + let recommended_headers = self.config.recommended_headers.clone(); ServiceBuilder::new() .checkpoint(move |req: RouterRequest| { - if should_accept(&req) { + if should_accept(&req, recommended_headers.as_slice()) { Ok(ControlFlow::Continue(req)) } else { let error = crate::Error { message: format!("This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \ Please either specify a 'content-type' header (with a mime-type that is not one of {}) \ - or provide a header such that the request is preflighted: \ - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests", - NON_PREFLIGHTED_CONTENT_TYPES.join(",")), + or provide one of the following headers: {}", + NON_PREFLIGHTED_CONTENT_TYPES.join(", "), + recommended_headers.join(", ")), locations: Default::default(), path: Default::default(), extensions: Default::default(), @@ -77,34 +89,42 @@ impl Plugin for Csrf { } } -fn should_accept(req: &RouterRequest) -> bool { +fn should_accept(req: &RouterRequest, recommended_headers: &[String]) -> bool { let headers = req.originating_request.headers(); - headers_require_preflight(headers) || content_type_requires_preflight(headers) + content_type_requires_preflight(headers) + || recommended_header_is_provided(headers, recommended_headers) } -fn headers_require_preflight(headers: &HeaderMap) -> bool { - headers - .keys() - .any(|header_name| !NON_PREFLIGHTED_HEADER_NAMES.contains(header_name)) +fn recommended_header_is_provided(headers: &HeaderMap, recommended_headers: &[String]) -> bool { + recommended_headers + .iter() + .any(|header| headers.get(header).is_some()) } fn content_type_requires_preflight(headers: &HeaderMap) -> bool { headers - .get(header::CONTENT_TYPE) - .map(|content_type| { + .get_all(header::CONTENT_TYPE) + .iter() + .any(|content_type| { if let Ok(as_str) = content_type.to_str() { - if let Ok(mime_type) = as_str.parse::() { - return !NON_PREFLIGHTED_CONTENT_TYPES.contains(&mime_type.essence_str()); + // https://github.com/apollographql/router/pull/1006#discussion_r869777439 + let trimmed_and_tabs_removed = as_str.trim().replace("\t", " "); + if let Ok(mime_type) = trimmed_and_tabs_removed.parse::() { + !NON_PREFLIGHTED_CONTENT_TYPES.contains(&mime_type.essence_str()) + } else { + // If we get here, this means that we couldn't parse the content-type value into + // a valid mime type... which would be safe enough for us to assume preflight was triggered if the `mime` + // crate followed the fetch specification, but it unfortunately doesn't (see comment above). + // + // Better safe than sorry, we will mark this content_type value as not sufficient to have triggered preflight + false } + } else { + // If we get here, this means that turning the content-type header value + // into a string failed (ie it's not valid UTF-8) which would lead to a preflight. + true } - // If we get here, this means that either turning the content-type header value - // into a string failed (ie it's not valid UTF-8), or we couldn't parse it into - // a valid mime type... which is actually *ok* because that would lead to a preflight. - // (That said, it would also be reasonable to reject such requests with provided - // yet unparsable Content-Type here.) - true }) - .unwrap_or(false) } register_plugin!("apollo", "csrf", Csrf); @@ -147,10 +167,13 @@ mod csrf_tests { let mock = mock_service.build(); - let mut service_stack = Csrf::new(CSRFConfig { disabled: false }) - .await - .unwrap() - .router_service(mock.boxed()); + let mut service_stack = Csrf::new(CSRFConfig { + disabled: false, + recommended_headers: apollo_custom_preflight_headers(), + }) + .await + .unwrap() + .router_service(mock.boxed()); let with_preflight_content_type = RouterRequest::fake_builder() .headers( @@ -169,16 +192,11 @@ mod csrf_tests { .await .unwrap(); - match res.response.into_body() { - ResponseBody::GraphQL(res) => { - assert_eq!(res.data.unwrap(), expected_response_data); - } - other => panic!("expected graphql response, found {:?}", other), - } + assert_data(res, expected_response_data.clone()); let with_preflight_header = RouterRequest::fake_builder() .headers( - [("x-this-is-a-custom-header".into(), "this-is-a-test".into())] + [("apollo-require-preflight".into(), "this-is-a-test".into())] .into_iter() .collect(), ) @@ -187,22 +205,20 @@ mod csrf_tests { let res = service_stack.oneshot(with_preflight_header).await.unwrap(); - match res.response.into_body() { - ResponseBody::GraphQL(res) => { - assert_eq!(res.data.unwrap(), expected_response_data); - } - other => panic!("expected graphql response, found {:?}", other), - } + assert_data(res, expected_response_data); } #[tokio::test] async fn it_rejects_non_preflighted_headers_request() { let mock = MockRouterService::new().build(); - let service_stack = Csrf::new(CSRFConfig { disabled: false }) - .await - .unwrap() - .router_service(mock.boxed()); + let service_stack = Csrf::new(CSRFConfig { + disabled: false, + recommended_headers: apollo_custom_preflight_headers(), + }) + .await + .unwrap() + .router_service(mock.boxed()); let non_preflighted_request = RouterRequest::fake_builder().build().unwrap(); @@ -211,24 +227,20 @@ mod csrf_tests { .await .unwrap(); - match res.response.into_body() { - ResponseBody::GraphQL(res) => { - assert_eq!(res.errors[0].message, "This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \ - Please either specify a 'content-type' header (with a mime-type that is not one of application/x-www-form-urlencoded,multipart/form-data,text/plain) \ - or provide a header such that the request is preflighted: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests"); - } - other => panic!("expected graphql response, found {:?}", other), - } + assert_error(res); } #[tokio::test] async fn it_rejects_non_preflighted_content_type_request() { let mock = MockRouterService::new().build(); - let service_stack = Csrf::new(CSRFConfig { disabled: false }) - .await - .unwrap() - .router_service(mock.boxed()); + let service_stack = Csrf::new(CSRFConfig { + disabled: false, + recommended_headers: apollo_custom_preflight_headers(), + }) + .await + .unwrap() + .router_service(mock.boxed()); let non_preflighted_request = RouterRequest::fake_builder() .headers( @@ -244,14 +256,7 @@ mod csrf_tests { .await .unwrap(); - match res.response.into_body() { - ResponseBody::GraphQL(res) => { - assert_eq!(res.errors[0].message, "This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \ - Please either specify a 'content-type' header (with a mime-type that is not one of application/x-www-form-urlencoded,multipart/form-data,text/plain) \ - or provide a header such that the request is preflighted: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests"); - } - other => panic!("expected graphql response, found {:?}", other), - } + assert_error(res); } #[tokio::test] @@ -267,10 +272,13 @@ mod csrf_tests { let mock = mock_service.build(); - let service_stack = Csrf::new(CSRFConfig { disabled: true }) - .await - .unwrap() - .router_service(mock.boxed()); + let service_stack = Csrf::new(CSRFConfig { + disabled: true, + recommended_headers: apollo_custom_preflight_headers(), + }) + .await + .unwrap() + .router_service(mock.boxed()); let non_preflighted_request = RouterRequest::fake_builder().build().unwrap(); @@ -279,6 +287,10 @@ mod csrf_tests { .await .unwrap(); + assert_data(res, expected_response_data); + } + + fn assert_data(res: RouterResponse, expected_response_data: serde_json_bytes::Value) { match res.response.into_body() { ResponseBody::GraphQL(res) => { assert_eq!(res.data.unwrap(), expected_response_data); @@ -286,4 +298,23 @@ mod csrf_tests { other => panic!("expected graphql response, found {:?}", other), } } + + fn assert_error(res: RouterResponse) { + match res.response.into_body() { + ResponseBody::GraphQL(res) => { + assert_eq!( + 1, + res.errors.len(), + "expected one(1) error in the RouterResponse, found {}\n{:?}", + res.errors.len(), + res.errors + ); + assert_eq!(res.errors[0].message, "This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \ + Please either specify a 'content-type' header \ + (with a mime-type that is not one of application/x-www-form-urlencoded, multipart/form-data, text/plain) \ + or provide one of the following headers: x-apollo-operation-name, apollo-require-preflight"); + } + other => panic!("expected graphql response, found {:?}", other), + } + } } diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index 7277342f82..c1921f85a3 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -80,7 +80,7 @@ tokio = { version = "1.17.0", features = ["full"] } tokio-util = { version = "0.7.1", features = ["net", "codec"] } tonic = { version = "0.6.2", features = ["transport", "tls"] } tower = { version = "0.4.12", features = ["full"] } -tower-http = { version = "0.2.5", features = ["trace", "cors"] } +tower-http = { version = "0.3.3", features = ["trace", "cors"] } tower-service = "0.3.1" tracing = "0.1.34" tracing-core = "0.1.26" diff --git a/apollo-router/src/axum_http_server_factory.rs b/apollo-router/src/axum_http_server_factory.rs index a1e89ffb1c..3447a1f5f8 100644 --- a/apollo-router/src/axum_http_server_factory.rs +++ b/apollo-router/src/axum_http_server_factory.rs @@ -1284,7 +1284,10 @@ mod tests { .header(ACCEPT, "text/html") .header(ORIGIN, "http://studio") .header(ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header(ACCESS_CONTROL_REQUEST_HEADERS, "Content-type") + .header( + ACCESS_CONTROL_REQUEST_HEADERS, + "Content-type, x-an-other-test-header, apollo-require-preflight", + ) .send() .await .unwrap(); @@ -1295,11 +1298,12 @@ mod tests { vec!["http://studio"], "Incorrect access control allow origin header" ); + let headers = response.headers().get_all(ACCESS_CONTROL_ALLOW_HEADERS); assert_header_contains!( &response, ACCESS_CONTROL_ALLOW_HEADERS, - &["content-type"], - "Incorrect access control allow header header" + &["Content-type, x-an-other-test-header, apollo-require-preflight"], + "Incorrect access control allow header header {headers:?}" ); assert_header_contains!( &response, diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index 4b0998df64..913f57a600 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -20,7 +20,7 @@ use std::fmt; use std::net::SocketAddr; use std::str::FromStr; use thiserror::Error; -use tower_http::cors::{Any, CorsLayer, Origin}; +use tower_http::cors::{self, CorsLayer}; use typed_builder::TypedBuilder; /// Configuration error. @@ -321,10 +321,19 @@ pub struct Cors { pub allow_credentials: Option, /// The headers to allow. - /// Defaults to the required request header for Apollo Studio - #[serde(default = "default_cors_headers")] - #[builder(default_code = "default_cors_headers()")] - pub allow_headers: Vec, + /// If this is not set, we will default to + /// the `mirror_request` mode, which mirrors the received + /// `access-control-request-headers` preflight has sent. + /// + /// Note that if you set headers here, + /// you also want to have a look at your `CSRF` plugins configuration, + /// and make sure you either: + /// - accept `x-apollo-operation-name` AND / OR `apollo-require-preflight` + /// - defined `csrf` recommended headers in your yml configuration, as shown in the + /// `examples/cors-and-csrf/*.router.yaml` files. + #[serde(default)] + #[builder(default)] + pub allow_headers: Option>, #[serde(default)] #[builder(default)] @@ -348,16 +357,6 @@ fn default_origins() -> Vec { vec!["https://studio.apollographql.com".into()] } -fn default_cors_headers() -> Vec { - vec![ - "Content-Type".into(), - "apollographql-client-name".into(), - "apollographql-client-version".into(), - "x-apollo-operation-name".into(), - "apollo-require-preflight".into(), - ] -} - fn default_cors_methods() -> Vec { vec!["GET".into(), "POST".into(), "OPTIONS".into()] } @@ -382,41 +381,50 @@ impl Default for Server { impl Cors { pub fn into_layer(self) -> CorsLayer { - let cors = - CorsLayer::new() - .allow_credentials(self.allow_credentials.unwrap_or_default()) - .allow_headers(self.allow_headers.iter().filter_map(|header| { - header - .parse() - .map_err(|_| tracing::error!("header name '{header}' is not valid")) - .ok() - })) - .expose_headers(self.expose_headers.unwrap_or_default().iter().filter_map( - |header| { + let allow_headers = if let Some(headers_to_allow) = self.allow_headers { + cors::AllowHeaders::list(headers_to_allow.iter().filter_map(|header| { + header + .parse() + .map_err(|_| tracing::error!("header name '{header}' is not valid")) + .ok() + })) + } else { + cors::AllowHeaders::mirror_request() + }; + let cors = CorsLayer::new() + .allow_credentials(self.allow_credentials.unwrap_or_default()) + .allow_headers(allow_headers) + .expose_headers(cors::ExposeHeaders::list( + self.expose_headers + .unwrap_or_default() + .iter() + .filter_map(|header| { header .parse() .map_err(|_| tracing::error!("header name '{header}' is not valid")) .ok() - }, - )) - .allow_methods(self.methods.iter().filter_map(|method| { + }), + )) + .allow_methods(cors::AllowMethods::list(self.methods.iter().filter_map( + |method| { method .parse() .map_err(|_| tracing::error!("method '{method}' is not valid")) .ok() - })); + }, + ))); if self.allow_any_origin.unwrap_or_default() { - cors.allow_origin(Any) + cors.allow_origin(cors::Any) } else { - cors.allow_origin(Origin::list(self.origins.into_iter().filter_map( - |origin| { + cors.allow_origin(cors::AllowOrigin::list( + self.origins.into_iter().filter_map(|origin| { origin .parse() .map_err(|_| tracing::error!("origin '{origin}' is not valid")) .ok() - }, - ))) + }), + )) } } } @@ -760,6 +768,11 @@ mod tests { !cors.allow_any_origin.unwrap_or_default(), "Allow any origin should be disabled by default" ); + + assert!( + cors.allow_headers.is_none(), + "No allow_headers list should be present by default" + ); } #[test] diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 23e0b97f9c..dc72e688bc 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -12,8 +12,20 @@ expression: "&schema" "type": "object", "properties": { "disabled": { + "description": "CSRFConfig is enabled by default; set disabled = true to disable the plugin behavior", "default": false, "type": "boolean" + }, + "recommended_headers": { + "description": "Override the headers to check for by setting custom_headers Note that if you set recommended_headers here, you may also want to have a look at your `CORS` configuration, and make sure you either: - did not set any `allow_headers` list (so it defaults to `mirror_request`) - added your recommended headers to the allow_headers list, as shown in the `examples/cors-and-csrf/*.router.yaml` files.", + "default": [ + "x-apollo-operation-name", + "apollo-require-preflight" + ], + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -357,18 +369,13 @@ expression: "&schema" "nullable": true }, "allow_headers": { - "description": "The headers to allow. Defaults to the required request header for Apollo Studio", - "default": [ - "Content-Type", - "apollographql-client-name", - "apollographql-client-version", - "x-apollo-operation-name", - "apollo-require-preflight" - ], + "description": "The headers to allow. If this is not set, we will default to the `mirror_request` mode, which mirrors the received `access-control-request-headers` preflight has sent.\n\nNote that if you set headers here, you also want to have a look at your `CSRF` plugins configuration, and make sure you either: - accept `x-apollo-operation-name` AND / OR `apollo-require-preflight` - defined `csrf` recommended headers in your yml configuration, as shown in the `examples/cors-and-csrf/*.router.yaml` files.", + "default": null, "type": "array", "items": { "type": "string" - } + }, + "nullable": true }, "expose_headers": { "description": "Which response headers should be made available to scripts running in the browser, in response to a cross-origin request.", diff --git a/examples/cors-and-csrf/custom-headers.router.yaml b/examples/cors-and-csrf/custom-headers.router.yaml new file mode 100644 index 0000000000..73681b5243 --- /dev/null +++ b/examples/cors-and-csrf/custom-headers.router.yaml @@ -0,0 +1,25 @@ +server: + listen: localhost:4000 + cors: + # adding the headers in the CSRF section + # and not here would fail the OPTIONS preflight + # you can: + # - either add them here to be explicit + # - or not set the `allow_headers` key at all. + # the default behavior is `mirror_request`, + # which mirrors the received + # `access-control-request-headers` preflight has sent + allow_headers: + - x-my-custom-recommended-header + - x-and-an-other-recommended-header + - content-type # and many more! +csrf: + # you could set disabled: false, but That wouldn't be safe https://developer.mozilla.org/en-US/docs/Glossary/CSRF + + # pick recommended headers that are custom enough + # to make sure browsers would need to have added them with javascript, + # which would trigger a preflight https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors + recommended_headers: + - x-my-custom-recommended-header # we will look for presence of this header... + - x-and-an-other-recommended-header # ...and that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests + From a76847c71d9c82302c44ab5d7d6bfc02e88da0e2 Mon Sep 17 00:00:00 2001 From: o0Ignition0o Date: Wed, 11 May 2022 18:38:29 +0200 Subject: [PATCH 03/19] compliance --- apollo-router-core/src/plugins/csrf.rs | 2 +- licenses.html | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs index 9a59145f52..76da1de98e 100644 --- a/apollo-router-core/src/plugins/csrf.rs +++ b/apollo-router-core/src/plugins/csrf.rs @@ -108,7 +108,7 @@ fn content_type_requires_preflight(headers: &HeaderMap) -> bool { .any(|content_type| { if let Ok(as_str) = content_type.to_str() { // https://github.com/apollographql/router/pull/1006#discussion_r869777439 - let trimmed_and_tabs_removed = as_str.trim().replace("\t", " "); + let trimmed_and_tabs_removed = as_str.trim().replace('\t', " "); if let Ok(mime_type) = trimmed_and_tabs_removed.parse::() { !NON_PREFLIGHTED_CONTENT_TYPES.contains(&mime_type.essence_str()) } else { diff --git a/licenses.html b/licenses.html index 5fa2d7573f..35b85b1ef6 100644 --- a/licenses.html +++ b/licenses.html @@ -11307,7 +11307,6 @@

MIT License

Used by:

Copyright (c) 2019-2021 Tower Contributors
 

From ff83523a5fa913872d4edd3041841dec8c1b62fb Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Wed, 11 May 2022 18:56:27 +0200
Subject: [PATCH 04/19] remove noise from the custom-headers.router.yml file

---
 examples/cors-and-csrf/custom-headers.router.yaml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/examples/cors-and-csrf/custom-headers.router.yaml b/examples/cors-and-csrf/custom-headers.router.yaml
index 73681b5243..b1235fa9e9 100644
--- a/examples/cors-and-csrf/custom-headers.router.yaml
+++ b/examples/cors-and-csrf/custom-headers.router.yaml
@@ -1,5 +1,4 @@
 server:
-  listen: localhost:4000
   cors:
     # adding the headers in the CSRF section
     # and not here would fail the OPTIONS preflight

From 5f758445da6948a4ec980fc97859f77e24a413d1 Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Wed, 11 May 2022 21:06:22 +0200
Subject: [PATCH 05/19] recommended -> required + get closer to the fetch
 specification when it comes to content type parsing

---
 apollo-router-core/src/plugins/csrf.rs        | 77 +++++++++++--------
 ...nfiguration__tests__schema_generation.snap |  4 +-
 .../cors-and-csrf/custom-headers.router.yaml  | 12 +--
 3 files changed, 51 insertions(+), 42 deletions(-)

diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs
index 76da1de98e..92313041bf 100644
--- a/apollo-router-core/src/plugins/csrf.rs
+++ b/apollo-router-core/src/plugins/csrf.rs
@@ -16,14 +16,14 @@ struct CSRFConfig {
     disabled: bool,
     /// Override the headers to check for by setting
     /// custom_headers
-    /// Note that if you set recommended_headers here,
+    /// Note that if you set required_headers here,
     /// you may also want to have a look at your `CORS` configuration,
     /// and make sure you either:
     /// - did not set any `allow_headers` list (so it defaults to `mirror_request`)
-    /// - added your recommended headers to the allow_headers list, as shown in the
+    /// - added your required headers to the allow_headers list, as shown in the
     /// `examples/cors-and-csrf/*.router.yaml` files.
     #[serde(default = "apollo_custom_preflight_headers")]
-    recommended_headers: Vec,
+    required_headers: Vec,
 }
 
 fn apollo_custom_preflight_headers() -> Vec {
@@ -57,10 +57,10 @@ impl Plugin for Csrf {
         service: BoxService,
     ) -> BoxService {
         if !self.config.disabled {
-            let recommended_headers = self.config.recommended_headers.clone();
+            let required_headers = self.config.required_headers.clone();
             ServiceBuilder::new()
                 .checkpoint(move |req: RouterRequest| {
-                    if should_accept(&req, recommended_headers.as_slice()) {
+                    if should_accept(&req, required_headers.as_slice()) {
                         Ok(ControlFlow::Continue(req))
                     } else {
                         let error = crate::Error {
@@ -68,7 +68,7 @@ impl Plugin for Csrf {
                             Please either specify a 'content-type' header (with a mime-type that is not one of {}) \
                             or provide one of the following headers: {}", 
                             NON_PREFLIGHTED_CONTENT_TYPES.join(", "),
-                            recommended_headers.join(", ")),
+                            required_headers.join(", ")),
                             locations: Default::default(),
                             path: Default::default(),
                             extensions: Default::default(),
@@ -89,42 +89,51 @@ impl Plugin for Csrf {
     }
 }
 
-fn should_accept(req: &RouterRequest, recommended_headers: &[String]) -> bool {
+fn should_accept(req: &RouterRequest, required_headers: &[String]) -> bool {
     let headers = req.originating_request.headers();
     content_type_requires_preflight(headers)
-        || recommended_header_is_provided(headers, recommended_headers)
+        || recommended_header_is_provided(headers, required_headers)
 }
 
-fn recommended_header_is_provided(headers: &HeaderMap, recommended_headers: &[String]) -> bool {
-    recommended_headers
+fn recommended_header_is_provided(headers: &HeaderMap, required_headers: &[String]) -> bool {
+    required_headers
         .iter()
         .any(|header| headers.get(header).is_some())
 }
 
 fn content_type_requires_preflight(headers: &HeaderMap) -> bool {
-    headers
+    let joined_content_type_header_value = if let Ok(combined_headers) = headers
         .get_all(header::CONTENT_TYPE)
         .iter()
-        .any(|content_type| {
-            if let Ok(as_str) = content_type.to_str() {
-                // https://github.com/apollographql/router/pull/1006#discussion_r869777439
-                let trimmed_and_tabs_removed = as_str.trim().replace('\t', " ");
-                if let Ok(mime_type) = trimmed_and_tabs_removed.parse::() {
-                    !NON_PREFLIGHTED_CONTENT_TYPES.contains(&mime_type.essence_str())
-                } else {
-                    // If we get here, this means that we couldn't parse the content-type value into
-                    // a valid mime type... which would be safe enough for us to assume preflight was triggered if the `mime`
-                    // crate followed the fetch specification, but it unfortunately doesn't (see comment above).
-                    //
-                    // Better safe than sorry, we will mark this content_type value as not sufficient to have triggered preflight
-                    false
-                }
-            } else {
-                // If we get here, this means that turning the content-type header value
-                // into a string failed (ie it's not valid UTF-8) which would lead to a preflight.
-                true
-            }
+        .map(|header_value| {
+            // https://github.com/apollographql/router/pull/1006#discussion_r869777439
+            header_value
+                .to_str()
+                .map(|as_str| as_str.trim().replace('\u{0009}', "\u{0020}")) // replace tab with space
         })
+        .collect::, _>>()
+    {
+        // https://fetch.spec.whatwg.org/#concept-header-list-combine
+        combined_headers.join("\u{002C}\u{0020}") // ', '
+    } else {
+        // We couldn't parse a header value, let's err on the side of caution here
+        return false;
+    };
+
+    dbg!("'\u{002C}\u{0020}'");
+
+    if let Ok(mime_type) = joined_content_type_header_value.parse::() {
+        // If we get here, this means that we couldn't parse the content-type value into
+        // a valid mime type... which would be safe enough for us to assume preflight was triggered if the `mime`
+        // crate followed the fetch specification, but it unfortunately doesn't (see comment above).
+        //
+        // Better safe than sorry, we will claim we don't have solid enough reasons
+        // to believe the request will have triggered preflight
+
+        !NON_PREFLIGHTED_CONTENT_TYPES.contains(&mime_type.essence_str())
+    } else {
+        false
+    }
 }
 
 register_plugin!("apollo", "csrf", Csrf);
@@ -169,7 +178,7 @@ mod csrf_tests {
 
         let mut service_stack = Csrf::new(CSRFConfig {
             disabled: false,
-            recommended_headers: apollo_custom_preflight_headers(),
+            required_headers: apollo_custom_preflight_headers(),
         })
         .await
         .unwrap()
@@ -214,7 +223,7 @@ mod csrf_tests {
 
         let service_stack = Csrf::new(CSRFConfig {
             disabled: false,
-            recommended_headers: apollo_custom_preflight_headers(),
+            required_headers: apollo_custom_preflight_headers(),
         })
         .await
         .unwrap()
@@ -236,7 +245,7 @@ mod csrf_tests {
 
         let service_stack = Csrf::new(CSRFConfig {
             disabled: false,
-            recommended_headers: apollo_custom_preflight_headers(),
+            required_headers: apollo_custom_preflight_headers(),
         })
         .await
         .unwrap()
@@ -274,7 +283,7 @@ mod csrf_tests {
 
         let service_stack = Csrf::new(CSRFConfig {
             disabled: true,
-            recommended_headers: apollo_custom_preflight_headers(),
+            required_headers: apollo_custom_preflight_headers(),
         })
         .await
         .unwrap()
diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
index dc72e688bc..13cc47e169 100644
--- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
+++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
@@ -16,8 +16,8 @@ expression: "&schema"
           "default": false,
           "type": "boolean"
         },
-        "recommended_headers": {
-          "description": "Override the headers to check for by setting custom_headers Note that if you set recommended_headers here, you may also want to have a look at your `CORS` configuration, and make sure you either: - did not set any `allow_headers` list (so it defaults to `mirror_request`) - added your recommended headers to the allow_headers list, as shown in the `examples/cors-and-csrf/*.router.yaml` files.",
+        "required_headers": {
+          "description": "Override the headers to check for by setting custom_headers Note that if you set required_headers here, you may also want to have a look at your `CORS` configuration, and make sure you either: - did not set any `allow_headers` list (so it defaults to `mirror_request`) - added your required headers to the allow_headers list, as shown in the `examples/cors-and-csrf/*.router.yaml` files.",
           "default": [
             "x-apollo-operation-name",
             "apollo-require-preflight"
diff --git a/examples/cors-and-csrf/custom-headers.router.yaml b/examples/cors-and-csrf/custom-headers.router.yaml
index b1235fa9e9..5ed3a255eb 100644
--- a/examples/cors-and-csrf/custom-headers.router.yaml
+++ b/examples/cors-and-csrf/custom-headers.router.yaml
@@ -9,16 +9,16 @@ server:
     # which mirrors the received
     # `access-control-request-headers` preflight has sent
     allow_headers:
-      - x-my-custom-recommended-header
-      - x-and-an-other-recommended-header
+      - x-my-custom-required-header
+      - x-and-an-other-required-header
       - content-type # and many more!
 csrf:
   # you could set disabled: false, but That wouldn't be safe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
 
-  # pick recommended headers that are custom enough
+  # pick required headers that are custom enough
   # to make sure browsers would need to have added them with javascript,
   # which would trigger a preflight https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors
-  recommended_headers:
-    - x-my-custom-recommended-header # we will look for presence of this header...
-    - x-and-an-other-recommended-header # ...and that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
+  required_headers:
+    - x-my-custom-required-header # we will look for presence of this header...
+    - x-and-an-other-required-header # ...or that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
 

From 579eac0dc0e3b6dd46e26a39fb770cbe4a88a17d Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Wed, 11 May 2022 21:39:35 +0200
Subject: [PATCH 06/19] wip, add a bunch of comments

---
 apollo-router-core/src/plugins/csrf.rs        | 51 ++++++++++++++-----
 .../cors-and-csrf/custom-headers.router.yaml  |  1 +
 2 files changed, 38 insertions(+), 14 deletions(-)

diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs
index 92313041bf..4feb36131c 100644
--- a/apollo-router-core/src/plugins/csrf.rs
+++ b/apollo-router-core/src/plugins/csrf.rs
@@ -39,6 +39,7 @@ static NON_PREFLIGHTED_CONTENT_TYPES: &[&str] = &[
     "text/plain",
 ];
 
+/// The Csrf plugin makes sure any request received would have been preflighted if it was sent by a browser.
 #[derive(Debug, Clone)]
 struct Csrf {
     config: CSRFConfig,
@@ -60,7 +61,7 @@ impl Plugin for Csrf {
             let required_headers = self.config.required_headers.clone();
             ServiceBuilder::new()
                 .checkpoint(move |req: RouterRequest| {
-                    if should_accept(&req, required_headers.as_slice()) {
+                    if is_preflighted(&req, required_headers.as_slice()) {
                         Ok(ControlFlow::Continue(req))
                     } else {
                         let error = crate::Error {
@@ -89,23 +90,34 @@ impl Plugin for Csrf {
     }
 }
 
-fn should_accept(req: &RouterRequest, required_headers: &[String]) -> bool {
+// A `preflighted` request is the opposite of a `simple` request.
+//
+// A simple request is a request that satisfies the three predicates below:
+// - Has method `GET` `POST` or `HEAD` (which turns out to be the three methods our web server allows)
+// - If content-type is set, it must be with a mime type that is application/x-www-form-urlencoded OR multipart/form-data OR text/plain
+// - The only headers added by javascript code are part of the cors safelisted request headers (Accept,Accept-Language,Content-Language,Content-Type, and simple Range
+//
+// Given the first step is covered in our web browser, we'll take care of the two other steps below:
+fn is_preflighted(req: &RouterRequest, required_headers: &[String]) -> bool {
     let headers = req.originating_request.headers();
     content_type_requires_preflight(headers)
         || recommended_header_is_provided(headers, required_headers)
 }
 
-fn recommended_header_is_provided(headers: &HeaderMap, required_headers: &[String]) -> bool {
-    required_headers
-        .iter()
-        .any(|header| headers.get(header).is_some())
-}
-
+// Part two of the algorithm above:
+// If content-type is set, it must be with a mime type that is application/x-www-form-urlencoded OR multipart/form-data OR text/plain
+// The details of the algorithm are covered in the fetch specification https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+//
+// content_type_requires_preflight will thus return true if
+// the header value is !(`application/x-www-form-urlencoded` || `multipart/form-data` || `text/plain`)
 fn content_type_requires_preflight(headers: &HeaderMap) -> bool {
     let joined_content_type_header_value = if let Ok(combined_headers) = headers
         .get_all(header::CONTENT_TYPE)
         .iter()
         .map(|header_value| {
+            // The mime type parser we're using is a bit askew,
+            // so we're going to perform a bit of trimming, and character replacement
+            // before we combine the header values 
             // https://github.com/apollographql/router/pull/1006#discussion_r869777439
             header_value
                 .to_str()
@@ -120,27 +132,38 @@ fn content_type_requires_preflight(headers: &HeaderMap) -> bool {
         return false;
     };
 
-    dbg!("'\u{002C}\u{0020}'");
-
     if let Ok(mime_type) = joined_content_type_header_value.parse::() {
+        !NON_PREFLIGHTED_CONTENT_TYPES.contains(&mime_type.essence_str())
+    } else {
         // If we get here, this means that we couldn't parse the content-type value into
         // a valid mime type... which would be safe enough for us to assume preflight was triggered if the `mime`
         // crate followed the fetch specification, but it unfortunately doesn't (see comment above).
         //
         // Better safe than sorry, we will claim we don't have solid enough reasons
         // to believe the request will have triggered preflight
-
-        !NON_PREFLIGHTED_CONTENT_TYPES.contains(&mime_type.essence_str())
-    } else {
         false
     }
 }
 
+// Part three of the algorithm described above:
+// The only headers added by javascript code are part of the cors safelisted request headers (Accept,Accept-Language,Content-Language,Content-Type, and simple Range
+//
+// It would be pretty hard for us to keep track of the headers browser send themselves,
+// and the ones that were explicitely added by a javascript client (and have thus triggered preflight).
+// so we will do the oposite:
+// We hereby challenge any client to provide one of the required_headers.
+// Browsers definitely will not add any "x-apollo-operation-name" or "apollo-require-preflight" to every request anytime soon,
+// which means if the header is present, javascript has added it, and the browser will have triggered preflight. 
+fn recommended_header_is_provided(headers: &HeaderMap, required_headers: &[String]) -> bool {
+    required_headers
+        .iter()
+        .any(|header| headers.get(header).is_some())
+}
+
 register_plugin!("apollo", "csrf", Csrf);
 
 #[cfg(test)]
 mod csrf_tests {
-
     #[tokio::test]
     async fn plugin_registered() {
         crate::plugins()
diff --git a/examples/cors-and-csrf/custom-headers.router.yaml b/examples/cors-and-csrf/custom-headers.router.yaml
index 5ed3a255eb..0c68bfebd8 100644
--- a/examples/cors-and-csrf/custom-headers.router.yaml
+++ b/examples/cors-and-csrf/custom-headers.router.yaml
@@ -18,6 +18,7 @@ csrf:
   # pick required headers that are custom enough
   # to make sure browsers would need to have added them with javascript,
   # which would trigger a preflight https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors
+  # `content-type` is not a good idea for example.
   required_headers:
     - x-my-custom-required-header # we will look for presence of this header...
     - x-and-an-other-required-header # ...or that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests

From 00cf251d0efd4312d7e6b242a29d316937ebd8cb Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Wed, 11 May 2022 21:51:32 +0200
Subject: [PATCH 07/19] more comments

---
 apollo-router-core/src/plugins/csrf.rs | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs
index 4feb36131c..d4f5bd8a3a 100644
--- a/apollo-router-core/src/plugins/csrf.rs
+++ b/apollo-router-core/src/plugins/csrf.rs
@@ -40,6 +40,29 @@ static NON_PREFLIGHTED_CONTENT_TYPES: &[&str] = &[
 ];
 
 /// The Csrf plugin makes sure any request received would have been preflighted if it was sent by a browser.
+///
+/// Quoting the great apollo server comment:
+/// https://github.com/apollographql/apollo-server/blob/12bf5fc8ef305caa6a8848e37f862d32dae5957f/packages/server/src/preventCsrf.ts#L26 
+///
+/// We don't want random websites to be able to execute actual GraphQL operations
+/// from a user's browser unless our CORS policy supports it. It's not good
+/// enough just to ensure that the browser can't read the response from the
+/// operation; we also want to prevent CSRF, where the attacker can cause side
+/// effects with an operation or can measure the timing of a read operation. Our
+/// goal is to ensure that we don't run the context function or execute the
+/// GraphQL operation until the browser has evaluated the CORS policy, which
+/// means we want all operations to be pre-flighted. We can do that by only
+/// processing operations that have at least one header set that appears to be
+/// manually set by the JS code rather than by the browser automatically.
+///
+/// POST requests generally have a content-type `application/json`, which is
+/// sufficient to trigger preflighting. So we take extra care with requests that
+/// specify no content-type or that specify one of the three non-preflighted
+/// content types. For those operations, we require (if this feature is enabled)
+/// one of a set of specific headers to be set. By ensuring that every operation
+/// either has a custom content-type or sets one of these headers, we know we
+/// won't execute operations at the request of origins who our CORS policy will
+/// block.
 #[derive(Debug, Clone)]
 struct Csrf {
     config: CSRFConfig,

From 00769c2af6fee1d5c7010ac736744d58d8644b14 Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Wed, 11 May 2022 21:57:30 +0200
Subject: [PATCH 08/19] update documentation

---
 apollo-router-core/src/plugins/csrf.rs                 | 10 +++++-----
 apollo-router/src/configuration/mod.rs                 |  4 ++--
 ...outer__configuration__tests__schema_generation.snap |  6 +++---
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs
index d4f5bd8a3a..f7c6d3155e 100644
--- a/apollo-router-core/src/plugins/csrf.rs
+++ b/apollo-router-core/src/plugins/csrf.rs
@@ -10,7 +10,7 @@ use tower::{BoxError, ServiceBuilder, ServiceExt};
 #[derive(Deserialize, Debug, Clone, JsonSchema)]
 #[serde(deny_unknown_fields)]
 struct CSRFConfig {
-    /// CSRFConfig is enabled by default;
+    /// The CSRF plugin is enabled by default;
     /// set disabled = true to disable the plugin behavior
     #[serde(default)]
     disabled: bool,
@@ -21,7 +21,7 @@ struct CSRFConfig {
     /// and make sure you either:
     /// - did not set any `allow_headers` list (so it defaults to `mirror_request`)
     /// - added your required headers to the allow_headers list, as shown in the
-    /// `examples/cors-and-csrf/*.router.yaml` files.
+    /// `examples/cors-and-csrf/custom-headers.router.yaml` files.
     #[serde(default = "apollo_custom_preflight_headers")]
     required_headers: Vec,
 }
@@ -42,7 +42,7 @@ static NON_PREFLIGHTED_CONTENT_TYPES: &[&str] = &[
 /// The Csrf plugin makes sure any request received would have been preflighted if it was sent by a browser.
 ///
 /// Quoting the great apollo server comment:
-/// https://github.com/apollographql/apollo-server/blob/12bf5fc8ef305caa6a8848e37f862d32dae5957f/packages/server/src/preventCsrf.ts#L26 
+/// https://github.com/apollographql/apollo-server/blob/12bf5fc8ef305caa6a8848e37f862d32dae5957f/packages/server/src/preventCsrf.ts#L26
 ///
 /// We don't want random websites to be able to execute actual GraphQL operations
 /// from a user's browser unless our CORS policy supports it. It's not good
@@ -140,7 +140,7 @@ fn content_type_requires_preflight(headers: &HeaderMap) -> bool {
         .map(|header_value| {
             // The mime type parser we're using is a bit askew,
             // so we're going to perform a bit of trimming, and character replacement
-            // before we combine the header values 
+            // before we combine the header values
             // https://github.com/apollographql/router/pull/1006#discussion_r869777439
             header_value
                 .to_str()
@@ -176,7 +176,7 @@ fn content_type_requires_preflight(headers: &HeaderMap) -> bool {
 // so we will do the oposite:
 // We hereby challenge any client to provide one of the required_headers.
 // Browsers definitely will not add any "x-apollo-operation-name" or "apollo-require-preflight" to every request anytime soon,
-// which means if the header is present, javascript has added it, and the browser will have triggered preflight. 
+// which means if the header is present, javascript has added it, and the browser will have triggered preflight.
 fn recommended_header_is_provided(headers: &HeaderMap, required_headers: &[String]) -> bool {
     required_headers
         .iter()
diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs
index 913f57a600..6d551ba2a1 100644
--- a/apollo-router/src/configuration/mod.rs
+++ b/apollo-router/src/configuration/mod.rs
@@ -329,8 +329,8 @@ pub struct Cors {
     /// you also want to have a look at your `CSRF` plugins configuration,
     /// and make sure you either:
     /// - accept `x-apollo-operation-name` AND / OR `apollo-require-preflight`
-    /// - defined `csrf` recommended headers in your yml configuration, as shown in the
-    /// `examples/cors-and-csrf/*.router.yaml` files.
+    /// - defined `csrf` required headers in your yml configuration, as shown in the
+    /// `examples/cors-and-csrf/custom-headers.router.yaml` files.
     #[serde(default)]
     #[builder(default)]
     pub allow_headers: Option>,
diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
index 13cc47e169..1a7a756f47 100644
--- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
+++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
@@ -12,12 +12,12 @@ expression: "&schema"
       "type": "object",
       "properties": {
         "disabled": {
-          "description": "CSRFConfig is enabled by default; set disabled = true to disable the plugin behavior",
+          "description": "The CSRF plugin is enabled by default; set disabled = true to disable the plugin behavior",
           "default": false,
           "type": "boolean"
         },
         "required_headers": {
-          "description": "Override the headers to check for by setting custom_headers Note that if you set required_headers here, you may also want to have a look at your `CORS` configuration, and make sure you either: - did not set any `allow_headers` list (so it defaults to `mirror_request`) - added your required headers to the allow_headers list, as shown in the `examples/cors-and-csrf/*.router.yaml` files.",
+          "description": "Override the headers to check for by setting custom_headers Note that if you set required_headers here, you may also want to have a look at your `CORS` configuration, and make sure you either: - did not set any `allow_headers` list (so it defaults to `mirror_request`) - added your required headers to the allow_headers list, as shown in the `examples/cors-and-csrf/custom-headers.router.yaml` files.",
           "default": [
             "x-apollo-operation-name",
             "apollo-require-preflight"
@@ -369,7 +369,7 @@ expression: "&schema"
               "nullable": true
             },
             "allow_headers": {
-              "description": "The headers to allow. If this is not set, we will default to the `mirror_request` mode, which mirrors the received `access-control-request-headers` preflight has sent.\n\nNote that if you set headers here, you also want to have a look at your `CSRF` plugins configuration, and make sure you either: - accept `x-apollo-operation-name` AND / OR `apollo-require-preflight` - defined `csrf` recommended headers in your yml configuration, as shown in the `examples/cors-and-csrf/*.router.yaml` files.",
+              "description": "The headers to allow. If this is not set, we will default to the `mirror_request` mode, which mirrors the received `access-control-request-headers` preflight has sent.\n\nNote that if you set headers here, you also want to have a look at your `CSRF` plugins configuration, and make sure you either: - accept `x-apollo-operation-name` AND / OR `apollo-require-preflight` - defined `csrf` required headers in your yml configuration, as shown in the `examples/cors-and-csrf/custom-headers.router.yaml` files.",
               "default": null,
               "type": "array",
               "items": {

From 03ca032d77b50f3dc5cac58000b08917c7ad6c7f Mon Sep 17 00:00:00 2001
From: Simon Sapin 
Date: Thu, 12 May 2022 12:55:58 +0200
Subject: [PATCH 09/19] csrf tests: add helper functions for accepted/rejected
 requests

---
 apollo-router-core/src/plugins/csrf.rs | 131 ++++++++-----------------
 1 file changed, 40 insertions(+), 91 deletions(-)

diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs
index f7c6d3155e..fdfe63f5d3 100644
--- a/apollo-router-core/src/plugins/csrf.rs
+++ b/apollo-router-core/src/plugins/csrf.rs
@@ -33,6 +33,15 @@ fn apollo_custom_preflight_headers() -> Vec {
     ]
 }
 
+impl Default for CSRFConfig {
+    fn default() -> Self {
+        Self {
+            disabled: false,
+            required_headers: apollo_custom_preflight_headers(),
+        }
+    }
+}
+
 static NON_PREFLIGHTED_CONTENT_TYPES: &[&str] = &[
     "application/x-www-form-urlencoded",
     "multipart/form-data",
@@ -207,29 +216,11 @@ mod csrf_tests {
     use super::*;
     use crate::{plugin::utils::test::MockRouterService, ResponseBody};
     use serde_json_bytes::json;
-    use tower::{Service, ServiceExt};
+    use tower::ServiceExt;
 
     #[tokio::test]
     async fn it_lets_preflighted_request_pass_through() {
-        let expected_response_data = json!({ "test": 1234 });
-        let expected_response_data2 = expected_response_data.clone();
-        let mut mock_service = MockRouterService::new();
-        mock_service.expect_call().times(2).returning(move |_| {
-            RouterResponse::fake_builder()
-                .data(expected_response_data2.clone())
-                .build()
-        });
-
-        let mock = mock_service.build();
-
-        let mut service_stack = Csrf::new(CSRFConfig {
-            disabled: false,
-            required_headers: apollo_custom_preflight_headers(),
-        })
-        .await
-        .unwrap()
-        .router_service(mock.boxed());
-
+        let config = CSRFConfig::default();
         let with_preflight_content_type = RouterRequest::fake_builder()
             .headers(
                 [("content-type".into(), "application/json".into())]
@@ -238,16 +229,7 @@ mod csrf_tests {
             )
             .build()
             .unwrap();
-
-        let res = service_stack
-            .ready()
-            .await
-            .unwrap()
-            .call(with_preflight_content_type)
-            .await
-            .unwrap();
-
-        assert_data(res, expected_response_data.clone());
+        assert_accepted(config.clone(), with_preflight_content_type).await;
 
         let with_preflight_header = RouterRequest::fake_builder()
             .headers(
@@ -257,46 +239,19 @@ mod csrf_tests {
             )
             .build()
             .unwrap();
-
-        let res = service_stack.oneshot(with_preflight_header).await.unwrap();
-
-        assert_data(res, expected_response_data);
+        assert_accepted(config, with_preflight_header).await;
     }
 
     #[tokio::test]
     async fn it_rejects_non_preflighted_headers_request() {
-        let mock = MockRouterService::new().build();
-
-        let service_stack = Csrf::new(CSRFConfig {
-            disabled: false,
-            required_headers: apollo_custom_preflight_headers(),
-        })
-        .await
-        .unwrap()
-        .router_service(mock.boxed());
-
+        let config = CSRFConfig::default();
         let non_preflighted_request = RouterRequest::fake_builder().build().unwrap();
-
-        let res = service_stack
-            .oneshot(non_preflighted_request)
-            .await
-            .unwrap();
-
-        assert_error(res);
+        assert_rejected(config, non_preflighted_request).await
     }
 
     #[tokio::test]
     async fn it_rejects_non_preflighted_content_type_request() {
-        let mock = MockRouterService::new().build();
-
-        let service_stack = Csrf::new(CSRFConfig {
-            disabled: false,
-            required_headers: apollo_custom_preflight_headers(),
-        })
-        .await
-        .unwrap()
-        .router_service(mock.boxed());
-
+        let config = CSRFConfig::default();
         let non_preflighted_request = RouterRequest::fake_builder()
             .headers(
                 [("content-type".into(), "text/plain".into())]
@@ -305,56 +260,50 @@ mod csrf_tests {
             )
             .build()
             .unwrap();
-
-        let res = service_stack
-            .oneshot(non_preflighted_request)
-            .await
-            .unwrap();
-
-        assert_error(res);
+        assert_rejected(config, non_preflighted_request).await
     }
 
     #[tokio::test]
     async fn it_accepts_non_preflighted_headers_request_when_plugin_is_disabled() {
-        let expected_response_data = json!({ "test": 1234 });
-        let expected_response_data2 = expected_response_data.clone();
+        let config = CSRFConfig {
+            disabled: true,
+            ..Default::default()
+        };
+        let non_preflighted_request = RouterRequest::fake_builder().build().unwrap();
+        assert_accepted(config, non_preflighted_request).await
+    }
+
+    async fn assert_accepted(config: CSRFConfig, request: RouterRequest) {
         let mut mock_service = MockRouterService::new();
         mock_service.expect_call().times(1).returning(move |_| {
             RouterResponse::fake_builder()
-                .data(expected_response_data2.clone())
+                .data(json!({ "test": 1234_u32 }))
                 .build()
         });
 
         let mock = mock_service.build();
-
-        let service_stack = Csrf::new(CSRFConfig {
-            disabled: true,
-            required_headers: apollo_custom_preflight_headers(),
-        })
-        .await
-        .unwrap()
-        .router_service(mock.boxed());
-
-        let non_preflighted_request = RouterRequest::fake_builder().build().unwrap();
-
-        let res = service_stack
-            .oneshot(non_preflighted_request)
+        let service_stack = Csrf::new(config)
             .await
-            .unwrap();
-
-        assert_data(res, expected_response_data);
-    }
+            .unwrap()
+            .router_service(mock.boxed());
+        let res = service_stack.oneshot(request).await.unwrap();
 
-    fn assert_data(res: RouterResponse, expected_response_data: serde_json_bytes::Value) {
         match res.response.into_body() {
             ResponseBody::GraphQL(res) => {
-                assert_eq!(res.data.unwrap(), expected_response_data);
+                assert_eq!(res.data.unwrap(), json!({ "test": 1234_u32 }));
             }
             other => panic!("expected graphql response, found {:?}", other),
         }
     }
 
-    fn assert_error(res: RouterResponse) {
+    async fn assert_rejected(config: CSRFConfig, request: RouterRequest) {
+        let mock = MockRouterService::new().build();
+        let service_stack = Csrf::new(config)
+            .await
+            .unwrap()
+            .router_service(mock.boxed());
+        let res = service_stack.oneshot(request).await.unwrap();
+
         match res.response.into_body() {
             ResponseBody::GraphQL(res) => {
                 assert_eq!(

From d4afa385fc61eca8883aa29811b29cd44fbfed7e Mon Sep 17 00:00:00 2001
From: Simon Sapin 
Date: Thu, 12 May 2022 12:58:05 +0200
Subject: [PATCH 10/19] csrf: add a test for MIME type parsing

---
 apollo-router-core/src/plugins/csrf.rs | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs
index fdfe63f5d3..a33d7be6a6 100644
--- a/apollo-router-core/src/plugins/csrf.rs
+++ b/apollo-router-core/src/plugins/csrf.rs
@@ -260,7 +260,17 @@ mod csrf_tests {
             )
             .build()
             .unwrap();
-        assert_rejected(config, non_preflighted_request).await
+        assert_rejected(config.clone(), non_preflighted_request).await;
+
+        let non_preflighted_request = RouterRequest::fake_builder()
+            .headers(
+                [("content-type".into(), "text/plain; charset=utf8".into())]
+                    .into_iter()
+                    .collect(),
+            )
+            .build()
+            .unwrap();
+        assert_rejected(config, non_preflighted_request).await;
     }
 
     #[tokio::test]

From 19f96a2086cb93b28a475f4df94a02fa5b8d13dd Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Thu, 12 May 2022 15:03:48 +0200
Subject: [PATCH 11/19] wip, try to update docs

---
 docs/source/configuration/cors.mdx            | 219 +++++++++++++++++-
 .../cors-and-csrf/custom-headers.router.yaml  |   2 +-
 2 files changed, 218 insertions(+), 3 deletions(-)

diff --git a/docs/source/configuration/cors.mdx b/docs/source/configuration/cors.mdx
index ad662bf327..7aa775e9b6 100644
--- a/docs/source/configuration/cors.mdx
+++ b/docs/source/configuration/cors.mdx
@@ -1,6 +1,7 @@
 ---
 title: Configuring CORS in the Apollo Router
 sidebar_title: CORS
+description: Control access to your server's resources
 ---
 
 import { Link } from 'gatsby';
@@ -31,8 +32,10 @@ server:
     allow_credentials: true
 
     # The headers to allow.
-    # (Defaults to [ Content-Type ], which is required for Apollo Studio)
-    allow_headers: [ Content-Type, Authorization ]
+    # If this field is not set, CORS will default to the `mirror_request` mode,
+    # which mirrors the received `access-control-request-headers`
+    # (This is equivalent to allowing any headers)
+    allow_headers: [ Content-Type, Authorization, x-my-custom-required-header, x-and-an-other-required-header ]
 
     # Allowed request methods
     # (Defaults to [ GET, POST, OPTIONS ])
@@ -42,4 +45,216 @@ server:
     # browser in response to a cross-origin request.
     # (Defaults to empty array)
     expose_headers: []
+
+csrf:
+  # you could set disabled: false, but That wouldn't be safe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
+
+  # pick required headers that are custom enough
+  # to make sure browsers would need to have added them with javascript,
+  # which would trigger a preflight https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors
+  # `content-type` is not a safe idea for example.
+  required_headers:
+    - x-my-custom-required-header # we will look for presence of this header...
+    - x-and-an-other-required-header # ...or that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
+
+```
+
+[Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) is an HTTP-header-based protocol that enables a server to dictate which origins can access its resources. Put another way, your server can specify which websites can tell a user's browser to talk to your server, and precisely which types of HTTP requests are allowed.
+
+Apollo Server's default CORS behavior enables any website on the internet to tell a user's browser to connect to your server.
+
+> ⚠️ If your app is only visible on a private network and uses network separation for security, the default CORS behavior is **not secure**. See [Specifying origins](#specifying-origins) for more information.
+
+By default, websites running on domains that differ from your server's domain can't pass cookies with their requests. For details on enabling cross-origin cookie passing for authentication, see [Passing credentials with CORS](#passing-credentials-with-cors).
+
+## Why use CORS?
+
+Most developers know about CORS because they run into the all-too-common [CORS error](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors). CORS errors usually occur when you set up an API call or try to get your separately hosted server and client to talk to each other. To better understand what CORS is and why we use it, we'll briefly go over some background context.
+
+Internet users should always exercise caution when installing any new software on their devices. But when it comes to browsing the web, we navigate to different sites all the time, letting our browsers load content from those sites along the way. This comes with inherent risks.
+
+As web developers, we don't want a user's browser to do anything fishy to our server while the user is visiting another website. Browser security mechanisms (e.g., CORS or SOP) can give developers peace of mind by enabling a website's server to specify which browser origins can request resources from that server.
+
+The [_origin_](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#definition_of_an_origin) of a piece of web content consists of that content's domain, protocol, and port. The [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) (SOP) is a security mechanism that restricts scripts on one origin from interacting with resources from another origin. This means that scripts on websites can interact with resources from the same origin without jumping through any extra hoops.
+
+If two URLs differ in their domain, protocol, or port, then those URLs come from two different origins:
+
+```bash
+# Same origin
+http://example.com:8080/ <==> http://example.com:8080/
+
+# Different origin (difference in domain, protocol, and port)
+http://example.com:8080/ =X= https://example1.com:8081/
 ```
+
+However, as we all know, the internet is an exciting place full of resources that can make websites better (importing images, extra fonts, making API calls, and so on).  Developers needed a new protocol to relax SOP and safely _share_ resources across different origins.
+
+Cross-Origin Resource Sharing is the mechanism that allows a web page to share resources across different origins. CORS provides an extra layer of protection by enabling servers and clients to define HTTP headers that specify _which_ external clients' scripts can access their resources.
+
+Note that both SOP and CORS are related to [_browser_ security](https://developer.mozilla.org/en-US/docs/Web/Security#security-related_glossary_terms). Neither prevents _other_ types of software from requesting resources from your server.
+
+## Choosing CORS options for your project
+
+When thinking about configuring CORS for your application, there are two main settings to consider:
+
+* Which origins can access your server's resources
+* Whether your server accepts user credentials (i.e., cookies) with requests
+
+### Specifying origins
+
+CORS uses [specific HTTP request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers) as part of its protocol, including [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin). The `Access-Control-Allow-Origin` header (ACAO) enables a server to dictate which origins can use scripts to access that server's resources.
+
+Depending on what you're building, the origins you specify in your CORS configuration might need to change when you're ready to deploy your application.
+
+#### ⚠️ Applications on private networks
+
+If your browser is running your API on a private network (i.e., not on the public internet) and it relies on the privacy of that network for security, **the default CORS behavior of Apollo Server is insecure**. We strongly recommend [specifying which origins](#configuring-cors-options-for-apollo-server) can access your server's resources.
+
+If you don't, while your personal computer is on your private network, a script on any website could potentially make your browser talk to your private API. [Some browsers, such as Chrome, have features under development](https://wicg.github.io/private-network-access/) to solve this problem. But in the meantime, all servers on private networks should *always* specify origins in their CORS configuration.
+
+#### Federated subgraphs
+
+If you're building a [federated graph](/federation/), we strongly recommend that _all_ of your production subgraph servers [_disable_ CORS](../api/apollo-server#cors) (or at least specify origins and limit them to tools like the Apollo Explorer). Most subgraphs should allow communication _only_ from your gateways, and that communication doesn't involve a browser at all.
+
+For more information, see [Securing your subgraphs](/federation/subgraphs/#securing-your-subgraphs).
+
+#### APIs that require cookies
+
+If your API needs to accept [cross-origin cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) with requests, you _must_ specify origins in your CORS configuration. Otherwise, cross-origin cookies are automatically disabled. This is _not_ a security vulnerability, but it does prevent your API from successfully providing cookies.
+
+For examples, see [Passing credentials with CORS](#passing-credentials-with-cors).
+
+#### APIs with known consumers
+
+If you create an API on the public internet to serve resources to your _own_ websites or apps, you might want to [specify which origins](#configuring-cors-options-for-apollo-server) can access your server's resources. Explicitly specifying origins can provide an extra level of security.
+
+#### Public or embedded APIs
+
+If you create a public API or an API to embed in websites you don't control yourself, you probably want to allow _all_ origins to access your server's resources. You can use Apollo Server's default ACAO value (the wildcard `*`) in these situations, allowing requests from any origin.
+
+> Using the [wildcard (`*`)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#directives) value for the ACAO header enables _any_ website to tell a user's browser to send an arbitrary request (without cookies or other credentials) to your server and read that server's response.
+
+#### None of the above
+
+If your application doesn't fit into any of the above categories, the default Apollo Server CORS behavior should suit your use case. You can always specify origins in your CORS configuration later or disable cross-origin requests by [disabling CORS for your server](../api/apollo-server#cors).
+
+##  Configuring CORS options for Apollo Server
+
+By default, the batteries-included `apollo-server` package serves the `Access-Control-Allow-Origin` HTTP header with the wildcard value (`*`). This allows scripts on any origin to make requests, _without cookies_, to the server and read its responses.
+
+> If you need to pass credentials to your server (e.g., via cookies), you can't use the wildcard value (`*`) for your origin. You _must_ provide specific origins. For examples, see [Passing credentials with CORS](#passing-credentials-with-cors).
+
+The batteries-included `apollo-server` [has a `cors` option](../api/apollo-server/#cors), which you can use to change your server's default CORS behavior:
+
+```js
+const server = new ApolloServer({
+  typeDefs,
+  resolvers,
+  csrfPrevention: true,  // see below for more about this
+  cors: {
+    origin: ["https://www.your-app.example", "https://studio.apollographql.com"]
+  },
+});
+```
+
+Under the hood, the batteries-included `apollo-server` wraps around middleware provided by the [`cors`](https://github.com/expressjs/cors#configuration-options) npm package, enabling you to configure the `Access-Control-Allow-Origin` header via the `origin` option. The example above enables CORS requests from `https://www.your-app.example`, along with `https://studio.apollographql.com`.
+
+> Note if you plan to use [Apollo Studio Explorer](https://www.apollographql.com/docs/studio/explorer/explorer/) as a GraphQL web IDE you should include `https://studio.apollographql.com` in your list of valid origins.
+
+If you're using another integration of Apollo Server, you can add the `cors` configuration option to your framework-specific middleware function to change your server's CORS behavior. To learn more about the CORS defaults and options for your integration of Apollo Server, see [CORS configuration options](../api/apollo-server/#cors-1).
+
+Below is an example of setting up CORS with the [`apollo-server-express`](../integrations/middleware#swapping-out-apollo-server) package:
+
+```js
+// ... set up the server
+await server.start();
+
+const corsOptions = {
+  origin: ["https://www.your-app.example", "https://studio.apollographql.com"]
+};
+
+server.applyMiddleware({
+  app,
+  cors: corsOptions,
+  path: "/graphql",
+});
+```
+
+You can also [remove CORS middleware entirely](../api/apollo-server#cors) to disable cross-origin requests. In the batteries-included `apollo-server` and `apollo-server-express`, you do this by passing `cors: false`. This is [recommended for subgraphs in a federated graph](/federation/subgraphs/#securing-your-subgraphs).
+
+### Passing credentials with CORS
+
+If your server requires requests to [include a user's credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials) (e.g., cookies), you need to modify your CORS configuration to tell the browser those credentials are allowed.
+
+You can enable credentials with CORS by setting the [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) HTTP header to `true`.
+
+> You *must* [specify an origin](#configuring-cors-options-for-apollo-server) to enable credentialed requests. If your server sends the `*` wildcard value for the `Access-Control-Allow-Origin` HTTP header, your browser will refuse to send credentials.
+
+To  enable your browser to pass credentials with the batteries-included `apollo-server` you can use the [`cors` option](../api/apollo-server/#cors) to specify `origins` and set `credentials` to `true`:
+
+```js
+const server = new ApolloServer({
+  typeDefs,
+  resolvers,
+  csrfPrevention: true,  // see below for more about this
+  cors: {
+    origin: yourOrigin,
+    credentials: true
+  },
+});
+```
+
+Using `cors` middleware with an integration of Apollo Server, you can set the [`credentials`](https://github.com/expressjs/cors#configuration-options) option to `true` before passing it to your [framework-specific middleware function](https://www.apollographql.com/docs/apollo-server/api/apollo-server#framework-specific-middleware-function):
+
+```js
+//  The example below is using the apollo-server-express package.
+
+// ... set up the server
+await server.start();
+
+const corsOptions = { origin: yourOrigin, credentials: true };
+
+server.applyMiddleware({
+  app,
+  cors: corsOptions,
+  path: "/graphql",
+});
+```
+
+>The `cors` middleware `origin` option also accepts a boolean value, which means you can technically configure CORS to allow all cross-origin requests _with_ credentials (i.e., `{origin: true, credentials: true}`). This is almost certainly insecure, because any website could read information previously protected by a user's cookies. Instead, you should [specify origins in your CORS configuration](#configuring-cors-options-for-apollo-server) whenever you enable credentials.
+
+For examples of sending cookies and authorization headers from Apollo Client, see [Authentication](/react/networking/authentication/). For more guidance on adding authentication logic to Apollo Server, see [Authentication and authorization](./authentication).
+
+## Preventing Cross-Site Request Forgery (CSRF)
+
+Your server's CORS policy enables you to control which websites can talk to your server. In most cases, the browser checks your server's CORS policy by sending a [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) before sending the actual operation. This is a separate HTTP request. Unlike most HTTP requests (which use the `GET` or `POST` method), this request uses a method called `OPTIONS`. The browser sends an `Origin` header, along with some other headers that start with `Access-Control-`. These headers describe the kind of request that the potentially untrusted JavaScript wants to make. Your server returns a response with `Access-Control-*` headers describing its policies (as described above), and the browser uses that response to decide whether it's OK to send the real request.  Processing the `OPTIONS` preflight request never actually executes GraphQL operations.
+
+However, in some circumstances, the browser will *not* send this preflight request. If the request is considered ["simple"](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests), then the browser just sends the request without sending a preflight first. Your server's response can still contain `Access-Control-*` headers, and if they indicate that the origin that sent the request shouldn't be able to access the site, the browser will hide your server's response from the problematic JavaScript code.
+
+Unfortunately, this means that your server might execute GraphQL operations from "simple" requests sent by sites that shouldn't be allowed to communicate with your server. And these requests can even contain cookies! Although the browser will hide your server's response data from the malicious code, that might not be sufficient. If running the operation has side effects, then the attacker might not care if it can read the response or not, as long as it can use an unsuspecting user's browser (and cookies!) to trigger those side effects. Even with a read-only query, the malicious code might be able to figure out something about the response based entirely on how long the query takes to execute.
+
+Attacks that use simple requests for their side effects are called ["cross-site request forgery" attacks](https://owasp.org/www-community/attacks/csrf), or CSRF. Attacks that measure the timing of simple requests are called "cross-site search" attacks, or XS-Search.
+
+To avoid CSRF and XS-Search attacks, GraphQL servers should refuse to execute any operation coming from a browser that has not "preflighted" that operation. There's no reliable way to detect whether a request came from a browser, so GraphQL servers should not execute any operation in a "simple request".
+
+The most important rule for whether or not a request is ["simple"](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) is whether it tries to set arbitrary HTTP request headers. Any request that sets the `Content-Type` header to `application/json` (or anything other than a list of three particular values) cannot be a simple request, and thus it must be preflighted. Because all `POST` requests recognized by Apollo Server must contain a `Content-Type` header specifying `application/json`, we can be confident that they are not simple requests and that if they come from a browser, they have been preflighted.
+
+However, Apollo Server also handles [`GET` requests](../requests#get-requests). `GET` requests do not require a `Content-Type` header, so they can potentially be simple requests. So how can we ensure that we only execute `GET` requests that are _not_ simple requests? If we require the request to include an HTTP header that is never set automatically by the browser, then that is sufficient: requests that set HTTP headers other than the handful defined in the spec must be preflighted.
+
+Apollo Server 3.7 introduced a CSRF prevention feature, which you can enable by passing `csrfPrevention: true` to `new ApolloServer()`. When this feature is enabled, Apollo Server only executes GraphQL operations if at least one of the following conditions is true:
+
+- The incoming request includes a `Content-Type` header that specifies a type other than `text/plain`, `application/x-www-form-urlencoded`, or `multipart/form-data`. Notably, a `Content-Type` of `application/json` (including any suffix like `application/json; charset=utf-8`) is sufficient. This means that all `POST` requests (which must use `Content-Type: application/json`) will be executed. Additionally, all versions of [Apollo Client Web](/react/api/link/apollo-link-http) that support `GET` requests do include `Content-Type: application/json` headers, so any request from Apollo Client Web (`POST` or `GET`) will be executed.
+- There is a non-empty `X-Apollo-Operation-Name` header. This header is sent with all operations (`POST` or `GET`) by [Apollo iOS](/ios) (v0.13.0+) and [Apollo Kotlin](/kotlin) (all versions, including its former name "Apollo Android"), so any request from Apollo iOS or Apollo Kotlin will be executed.
+- There is a non-empty `Apollo-Require-Preflight` header.
+
+Note that all HTTP header names are case-insensitive.
+
+> CSRF prevention is only applied to requests that will execute GraphQL operations, not to requests that would load [landing pages](../api/plugin/landing-pages) or run [health checks](../monitoring/health-checks).)
+
+HTTP requests that satisfy none of the conditions above will be rejected with a 400 status code and a message clearly explaining which headers need to be added in order to make the request succeed.
+
+**We highly recommend that you enable CSRF prevention by passing `csrfPrevention: true` to `new ApolloServer()`**. This feature will be enabled by default in Apollo Server 4. It should have no impact on legitimate use of your graph, *unless* you have clients that send `GET` requests and are not Apollo Client Web, Apollo iOS, or Apollo Kotlin. If you do send `GET` requests with other clients, you should configure them to send a non-empty `Apollo-Require-Preflight` header along with all requests.
+
+You can also configure the set of headers that allow execution. For example, if you use a GraphQL client that performs `GET` requests without sending `Content-Type`, `X-Apollo-Operation-Name`, or `Apollo-Require-Preflight` headers, but it does send a `Some-Special-Header` header, you can pass `csrfPrevention: { requestHeaders: ['Some-Special-Header'] }` to `new ApolloServer()`.  This option replaces checking for the two headers `X-Apollo-Operation-Name` and `Apollo-Require-Preflight` in the CSRF prevention logic. The check for `Content-Type` remains the same.
+
+Note that Apollo Server does not permit executing mutations in `GET` requests (just queries). As long as you ensure that only mutations can have side effects, you are somewhat protected from the "side effects" aspect of CSRFs even without enabling CSRF protection. However, you would still be vulnerable to XS-Search timing attacks.
diff --git a/examples/cors-and-csrf/custom-headers.router.yaml b/examples/cors-and-csrf/custom-headers.router.yaml
index 0c68bfebd8..62f63f28e5 100644
--- a/examples/cors-and-csrf/custom-headers.router.yaml
+++ b/examples/cors-and-csrf/custom-headers.router.yaml
@@ -18,7 +18,7 @@ csrf:
   # pick required headers that are custom enough
   # to make sure browsers would need to have added them with javascript,
   # which would trigger a preflight https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors
-  # `content-type` is not a good idea for example.
+  # `content-type` is not a safe idea for example.
   required_headers:
     - x-my-custom-required-header # we will look for presence of this header...
     - x-and-an-other-required-header # ...or that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests

From 1759abb476780f68036ab16ef4d3807df458a464 Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Thu, 12 May 2022 15:46:51 +0200
Subject: [PATCH 12/19] rename csrf disabled option to unsafe_disabled + docs
 pass 1/n

---
 CONTRIBUTING.md                               |   5 +-
 apollo-router-core/src/plugins/csrf.rs        |  13 +-
 docs/source/configuration/cors.mdx            | 129 +++++++++---------
 .../cors-and-csrf/custom-headers.router.yaml  |   2 +-
 4 files changed, 78 insertions(+), 71 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b2b86b14ad..4f59f50336 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -61,6 +61,9 @@ and [Apollo's Docs Theme for Gatsby](https://github.com/apollographql/gatsby-the
 .
 
 To contribute to these docs, you can add or edit the markdown & MDX files in the `docs/source` directory.
+
 ```
 
 This will start up a development server with live reload enabled. You can see the docs by
diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs
index a33d7be6a6..7f0f049328 100644
--- a/apollo-router-core/src/plugins/csrf.rs
+++ b/apollo-router-core/src/plugins/csrf.rs
@@ -11,9 +11,10 @@ use tower::{BoxError, ServiceBuilder, ServiceExt};
 #[serde(deny_unknown_fields)]
 struct CSRFConfig {
     /// The CSRF plugin is enabled by default;
-    /// set disabled = true to disable the plugin behavior
+    /// set unsafe_disabled = true to disable the plugin behavior
+    /// Note that setting this to true is deemed unsafe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
     #[serde(default)]
-    disabled: bool,
+    unsafe_disabled: bool,
     /// Override the headers to check for by setting
     /// custom_headers
     /// Note that if you set required_headers here,
@@ -36,7 +37,7 @@ fn apollo_custom_preflight_headers() -> Vec {
 impl Default for CSRFConfig {
     fn default() -> Self {
         Self {
-            disabled: false,
+            unsafe_disabled: false,
             required_headers: apollo_custom_preflight_headers(),
         }
     }
@@ -89,7 +90,7 @@ impl Plugin for Csrf {
         &mut self,
         service: BoxService,
     ) -> BoxService {
-        if !self.config.disabled {
+        if !self.config.unsafe_disabled {
             let required_headers = self.config.required_headers.clone();
             ServiceBuilder::new()
                 .checkpoint(move |req: RouterRequest| {
@@ -201,7 +202,7 @@ mod csrf_tests {
         crate::plugins()
             .get("apollo.csrf")
             .expect("Plugin not found")
-            .create_instance(&serde_json::json!({ "disabled": true }))
+            .create_instance(&serde_json::json!({ "unsafe_disabled": true }))
             .await
             .unwrap();
 
@@ -276,7 +277,7 @@ mod csrf_tests {
     #[tokio::test]
     async fn it_accepts_non_preflighted_headers_request_when_plugin_is_disabled() {
         let config = CSRFConfig {
-            disabled: true,
+            unsafe_disabled: true,
             ..Default::default()
         };
         let non_preflighted_request = RouterRequest::fake_builder().build().unwrap();
diff --git a/docs/source/configuration/cors.mdx b/docs/source/configuration/cors.mdx
index 7aa775e9b6..7add01f797 100644
--- a/docs/source/configuration/cors.mdx
+++ b/docs/source/configuration/cors.mdx
@@ -6,63 +6,11 @@ description: Control access to your server's resources
 
 import { Link } from 'gatsby';
 
-The Apollo Router supports [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) to indicate which origins it accepts requests from. **By default, the router accepts requests from the Apollo studio**.
-
-If your environment has security requirements around CORS, you can specify them in the router's [configuration file](./overview/#configuration-file):
-
-```yaml title="router.yaml"
-server:
-  #
-  # CORS (Cross Origin Resource Sharing)
-  #
-  cors:
-
-    # Set to true to allow any origin
-    # (Defaults to false)
-    allow_any_origin: true
-
-    # List of accepted origins
-    # (Ignored if allow_any_origin is true)
-    # (Defaults to the Apollo Studio url: `https://studio.apollographql.com`)
-    origins:
-      - https://www.my-frontend.com
-
-    # Set to true to add the `Access-Control-Allow-Credentials` header
-    # (Defaults to false)
-    allow_credentials: true
-
-    # The headers to allow.
-    # If this field is not set, CORS will default to the `mirror_request` mode,
-    # which mirrors the received `access-control-request-headers`
-    # (This is equivalent to allowing any headers)
-    allow_headers: [ Content-Type, Authorization, x-my-custom-required-header, x-and-an-other-required-header ]
-
-    # Allowed request methods
-    # (Defaults to [ GET, POST, OPTIONS ])
-    methods: [ POST, OPTIONS ]
-
-    # Which response headers are available to scripts running in the
-    # browser in response to a cross-origin request.
-    # (Defaults to empty array)
-    expose_headers: []
-
-csrf:
-  # you could set disabled: false, but That wouldn't be safe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
-
-  # pick required headers that are custom enough
-  # to make sure browsers would need to have added them with javascript,
-  # which would trigger a preflight https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors
-  # `content-type` is not a safe idea for example.
-  required_headers:
-    - x-my-custom-required-header # we will look for presence of this header...
-    - x-and-an-other-required-header # ...or that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
-
-```
-
 [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) is an HTTP-header-based protocol that enables a server to dictate which origins can access its resources. Put another way, your server can specify which websites can tell a user's browser to talk to your server, and precisely which types of HTTP requests are allowed.
 
-Apollo Server's default CORS behavior enables any website on the internet to tell a user's browser to connect to your server.
+Apollo Router's default CORS behavior enables only the Apollo studio to tell a user's browser to connect to your server.
 
+> If your Apollo Router is serving data for web applications, you want to either add it to the list of allowed origins, or switch to `allow_any_origin`.
 > ⚠️ If your app is only visible on a private network and uses network separation for security, the default CORS behavior is **not secure**. See [Specifying origins](#specifying-origins) for more information.
 
 By default, websites running on domains that differ from your server's domain can't pass cookies with their requests. For details on enabling cross-origin cookie passing for authentication, see [Passing credentials with CORS](#passing-credentials-with-cors).
@@ -112,12 +60,6 @@ If your browser is running your API on a private network (i.e., not on the publi
 
 If you don't, while your personal computer is on your private network, a script on any website could potentially make your browser talk to your private API. [Some browsers, such as Chrome, have features under development](https://wicg.github.io/private-network-access/) to solve this problem. But in the meantime, all servers on private networks should *always* specify origins in their CORS configuration.
 
-#### Federated subgraphs
-
-If you're building a [federated graph](/federation/), we strongly recommend that _all_ of your production subgraph servers [_disable_ CORS](../api/apollo-server#cors) (or at least specify origins and limit them to tools like the Apollo Explorer). Most subgraphs should allow communication _only_ from your gateways, and that communication doesn't involve a browser at all.
-
-For more information, see [Securing your subgraphs](/federation/subgraphs/#securing-your-subgraphs).
-
 #### APIs that require cookies
 
 If your API needs to accept [cross-origin cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) with requests, you _must_ specify origins in your CORS configuration. Otherwise, cross-origin cookies are automatically disabled. This is _not_ a security vulnerability, but it does prevent your API from successfully providing cookies.
@@ -130,13 +72,20 @@ If you create an API on the public internet to serve resources to your _own_ web
 
 #### Public or embedded APIs
 
-If you create a public API or an API to embed in websites you don't control yourself, you probably want to allow _all_ origins to access your server's resources. You can use Apollo Server's default ACAO value (the wildcard `*`) in these situations, allowing requests from any origin.
+If you create a public API or an API to embed in websites you don't control yourself, you probably want to allow _any_ origin to access your server's resources. You can set `allow_any_origin` in your `router.yml` configuration:
+
+```yml
+server:
+  cors:
+    allow_any_origin: true
+```
 
 > Using the [wildcard (`*`)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#directives) value for the ACAO header enables _any_ website to tell a user's browser to send an arbitrary request (without cookies or other credentials) to your server and read that server's response.
 
-#### None of the above
+#### APIs with non browser consumers
 
-If your application doesn't fit into any of the above categories, the default Apollo Server CORS behavior should suit your use case. You can always specify origins in your CORS configuration later or disable cross-origin requests by [disabling CORS for your server](../api/apollo-server#cors).
+The default Apollo Server CORS behavior should suit your use case, and https://studio.apollographql.com/sandbox/explorer allows you to run queries against your Apollo Router.
+You can always specify origins in your CORS configuration later. 
 
 ##  Configuring CORS options for Apollo Server
 
@@ -258,3 +207,57 @@ HTTP requests that satisfy none of the conditions above will be rejected with a
 You can also configure the set of headers that allow execution. For example, if you use a GraphQL client that performs `GET` requests without sending `Content-Type`, `X-Apollo-Operation-Name`, or `Apollo-Require-Preflight` headers, but it does send a `Some-Special-Header` header, you can pass `csrfPrevention: { requestHeaders: ['Some-Special-Header'] }` to `new ApolloServer()`.  This option replaces checking for the two headers `X-Apollo-Operation-Name` and `Apollo-Require-Preflight` in the CSRF prevention logic. The check for `Content-Type` remains the same.
 
 Note that Apollo Server does not permit executing mutations in `GET` requests (just queries). As long as you ensure that only mutations can have side effects, you are somewhat protected from the "side effects" aspect of CSRFs even without enabling CSRF protection. However, you would still be vulnerable to XS-Search timing attacks.
+
+
+The Apollo Router supports [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) to indicate which origins it accepts requests from. **By default, the router accepts requests from the Apollo studio**.
+
+If your environment has security requirements around CORS, you can specify them in the router's [configuration file](./overview/#configuration-file):
+
+```yaml title="router.yaml"
+server:
+  #
+  # CORS (Cross Origin Resource Sharing)
+  #
+  cors:
+
+    # Set to true to allow any origin
+    # (Defaults to false)
+    allow_any_origin: true
+
+    # List of accepted origins
+    # (Ignored if allow_any_origin is true)
+    # (Defaults to the Apollo Studio url: `https://studio.apollographql.com`)
+    origins:
+      - https://www.my-frontend.com
+
+    # Set to true to add the `Access-Control-Allow-Credentials` header
+    # (Defaults to false)
+    allow_credentials: true
+
+    # The headers to allow.
+    # If this field is not set, CORS will default to the `mirror_request` mode,
+    # which mirrors the received `access-control-request-headers`
+    # (This is equivalent to allowing any headers)
+    allow_headers: [ Content-Type, Authorization, x-my-custom-required-header, x-and-an-other-required-header ]
+
+    # Allowed request methods
+    # (Defaults to [ GET, POST, OPTIONS ])
+    methods: [ POST, OPTIONS ]
+
+    # Which response headers are available to scripts running in the
+    # browser in response to a cross-origin request.
+    # (Defaults to empty array)
+    expose_headers: []
+
+csrf:
+  # you could set unsafe_disabled: true, but That wouldn't be safe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
+
+  # pick required headers that are custom enough
+  # to make sure browsers would need to have added them with javascript,
+  # which would trigger a preflight https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors
+  # `content-type` is not a safe idea for example.
+  required_headers:
+    - x-my-custom-required-header # we will look for presence of this header...
+    - x-and-an-other-required-header # ...or that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
+
+```
diff --git a/examples/cors-and-csrf/custom-headers.router.yaml b/examples/cors-and-csrf/custom-headers.router.yaml
index 62f63f28e5..55c8dd234f 100644
--- a/examples/cors-and-csrf/custom-headers.router.yaml
+++ b/examples/cors-and-csrf/custom-headers.router.yaml
@@ -13,7 +13,7 @@ server:
       - x-and-an-other-required-header
       - content-type # and many more!
 csrf:
-  # you could set disabled: false, but That wouldn't be safe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
+  # you could set unsafe_disabled: true, but That wouldn't be safe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
 
   # pick required headers that are custom enough
   # to make sure browsers would need to have added them with javascript,

From 669b786bd6c6f854405714a5945ed769a7f353a0 Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Thu, 12 May 2022 16:13:43 +0200
Subject: [PATCH 13/19] cors documentation part 2/n

---
 docs/source/configuration/cors.mdx | 30 +++++++++++++-----------------
 1 file changed, 13 insertions(+), 17 deletions(-)

diff --git a/docs/source/configuration/cors.mdx b/docs/source/configuration/cors.mdx
index 7add01f797..3c88e9dc10 100644
--- a/docs/source/configuration/cors.mdx
+++ b/docs/source/configuration/cors.mdx
@@ -10,7 +10,8 @@ import { Link } from 'gatsby';
 
 Apollo Router's default CORS behavior enables only the Apollo studio to tell a user's browser to connect to your server.
 
-> If your Apollo Router is serving data for web applications, you want to either add it to the list of allowed origins, or switch to `allow_any_origin`.
+If your Apollo Router is serving data for web applications, you want to either add it to the list of allowed origins, or switch to `allow_any_origin`.
+
 > ⚠️ If your app is only visible on a private network and uses network separation for security, the default CORS behavior is **not secure**. See [Specifying origins](#specifying-origins) for more information.
 
 By default, websites running on domains that differ from your server's domain can't pass cookies with their requests. For details on enabling cross-origin cookie passing for authentication, see [Passing credentials with CORS](#passing-credentials-with-cors).
@@ -54,11 +55,11 @@ CORS uses [specific HTTP request headers](https://developer.mozilla.org/en-US/do
 
 Depending on what you're building, the origins you specify in your CORS configuration might need to change when you're ready to deploy your application.
 
-#### ⚠️ Applications on private networks
+#### Applications on private networks
 
-If your browser is running your API on a private network (i.e., not on the public internet) and it relies on the privacy of that network for security, **the default CORS behavior of Apollo Server is insecure**. We strongly recommend [specifying which origins](#configuring-cors-options-for-apollo-server) can access your server's resources.
+If your browser is running your API on a private network (i.e., not on the public internet) and it relies on the privacy of that network for security, we strongly recommend [specifying which origins](#configuring-cors-options-for-apollo-server) can access your server's resources.
 
-If you don't, while your personal computer is on your private network, a script on any website could potentially make your browser talk to your private API. [Some browsers, such as Chrome, have features under development](https://wicg.github.io/private-network-access/) to solve this problem. But in the meantime, all servers on private networks should *always* specify origins in their CORS configuration.
+If you instead rely on `allow_any_origin`, while your personal computer is on your private network, a script on any website could potentially make your browser talk to your private API. [Some browsers, such as Chrome, have features under development](https://wicg.github.io/private-network-access/) to solve this problem. But in the meantime, all servers on private networks should *always* specify origins in their CORS configuration.
 
 #### APIs that require cookies
 
@@ -68,7 +69,7 @@ For examples, see [Passing credentials with CORS](#passing-credentials-with-cors
 
 #### APIs with known consumers
 
-If you create an API on the public internet to serve resources to your _own_ websites or apps, you might want to [specify which origins](#configuring-cors-options-for-apollo-server) can access your server's resources. Explicitly specifying origins can provide an extra level of security.
+If you create an API on the public internet to serve resources to your _own_ websites or apps, you want to [specify which origins](#configuring-cors-options-for-apollo-server) can access your server's resources.
 
 #### Public or embedded APIs
 
@@ -80,7 +81,7 @@ server:
     allow_any_origin: true
 ```
 
-> Using the [wildcard (`*`)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#directives) value for the ACAO header enables _any_ website to tell a user's browser to send an arbitrary request (without cookies or other credentials) to your server and read that server's response.
+> Using `allow_any_origin` makes the router send the [wildcard (`*`)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#directives) value for the ACAO header, which enables _any_ website to tell a user's browser to send an arbitrary request (without cookies or other credentials) to your server and read that server's response.
 
 #### APIs with non browser consumers
 
@@ -89,21 +90,16 @@ You can always specify origins in your CORS configuration later.
 
 ##  Configuring CORS options for Apollo Server
 
-By default, the batteries-included `apollo-server` package serves the `Access-Control-Allow-Origin` HTTP header with the wildcard value (`*`). This allows scripts on any origin to make requests, _without cookies_, to the server and read its responses.
+By default, the apollo router only allows requests from https://studio.apollographql.com. If your Apollo Router is serving data for web applications, you want to either add their origin to the list of allowed origins, or switch to `allow_any_origin`.
 
-> If you need to pass credentials to your server (e.g., via cookies), you can't use the wildcard value (`*`) for your origin. You _must_ provide specific origins. For examples, see [Passing credentials with CORS](#passing-credentials-with-cors).
+> If you need to pass credentials to your server (e.g., via cookies), you can't use the `allow_any_origin` flag. You _must_ provide specific origins. For examples, see [Passing credentials with CORS](#passing-credentials-with-cors).
 
 The batteries-included `apollo-server` [has a `cors` option](../api/apollo-server/#cors), which you can use to change your server's default CORS behavior:
 
-```js
-const server = new ApolloServer({
-  typeDefs,
-  resolvers,
-  csrfPrevention: true,  // see below for more about this
-  cors: {
-    origin: ["https://www.your-app.example", "https://studio.apollographql.com"]
-  },
-});
+```yml
+server:
+  cors:
+    allow_any_origin: true
 ```
 
 Under the hood, the batteries-included `apollo-server` wraps around middleware provided by the [`cors`](https://github.com/expressjs/cors#configuration-options) npm package, enabling you to configure the `Access-Control-Allow-Origin` header via the `origin` option. The example above enables CORS requests from `https://www.your-app.example`, along with `https://studio.apollographql.com`.

From c4f101fe4d008547969f5017a5a5f1fe6b819cf3 Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Fri, 13 May 2022 10:26:35 +0200
Subject: [PATCH 14/19] update documentation snapshot

---
 ...outer__configuration__tests__schema_generation.snap | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
index 1a7a756f47..611165ab88 100644
--- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
+++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap
@@ -11,11 +11,6 @@ expression: "&schema"
     "csrf": {
       "type": "object",
       "properties": {
-        "disabled": {
-          "description": "The CSRF plugin is enabled by default; set disabled = true to disable the plugin behavior",
-          "default": false,
-          "type": "boolean"
-        },
         "required_headers": {
           "description": "Override the headers to check for by setting custom_headers Note that if you set required_headers here, you may also want to have a look at your `CORS` configuration, and make sure you either: - did not set any `allow_headers` list (so it defaults to `mirror_request`) - added your required headers to the allow_headers list, as shown in the `examples/cors-and-csrf/custom-headers.router.yaml` files.",
           "default": [
@@ -26,6 +21,11 @@ expression: "&schema"
           "items": {
             "type": "string"
           }
+        },
+        "unsafe_disabled": {
+          "description": "The CSRF plugin is enabled by default; set unsafe_disabled = true to disable the plugin behavior Note that setting this to true is deemed unsafe https://developer.mozilla.org/en-US/docs/Glossary/CSRF",
+          "default": false,
+          "type": "boolean"
         }
       },
       "additionalProperties": false

From ba41af37d85bba3fc2283a9f1388fe9da25cc373 Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Fri, 13 May 2022 10:41:08 +0200
Subject: [PATCH 15/19] revert cors documentation, as we will polish it in a
 followup pr

---
 docs/source/configuration/cors.mdx | 218 +----------------------------
 1 file changed, 2 insertions(+), 216 deletions(-)

diff --git a/docs/source/configuration/cors.mdx b/docs/source/configuration/cors.mdx
index 3c88e9dc10..ad662bf327 100644
--- a/docs/source/configuration/cors.mdx
+++ b/docs/source/configuration/cors.mdx
@@ -1,210 +1,10 @@
 ---
 title: Configuring CORS in the Apollo Router
 sidebar_title: CORS
-description: Control access to your server's resources
 ---
 
 import { Link } from 'gatsby';
 
-[Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) is an HTTP-header-based protocol that enables a server to dictate which origins can access its resources. Put another way, your server can specify which websites can tell a user's browser to talk to your server, and precisely which types of HTTP requests are allowed.
-
-Apollo Router's default CORS behavior enables only the Apollo studio to tell a user's browser to connect to your server.
-
-If your Apollo Router is serving data for web applications, you want to either add it to the list of allowed origins, or switch to `allow_any_origin`.
-
-> ⚠️ If your app is only visible on a private network and uses network separation for security, the default CORS behavior is **not secure**. See [Specifying origins](#specifying-origins) for more information.
-
-By default, websites running on domains that differ from your server's domain can't pass cookies with their requests. For details on enabling cross-origin cookie passing for authentication, see [Passing credentials with CORS](#passing-credentials-with-cors).
-
-## Why use CORS?
-
-Most developers know about CORS because they run into the all-too-common [CORS error](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors). CORS errors usually occur when you set up an API call or try to get your separately hosted server and client to talk to each other. To better understand what CORS is and why we use it, we'll briefly go over some background context.
-
-Internet users should always exercise caution when installing any new software on their devices. But when it comes to browsing the web, we navigate to different sites all the time, letting our browsers load content from those sites along the way. This comes with inherent risks.
-
-As web developers, we don't want a user's browser to do anything fishy to our server while the user is visiting another website. Browser security mechanisms (e.g., CORS or SOP) can give developers peace of mind by enabling a website's server to specify which browser origins can request resources from that server.
-
-The [_origin_](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#definition_of_an_origin) of a piece of web content consists of that content's domain, protocol, and port. The [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) (SOP) is a security mechanism that restricts scripts on one origin from interacting with resources from another origin. This means that scripts on websites can interact with resources from the same origin without jumping through any extra hoops.
-
-If two URLs differ in their domain, protocol, or port, then those URLs come from two different origins:
-
-```bash
-# Same origin
-http://example.com:8080/ <==> http://example.com:8080/
-
-# Different origin (difference in domain, protocol, and port)
-http://example.com:8080/ =X= https://example1.com:8081/
-```
-
-However, as we all know, the internet is an exciting place full of resources that can make websites better (importing images, extra fonts, making API calls, and so on).  Developers needed a new protocol to relax SOP and safely _share_ resources across different origins.
-
-Cross-Origin Resource Sharing is the mechanism that allows a web page to share resources across different origins. CORS provides an extra layer of protection by enabling servers and clients to define HTTP headers that specify _which_ external clients' scripts can access their resources.
-
-Note that both SOP and CORS are related to [_browser_ security](https://developer.mozilla.org/en-US/docs/Web/Security#security-related_glossary_terms). Neither prevents _other_ types of software from requesting resources from your server.
-
-## Choosing CORS options for your project
-
-When thinking about configuring CORS for your application, there are two main settings to consider:
-
-* Which origins can access your server's resources
-* Whether your server accepts user credentials (i.e., cookies) with requests
-
-### Specifying origins
-
-CORS uses [specific HTTP request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers) as part of its protocol, including [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin). The `Access-Control-Allow-Origin` header (ACAO) enables a server to dictate which origins can use scripts to access that server's resources.
-
-Depending on what you're building, the origins you specify in your CORS configuration might need to change when you're ready to deploy your application.
-
-#### Applications on private networks
-
-If your browser is running your API on a private network (i.e., not on the public internet) and it relies on the privacy of that network for security, we strongly recommend [specifying which origins](#configuring-cors-options-for-apollo-server) can access your server's resources.
-
-If you instead rely on `allow_any_origin`, while your personal computer is on your private network, a script on any website could potentially make your browser talk to your private API. [Some browsers, such as Chrome, have features under development](https://wicg.github.io/private-network-access/) to solve this problem. But in the meantime, all servers on private networks should *always* specify origins in their CORS configuration.
-
-#### APIs that require cookies
-
-If your API needs to accept [cross-origin cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) with requests, you _must_ specify origins in your CORS configuration. Otherwise, cross-origin cookies are automatically disabled. This is _not_ a security vulnerability, but it does prevent your API from successfully providing cookies.
-
-For examples, see [Passing credentials with CORS](#passing-credentials-with-cors).
-
-#### APIs with known consumers
-
-If you create an API on the public internet to serve resources to your _own_ websites or apps, you want to [specify which origins](#configuring-cors-options-for-apollo-server) can access your server's resources.
-
-#### Public or embedded APIs
-
-If you create a public API or an API to embed in websites you don't control yourself, you probably want to allow _any_ origin to access your server's resources. You can set `allow_any_origin` in your `router.yml` configuration:
-
-```yml
-server:
-  cors:
-    allow_any_origin: true
-```
-
-> Using `allow_any_origin` makes the router send the [wildcard (`*`)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#directives) value for the ACAO header, which enables _any_ website to tell a user's browser to send an arbitrary request (without cookies or other credentials) to your server and read that server's response.
-
-#### APIs with non browser consumers
-
-The default Apollo Server CORS behavior should suit your use case, and https://studio.apollographql.com/sandbox/explorer allows you to run queries against your Apollo Router.
-You can always specify origins in your CORS configuration later. 
-
-##  Configuring CORS options for Apollo Server
-
-By default, the apollo router only allows requests from https://studio.apollographql.com. If your Apollo Router is serving data for web applications, you want to either add their origin to the list of allowed origins, or switch to `allow_any_origin`.
-
-> If you need to pass credentials to your server (e.g., via cookies), you can't use the `allow_any_origin` flag. You _must_ provide specific origins. For examples, see [Passing credentials with CORS](#passing-credentials-with-cors).
-
-The batteries-included `apollo-server` [has a `cors` option](../api/apollo-server/#cors), which you can use to change your server's default CORS behavior:
-
-```yml
-server:
-  cors:
-    allow_any_origin: true
-```
-
-Under the hood, the batteries-included `apollo-server` wraps around middleware provided by the [`cors`](https://github.com/expressjs/cors#configuration-options) npm package, enabling you to configure the `Access-Control-Allow-Origin` header via the `origin` option. The example above enables CORS requests from `https://www.your-app.example`, along with `https://studio.apollographql.com`.
-
-> Note if you plan to use [Apollo Studio Explorer](https://www.apollographql.com/docs/studio/explorer/explorer/) as a GraphQL web IDE you should include `https://studio.apollographql.com` in your list of valid origins.
-
-If you're using another integration of Apollo Server, you can add the `cors` configuration option to your framework-specific middleware function to change your server's CORS behavior. To learn more about the CORS defaults and options for your integration of Apollo Server, see [CORS configuration options](../api/apollo-server/#cors-1).
-
-Below is an example of setting up CORS with the [`apollo-server-express`](../integrations/middleware#swapping-out-apollo-server) package:
-
-```js
-// ... set up the server
-await server.start();
-
-const corsOptions = {
-  origin: ["https://www.your-app.example", "https://studio.apollographql.com"]
-};
-
-server.applyMiddleware({
-  app,
-  cors: corsOptions,
-  path: "/graphql",
-});
-```
-
-You can also [remove CORS middleware entirely](../api/apollo-server#cors) to disable cross-origin requests. In the batteries-included `apollo-server` and `apollo-server-express`, you do this by passing `cors: false`. This is [recommended for subgraphs in a federated graph](/federation/subgraphs/#securing-your-subgraphs).
-
-### Passing credentials with CORS
-
-If your server requires requests to [include a user's credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials) (e.g., cookies), you need to modify your CORS configuration to tell the browser those credentials are allowed.
-
-You can enable credentials with CORS by setting the [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) HTTP header to `true`.
-
-> You *must* [specify an origin](#configuring-cors-options-for-apollo-server) to enable credentialed requests. If your server sends the `*` wildcard value for the `Access-Control-Allow-Origin` HTTP header, your browser will refuse to send credentials.
-
-To  enable your browser to pass credentials with the batteries-included `apollo-server` you can use the [`cors` option](../api/apollo-server/#cors) to specify `origins` and set `credentials` to `true`:
-
-```js
-const server = new ApolloServer({
-  typeDefs,
-  resolvers,
-  csrfPrevention: true,  // see below for more about this
-  cors: {
-    origin: yourOrigin,
-    credentials: true
-  },
-});
-```
-
-Using `cors` middleware with an integration of Apollo Server, you can set the [`credentials`](https://github.com/expressjs/cors#configuration-options) option to `true` before passing it to your [framework-specific middleware function](https://www.apollographql.com/docs/apollo-server/api/apollo-server#framework-specific-middleware-function):
-
-```js
-//  The example below is using the apollo-server-express package.
-
-// ... set up the server
-await server.start();
-
-const corsOptions = { origin: yourOrigin, credentials: true };
-
-server.applyMiddleware({
-  app,
-  cors: corsOptions,
-  path: "/graphql",
-});
-```
-
->The `cors` middleware `origin` option also accepts a boolean value, which means you can technically configure CORS to allow all cross-origin requests _with_ credentials (i.e., `{origin: true, credentials: true}`). This is almost certainly insecure, because any website could read information previously protected by a user's cookies. Instead, you should [specify origins in your CORS configuration](#configuring-cors-options-for-apollo-server) whenever you enable credentials.
-
-For examples of sending cookies and authorization headers from Apollo Client, see [Authentication](/react/networking/authentication/). For more guidance on adding authentication logic to Apollo Server, see [Authentication and authorization](./authentication).
-
-## Preventing Cross-Site Request Forgery (CSRF)
-
-Your server's CORS policy enables you to control which websites can talk to your server. In most cases, the browser checks your server's CORS policy by sending a [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) before sending the actual operation. This is a separate HTTP request. Unlike most HTTP requests (which use the `GET` or `POST` method), this request uses a method called `OPTIONS`. The browser sends an `Origin` header, along with some other headers that start with `Access-Control-`. These headers describe the kind of request that the potentially untrusted JavaScript wants to make. Your server returns a response with `Access-Control-*` headers describing its policies (as described above), and the browser uses that response to decide whether it's OK to send the real request.  Processing the `OPTIONS` preflight request never actually executes GraphQL operations.
-
-However, in some circumstances, the browser will *not* send this preflight request. If the request is considered ["simple"](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests), then the browser just sends the request without sending a preflight first. Your server's response can still contain `Access-Control-*` headers, and if they indicate that the origin that sent the request shouldn't be able to access the site, the browser will hide your server's response from the problematic JavaScript code.
-
-Unfortunately, this means that your server might execute GraphQL operations from "simple" requests sent by sites that shouldn't be allowed to communicate with your server. And these requests can even contain cookies! Although the browser will hide your server's response data from the malicious code, that might not be sufficient. If running the operation has side effects, then the attacker might not care if it can read the response or not, as long as it can use an unsuspecting user's browser (and cookies!) to trigger those side effects. Even with a read-only query, the malicious code might be able to figure out something about the response based entirely on how long the query takes to execute.
-
-Attacks that use simple requests for their side effects are called ["cross-site request forgery" attacks](https://owasp.org/www-community/attacks/csrf), or CSRF. Attacks that measure the timing of simple requests are called "cross-site search" attacks, or XS-Search.
-
-To avoid CSRF and XS-Search attacks, GraphQL servers should refuse to execute any operation coming from a browser that has not "preflighted" that operation. There's no reliable way to detect whether a request came from a browser, so GraphQL servers should not execute any operation in a "simple request".
-
-The most important rule for whether or not a request is ["simple"](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) is whether it tries to set arbitrary HTTP request headers. Any request that sets the `Content-Type` header to `application/json` (or anything other than a list of three particular values) cannot be a simple request, and thus it must be preflighted. Because all `POST` requests recognized by Apollo Server must contain a `Content-Type` header specifying `application/json`, we can be confident that they are not simple requests and that if they come from a browser, they have been preflighted.
-
-However, Apollo Server also handles [`GET` requests](../requests#get-requests). `GET` requests do not require a `Content-Type` header, so they can potentially be simple requests. So how can we ensure that we only execute `GET` requests that are _not_ simple requests? If we require the request to include an HTTP header that is never set automatically by the browser, then that is sufficient: requests that set HTTP headers other than the handful defined in the spec must be preflighted.
-
-Apollo Server 3.7 introduced a CSRF prevention feature, which you can enable by passing `csrfPrevention: true` to `new ApolloServer()`. When this feature is enabled, Apollo Server only executes GraphQL operations if at least one of the following conditions is true:
-
-- The incoming request includes a `Content-Type` header that specifies a type other than `text/plain`, `application/x-www-form-urlencoded`, or `multipart/form-data`. Notably, a `Content-Type` of `application/json` (including any suffix like `application/json; charset=utf-8`) is sufficient. This means that all `POST` requests (which must use `Content-Type: application/json`) will be executed. Additionally, all versions of [Apollo Client Web](/react/api/link/apollo-link-http) that support `GET` requests do include `Content-Type: application/json` headers, so any request from Apollo Client Web (`POST` or `GET`) will be executed.
-- There is a non-empty `X-Apollo-Operation-Name` header. This header is sent with all operations (`POST` or `GET`) by [Apollo iOS](/ios) (v0.13.0+) and [Apollo Kotlin](/kotlin) (all versions, including its former name "Apollo Android"), so any request from Apollo iOS or Apollo Kotlin will be executed.
-- There is a non-empty `Apollo-Require-Preflight` header.
-
-Note that all HTTP header names are case-insensitive.
-
-> CSRF prevention is only applied to requests that will execute GraphQL operations, not to requests that would load [landing pages](../api/plugin/landing-pages) or run [health checks](../monitoring/health-checks).)
-
-HTTP requests that satisfy none of the conditions above will be rejected with a 400 status code and a message clearly explaining which headers need to be added in order to make the request succeed.
-
-**We highly recommend that you enable CSRF prevention by passing `csrfPrevention: true` to `new ApolloServer()`**. This feature will be enabled by default in Apollo Server 4. It should have no impact on legitimate use of your graph, *unless* you have clients that send `GET` requests and are not Apollo Client Web, Apollo iOS, or Apollo Kotlin. If you do send `GET` requests with other clients, you should configure them to send a non-empty `Apollo-Require-Preflight` header along with all requests.
-
-You can also configure the set of headers that allow execution. For example, if you use a GraphQL client that performs `GET` requests without sending `Content-Type`, `X-Apollo-Operation-Name`, or `Apollo-Require-Preflight` headers, but it does send a `Some-Special-Header` header, you can pass `csrfPrevention: { requestHeaders: ['Some-Special-Header'] }` to `new ApolloServer()`.  This option replaces checking for the two headers `X-Apollo-Operation-Name` and `Apollo-Require-Preflight` in the CSRF prevention logic. The check for `Content-Type` remains the same.
-
-Note that Apollo Server does not permit executing mutations in `GET` requests (just queries). As long as you ensure that only mutations can have side effects, you are somewhat protected from the "side effects" aspect of CSRFs even without enabling CSRF protection. However, you would still be vulnerable to XS-Search timing attacks.
-
-
 The Apollo Router supports [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) to indicate which origins it accepts requests from. **By default, the router accepts requests from the Apollo studio**.
 
 If your environment has security requirements around CORS, you can specify them in the router's [configuration file](./overview/#configuration-file):
@@ -231,10 +31,8 @@ server:
     allow_credentials: true
 
     # The headers to allow.
-    # If this field is not set, CORS will default to the `mirror_request` mode,
-    # which mirrors the received `access-control-request-headers`
-    # (This is equivalent to allowing any headers)
-    allow_headers: [ Content-Type, Authorization, x-my-custom-required-header, x-and-an-other-required-header ]
+    # (Defaults to [ Content-Type ], which is required for Apollo Studio)
+    allow_headers: [ Content-Type, Authorization ]
 
     # Allowed request methods
     # (Defaults to [ GET, POST, OPTIONS ])
@@ -244,16 +42,4 @@ server:
     # browser in response to a cross-origin request.
     # (Defaults to empty array)
     expose_headers: []
-
-csrf:
-  # you could set unsafe_disabled: true, but That wouldn't be safe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
-
-  # pick required headers that are custom enough
-  # to make sure browsers would need to have added them with javascript,
-  # which would trigger a preflight https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#what_requests_use_cors
-  # `content-type` is not a safe idea for example.
-  required_headers:
-    - x-my-custom-required-header # we will look for presence of this header...
-    - x-and-an-other-required-header # ...or that one if a request was made with a `simple` content type https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
-
 ```

From 6622d4c08ef3f23d906f26161707ac1c629a546d Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Fri, 13 May 2022 11:08:37 +0200
Subject: [PATCH 16/19] Add three changelog entries:

Two breaking sections:
- one that explains the CORS default behavior changed
- one that explains the CSRF plugin will reject simple requests by default.

One feature section that explains a new CSRF plugin now exists.
---
 NEXT_CHANGELOG.md | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md
index 14095f2d5f..51387355e8 100644
--- a/NEXT_CHANGELOG.md
+++ b/NEXT_CHANGELOG.md
@@ -25,6 +25,21 @@ Description! And a link to a [reference](http://url)
 # [v0.9.0-rc.1] - (unreleased)
 ## ❗ BREAKING ❗
 
+### CORS default behavior update [PR #1006](https://github.com/apollographql/router/pull/1006)
+The CORS allow_headers default behavior changes from:
+  - allow only `Content-Type`, `apollographql-client-name` and `apollographql-client-version`
+to:
+  - mirror the received `access-control-request-headers`
+
+This change loosens the CORS related headers restrictions, so it shouldn't have any impact on your setup.
+
+### CSRF Protection is enabled by default [PR #1006](https://github.com/apollographql/router/pull/1006)
+A [Cross-Site Request Forgery protection plugin](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) is enabled by default.
+
+This means [simple requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) will be rejected from now on (they represent a security risk).
+
+The plugin can be customized as explained in the [CORS and CSRF example](https://github.com/apollographql/router/tree/main/examples/cors-and-csrf/custom-headers.router.yaml)
+
 ### Remove the agent endpoint configuration for Zipkin [PR #1025](https://github.com/apollographql/router/pull/1025)
 Zipkin only supports the collector endpoint URL configuration.
 
@@ -53,6 +68,9 @@ telemetry:
 
 ## 🚀 Features ( :rocket: )
 
+### CSRF Protection [PR #1006](https://github.com/apollographql/router/pull/1006)
+The router now embeds a CSRF protection plugin, which is enabled by default. Have a look at the [CORS and CSRF example](https://github.com/apollographql/router/tree/main/examples/cors-and-csrf/custom-headers.router.yaml) to learn how to customize it. [Documentation](https://www.apollographql.com/docs/router/configuration/cors/) will be updated soon!
+
 ### helm chart now supports prometheus metrics [PR #1005](https://github.com/apollographql/router/pull/1005)
 The router has supported exporting prometheus metrics for a while. This change updates our helm chart to enable router deployment prometheus metrics. 
 

From 4d27490f98708abf18275720224670f760f3ba55 Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Fri, 13 May 2022 11:14:40 +0200
Subject: [PATCH 17/19] order breaking section items by impact

---
 NEXT_CHANGELOG.md | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md
index 51387355e8..c435c365e0 100644
--- a/NEXT_CHANGELOG.md
+++ b/NEXT_CHANGELOG.md
@@ -25,21 +25,6 @@ Description! And a link to a [reference](http://url)
 # [v0.9.0-rc.1] - (unreleased)
 ## ❗ BREAKING ❗
 
-### CORS default behavior update [PR #1006](https://github.com/apollographql/router/pull/1006)
-The CORS allow_headers default behavior changes from:
-  - allow only `Content-Type`, `apollographql-client-name` and `apollographql-client-version`
-to:
-  - mirror the received `access-control-request-headers`
-
-This change loosens the CORS related headers restrictions, so it shouldn't have any impact on your setup.
-
-### CSRF Protection is enabled by default [PR #1006](https://github.com/apollographql/router/pull/1006)
-A [Cross-Site Request Forgery protection plugin](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) is enabled by default.
-
-This means [simple requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) will be rejected from now on (they represent a security risk).
-
-The plugin can be customized as explained in the [CORS and CSRF example](https://github.com/apollographql/router/tree/main/examples/cors-and-csrf/custom-headers.router.yaml)
-
 ### Remove the agent endpoint configuration for Zipkin [PR #1025](https://github.com/apollographql/router/pull/1025)
 Zipkin only supports the collector endpoint URL configuration.
 
@@ -66,6 +51,21 @@ telemetry:
       endpoint: default
 ```
 
+### CSRF Protection is enabled by default [PR #1006](https://github.com/apollographql/router/pull/1006)
+A [Cross-Site Request Forgery protection plugin](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) is enabled by default.
+
+This means [simple requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) will be rejected from now on (they represent a security risk).
+
+The plugin can be customized as explained in the [CORS and CSRF example](https://github.com/apollographql/router/tree/main/examples/cors-and-csrf/custom-headers.router.yaml)
+
+### CORS default behavior update [PR #1006](https://github.com/apollographql/router/pull/1006)
+The CORS allow_headers default behavior changes from:
+  - allow only `Content-Type`, `apollographql-client-name` and `apollographql-client-version`
+to:
+  - mirror the received `access-control-request-headers`
+
+This change loosens the CORS related headers restrictions, so it shouldn't have any impact on your setup.
+
 ## 🚀 Features ( :rocket: )
 
 ### CSRF Protection [PR #1006](https://github.com/apollographql/router/pull/1006)

From a20105b4373c2a4eda327ad34373605df1eaafcd Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Fri, 13 May 2022 12:38:23 +0200
Subject: [PATCH 18/19] hook the csrf plugin + send preflighted requests in our
 integration tests+ add an failed csrf check integration test

---
 apollo-router-core/src/plugins/csrf.rs   | 24 ++++-----
 apollo-router-core/src/plugins/mod.rs    |  2 +-
 apollo-router/src/configuration/mod.rs   | 27 ++++++++--
 apollo-router/tests/integration_tests.rs | 65 ++++++++++++++++++++++--
 4 files changed, 97 insertions(+), 21 deletions(-)

diff --git a/apollo-router-core/src/plugins/csrf.rs b/apollo-router-core/src/plugins/csrf.rs
index 7f0f049328..82a5c863b3 100644
--- a/apollo-router-core/src/plugins/csrf.rs
+++ b/apollo-router-core/src/plugins/csrf.rs
@@ -9,7 +9,7 @@ use tower::{BoxError, ServiceBuilder, ServiceExt};
 
 #[derive(Deserialize, Debug, Clone, JsonSchema)]
 #[serde(deny_unknown_fields)]
-struct CSRFConfig {
+pub struct CSRFConfig {
     /// The CSRF plugin is enabled by default;
     /// set unsafe_disabled = true to disable the plugin behavior
     /// Note that setting this to true is deemed unsafe https://developer.mozilla.org/en-US/docs/Glossary/CSRF
@@ -74,7 +74,7 @@ static NON_PREFLIGHTED_CONTENT_TYPES: &[&str] = &[
 /// won't execute operations at the request of origins who our CORS policy will
 /// block.
 #[derive(Debug, Clone)]
-struct Csrf {
+pub struct Csrf {
     config: CSRFConfig,
 }
 
@@ -95,18 +95,18 @@ impl Plugin for Csrf {
             ServiceBuilder::new()
                 .checkpoint(move |req: RouterRequest| {
                     if is_preflighted(&req, required_headers.as_slice()) {
+                        tracing::warn!("request is preflighted");
                         Ok(ControlFlow::Continue(req))
                     } else {
-                        let error = crate::Error {
-                            message: format!("This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \
-                            Please either specify a 'content-type' header (with a mime-type that is not one of {}) \
-                            or provide one of the following headers: {}", 
-                            NON_PREFLIGHTED_CONTENT_TYPES.join(", "),
-                            required_headers.join(", ")),
-                            locations: Default::default(),
-                            path: Default::default(),
-                            extensions: Default::default(),
-                        };
+                        tracing::warn!("request is not preflighted");
+                        let error = crate::Error::builder().message(
+                            format!(
+                                "This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \
+                                Please either specify a 'content-type' header (with a mime-type that is not one of {}) \
+                                or provide one of the following headers: {}", 
+                                NON_PREFLIGHTED_CONTENT_TYPES.join(", "),
+                                required_headers.join(", ")
+                            )).build();
                         let res = RouterResponse::builder()
                             .error(error)
                             .status_code(StatusCode::BAD_REQUEST)
diff --git a/apollo-router-core/src/plugins/mod.rs b/apollo-router-core/src/plugins/mod.rs
index 85655d1456..7949b9810f 100644
--- a/apollo-router-core/src/plugins/mod.rs
+++ b/apollo-router-core/src/plugins/mod.rs
@@ -2,7 +2,7 @@
 //!
 //! These plugins are compiled into the router and configured via YAML configuration.
 
-mod csrf;
+pub mod csrf;
 mod forbid_mutations;
 mod headers;
 mod include_subgraph_errors;
diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs
index 0522d52a41..ea071a433f 100644
--- a/apollo-router/src/configuration/mod.rs
+++ b/apollo-router/src/configuration/mod.rs
@@ -85,6 +85,10 @@ pub struct Configuration {
 
 const APOLLO_PLUGIN_PREFIX: &str = "apollo.";
 
+// Add your plugin to this list so it gets automatically set up if its not been provided a custom configuration.
+// ! requires the plugin configuration to implement Default
+const MANDATORY_APOLLO_PLUGINS: &[&str] = &["csrf"];
+
 fn default_listen() -> ListenAddr {
     SocketAddr::from_str("127.0.0.1:4000").unwrap().into()
 }
@@ -105,12 +109,27 @@ impl Configuration {
 
         // Add all the apollo plugins
         for (plugin, config) in &self.apollo_plugins.plugins {
-            plugins.push((
-                format!("{}{}", APOLLO_PLUGIN_PREFIX, plugin),
-                config.clone(),
-            ));
+            let plugin_full_name = format!("{}{}", APOLLO_PLUGIN_PREFIX, plugin);
+            tracing::debug!(
+                "adding plugin {} with user provided configuration",
+                plugin_full_name.as_str()
+            );
+            plugins.push((plugin_full_name, config.clone()));
         }
 
+        // Add the mandatory apollo plugins with defaults,
+        // if a custom configuration hasn't been provided by the user
+        MANDATORY_APOLLO_PLUGINS.iter().for_each(|plugin_name| {
+            let plugin_full_name = format!("{}{}", APOLLO_PLUGIN_PREFIX, plugin_name);
+            if !plugins.iter().any(|p| p.0 == plugin_full_name) {
+                tracing::debug!(
+                    "adding plugin {} with default configuration",
+                    plugin_full_name.as_str()
+                );
+                plugins.push((plugin_full_name, Value::Object(Map::new())));
+            }
+        });
+
         // Add all the user plugins
         if let Some(config_map) = self.plugins.plugins.as_ref() {
             for (plugin, config) in config_map {
diff --git a/apollo-router/tests/integration_tests.rs b/apollo-router/tests/integration_tests.rs
index a06ebff9a5..7e4faa996b 100644
--- a/apollo-router/tests/integration_tests.rs
+++ b/apollo-router/tests/integration_tests.rs
@@ -5,8 +5,9 @@
 use apollo_router::plugins::telemetry::config::Tracing;
 use apollo_router::plugins::telemetry::{self, apollo, Telemetry};
 use apollo_router_core::{
-    http_compat, prelude::*, Object, PluggableRouterServiceBuilder, Plugin, ResponseBody,
-    RouterRequest, RouterResponse, Schema, SubgraphRequest, TowerSubgraphService, ValueExt,
+    http_compat, plugins::csrf, prelude::*, Object, PluggableRouterServiceBuilder, Plugin,
+    ResponseBody, RouterRequest, RouterResponse, Schema, SubgraphRequest, TowerSubgraphService,
+    ValueExt,
 };
 use http::Method;
 use maplit::hashmap;
@@ -44,12 +45,14 @@ macro_rules! assert_federated_response {
         };
 
         let originating_request = http_compat::Request::fake_builder().method(Method::POST)
+            // otherwise the query would be a simple one,
+            // and CSRF protection would reject it
+            .header("content-type", "application/json")
             .body(request)
             .build().expect("expecting valid originating request");
 
         let (actual, registry) = query_rust(originating_request.into()).await;
 
-
         tracing::debug!("query:\n{}\n", $query);
 
         assert!(
@@ -110,6 +113,7 @@ async fn api_schema_hides_field() {
 
     let originating_request = http_compat::Request::fake_builder()
         .method(Method::POST)
+        .header("content-type", "application/json")
         .body(request)
         .build()
         .expect("expecting valid request");
@@ -198,6 +202,7 @@ async fn queries_should_work_over_get() {
 
     let originating_request = http_compat::Request::fake_builder()
         .body(request)
+        .header("content-type", "application/json")
         .build()
         .expect("expecting valid request");
 
@@ -207,6 +212,47 @@ async fn queries_should_work_over_get() {
     assert_eq!(registry.totals(), expected_service_hits);
 }
 
+#[tokio::test(flavor = "multi_thread")]
+async fn simple_queries_should_not_work() {
+    let expected_error =apollo_router_core::Error {
+        message :"This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). \
+        Please either specify a 'content-type' header \
+        (with a mime-type that is not one of application/x-www-form-urlencoded, multipart/form-data, text/plain) \
+        or provide one of the following headers: x-apollo-operation-name, apollo-require-preflight".to_string(),
+        ..Default::default()
+    };
+
+    let request = graphql::Request::builder()
+        .query(Some(
+            r#"{ topProducts { upc name reviews {id product { name } author { id name } } } }"#
+                .to_string(),
+        ))
+        .variables(Arc::new(
+            vec![
+                ("topProductsFirst".into(), 2.into()),
+                ("reviewsForAuthorAuthorId".into(), 1.into()),
+            ]
+            .into_iter()
+            .collect(),
+        ))
+        .build();
+
+    let originating_request = http_compat::Request::fake_builder()
+        .body(request)
+        .build()
+        .expect("expecting valid request");
+
+    let (actual, registry) = query_rust(originating_request.into()).await;
+
+    assert_eq!(
+        1,
+        actual.errors.len(),
+        "CSRF should have rejected this query"
+    );
+    assert_eq!(expected_error, actual.errors[0]);
+    assert_eq!(registry.totals(), hashmap! {});
+}
+
 #[tokio::test(flavor = "multi_thread")]
 async fn queries_should_work_over_post() {
     let request = graphql::Request::builder()
@@ -232,6 +278,7 @@ async fn queries_should_work_over_post() {
 
     let http_request = http_compat::Request::fake_builder()
         .method(Method::POST)
+        .header("content-type", "application/json")
         .body(request)
         .build()
         .expect("expecting valid request");
@@ -263,6 +310,7 @@ async fn service_errors_should_be_propagated() {
 
     let originating_request = http_compat::Request::fake_builder()
         .body(request)
+        .header("content-type", "application/json")
         .build()
         .expect("expecting valid request");
 
@@ -306,6 +354,7 @@ async fn mutation_should_not_work_over_get() {
 
     let originating_request = http_compat::Request::fake_builder()
         .body(request)
+        .header("content-type", "application/json")
         .build()
         .expect("expecting valid request");
 
@@ -351,6 +400,7 @@ async fn mutation_should_work_over_post() {
 
     let http_request = http_compat::Request::fake_builder()
         .method(Method::POST)
+        .header("content-type", "application/json")
         .body(request)
         .build()
         .expect("expecting valid request");
@@ -402,6 +452,7 @@ async fn automated_persisted_queries() {
 
     let originating_request = http_compat::Request::fake_builder()
         .body(apq_only_request)
+        .header("content-type", "application/json")
         .build()
         .expect("expecting valid request");
 
@@ -425,6 +476,7 @@ async fn automated_persisted_queries() {
 
     let originating_request = http_compat::Request::fake_builder()
         .body(apq_request_with_query)
+        .header("content-type", "application/json")
         .build()
         .expect("expecting valid request");
 
@@ -443,6 +495,7 @@ async fn automated_persisted_queries() {
 
     let originating_request = http_compat::Request::fake_builder()
         .body(apq_only_request)
+        .header("content-type", "application/json")
         .build()
         .expect("expecting valid request");
 
@@ -501,6 +554,7 @@ async fn missing_variables() {
 
     let originating_request = http_compat::Request::fake_builder()
         .method(Method::POST)
+        .header("content-type", "application/json")
         .body(request)
         .build()
         .expect("expecting valid request");
@@ -568,7 +622,10 @@ async fn setup_router_and_registry() -> (
     })
     .await
     .unwrap();
-    builder = builder.with_dyn_plugin("apollo.telemetry".to_string(), Box::new(telemetry_plugin));
+    let csrf_plugin = csrf::Csrf::new(Default::default()).await.unwrap();
+    builder = builder
+        .with_dyn_plugin("apollo.telemetry".to_string(), Box::new(telemetry_plugin))
+        .with_dyn_plugin("apollo.csrf".to_string(), Box::new(csrf_plugin));
     for (name, _url) in subgraphs {
         let cloned_counter = counting_registry.clone();
         let cloned_name = name.clone();

From b35ed95a3140ca906bec27ec5209d9ce6aed6610 Mon Sep 17 00:00:00 2001
From: o0Ignition0o 
Date: Fri, 13 May 2022 12:40:44 +0200
Subject: [PATCH 19/19] revert CONTRIBUTING.md as it will be followed up on by
 @SimonSapin

---
 CONTRIBUTING.md | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4f59f50336..b2b86b14ad 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -61,9 +61,6 @@ and [Apollo's Docs Theme for Gatsby](https://github.com/apollographql/gatsby-the
 .
 
 To contribute to these docs, you can add or edit the markdown & MDX files in the `docs/source` directory.
-
+npm start
 ```
 
 This will start up a development server with live reload enabled. You can see the docs by