From a0692f9f540967166204ab09182ae1d5a0cd2134 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 2 Sep 2025 21:34:17 +0200 Subject: [PATCH] Reject JSON bodies with trailing chars (#3453) --- axum/src/json.rs | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/axum/src/json.rs b/axum/src/json.rs index c8c9b60b..59f2c859 100644 --- a/axum/src/json.rs +++ b/axum/src/json.rs @@ -189,12 +189,16 @@ where } } - let deserializer = &mut serde_json::Deserializer::from_slice(bytes); + let mut deserializer = serde_json::Deserializer::from_slice(bytes); - match serde_path_to_error::deserialize(deserializer) { - Ok(value) => Ok(Json(value)), - Err(err) => Err(make_rejection(err)), - } + serde_path_to_error::deserialize(&mut deserializer) + .map_err(make_rejection) + .and_then(|value| { + deserializer + .end() + .map(|()| Self(value)) + .map_err(|err| JsonSyntaxError::from_err(err).into()) + }) } } @@ -311,6 +315,30 @@ mod tests { assert_eq!(res.status(), StatusCode::BAD_REQUEST); } + #[crate::test] + async fn extra_chars_after_valid_json_syntax() { + #[derive(Debug, Deserialize)] + struct Input { + foo: String, + } + + let app = Router::new().route("/", post(|input: Json| async { input.0.foo })); + + let client = TestClient::new(app); + let res = client + .post("/") + .body(r#"{ "foo": "bar" } baz "#) + .header("content-type", "application/json") + .await; + + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + let body_text = res.text().await; + assert_eq!( + body_text, + "Failed to parse the request body as JSON: trailing characters at line 1 column 18" + ); + } + #[derive(Deserialize)] struct Foo { #[allow(dead_code)]