fix(axum): enforce mutation request boundaries

req: security/003
This commit is contained in:
slhx agent
2026-07-13 21:37:55 +02:00
parent 3ac9549cc3
commit 56ace537f3
6 changed files with 127 additions and 23 deletions
+62 -2
View File
@@ -1,6 +1,7 @@
use axum::extract::State;
use axum::http::{header, HeaderMap};
use axum::extract::{DefaultBodyLimit, State};
use axum::http::{header, HeaderMap, Request, StatusCode};
use axum::response::IntoResponse;
use axum::{body::Body, routing::post, Router};
use hemx_axum::{
interactions, runtime_js, runtime_js_hash, runtime_js_path, runtime_js_route_path,
runtime_js_script_src, runtime_js_source, DispatchRejection, EffectResponse, Form,
@@ -10,6 +11,65 @@ use hemx_axum::{
};
use hemx_core::{push, BuildFingerprint, Handle, IntoEffect, SafeHtml, Slot};
use scraper::{Html, Selector};
use std::sync::atomic::{AtomicUsize, Ordering};
use tower::ServiceExt;
static MUTATION_CALLS: AtomicUsize = AtomicUsize::new(0);
async fn bounded_mutation(_: InteractionRequest) -> StatusCode {
MUTATION_CALLS.fetch_add(1, Ordering::SeqCst);
StatusCode::NO_CONTENT
}
#[tokio::test]
async fn interaction_boundary_honors_media_type_and_host_body_limit() {
// test req: security/003
MUTATION_CALLS.store(0, Ordering::SeqCst);
let app = Router::new()
.route("/mutate", post(bounded_mutation))
.layer(DefaultBodyLimit::max(32));
let unsupported = app
.clone()
.oneshot(
Request::post("/mutate")
.header(header::CONTENT_TYPE, "application/json")
.body(Body::from(r#"{"__h":"1"}"#))
.unwrap(),
)
.await
.unwrap();
assert_eq!(unsupported.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
assert_eq!(MUTATION_CALLS.load(Ordering::SeqCst), 0);
let oversized = app
.clone()
.oneshot(
Request::post("/mutate")
.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.body(Body::from(format!("__h=1&value={}", "x".repeat(64))))
.unwrap(),
)
.await
.unwrap();
assert_eq!(oversized.status(), StatusCode::PAYLOAD_TOO_LARGE);
assert_eq!(MUTATION_CALLS.load(Ordering::SeqCst), 0);
let accepted = app
.oneshot(
Request::post("/mutate")
.header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded; charset=utf-8",
)
.body(Body::from("__h=1"))
.unwrap(),
)
.await
.unwrap();
assert_eq!(accepted.status(), StatusCode::NO_CONTENT);
assert_eq!(MUTATION_CALLS.load(Ordering::SeqCst), 1);
}
fn selector(value: &str) -> Selector {
Selector::parse(value).expect("test selector parses")