fix(security): V-01,V-02,V-04,V-05,V-06 - multiple vulnerability fixes

- V-01: CalDAV unbounded body limit → MAX_CALDAV_BODY = 1MB
- V-02: CardDAV unbounded body limit → MAX_CARDDAV_BODY = 1MB
- V-04: Batch operations without max size → MAX_BATCH_SIZE = 1000
- V-05: WOPI get_editor_url IDOR → authorize_wopi_access with ownership check
- V-06: JWT secret without entropy validation → panic <16, warn 16-31, accept >=32
This commit is contained in:
Dionisio
2026-03-05 16:57:44 +01:00
parent 4197cc3b7b
commit 4d81bcbd7b
5 changed files with 176 additions and 37 deletions
@@ -14,6 +14,10 @@ use crate::application::services::batch_operations::{
use crate::interfaces::api::handlers::ApiResult;
use crate::interfaces::middleware::auth::AuthUser;
/// Maximum number of items allowed in a single batch request.
/// Prevents fan-out amplification attacks and database connection exhaustion.
const MAX_BATCH_SIZE: usize = 1_000;
/// Shared state for the batch handler
#[derive(Clone)]
pub struct BatchHandlerState {
@@ -143,6 +147,15 @@ pub async fn move_files_batch(
)
.into_response());
}
if request.file_ids.len() > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", request.file_ids.len(), MAX_BATCH_SIZE)
})),
)
.into_response());
}
// Execute batch operation
let result = state
@@ -187,6 +200,15 @@ pub async fn copy_files_batch(
)
.into_response());
}
if request.file_ids.len() > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", request.file_ids.len(), MAX_BATCH_SIZE)
})),
)
.into_response());
}
// Execute batch operation
let result = state
@@ -231,6 +253,15 @@ pub async fn delete_files_batch(
)
.into_response());
}
if request.file_ids.len() > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", request.file_ids.len(), MAX_BATCH_SIZE)
})),
)
.into_response());
}
// Execute batch operation
let result = state
@@ -283,6 +314,15 @@ pub async fn delete_folders_batch(
)
.into_response());
}
if request.folder_ids.len() > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", request.folder_ids.len(), MAX_BATCH_SIZE)
})),
)
.into_response());
}
// Execute batch operation
let result = state
@@ -335,6 +375,15 @@ pub async fn create_folders_batch(
)
.into_response());
}
if request.folders.len() > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", request.folders.len(), MAX_BATCH_SIZE)
})),
)
.into_response());
}
// Transform the format for the service
let folders = request
@@ -386,6 +435,15 @@ pub async fn get_files_batch(
)
.into_response());
}
if request.file_ids.len() > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", request.file_ids.len(), MAX_BATCH_SIZE)
})),
)
.into_response());
}
// Execute batch operation
let result = state
@@ -430,6 +488,15 @@ pub async fn get_folders_batch(
)
.into_response());
}
if request.folder_ids.len() > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", request.folder_ids.len(), MAX_BATCH_SIZE)
})),
)
.into_response());
}
// Execute batch operation
let result = state
@@ -495,6 +562,16 @@ pub async fn trash_batch(
)
.into_response());
}
let combined_size = request.file_ids.len() + request.folder_ids.len();
if combined_size > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", combined_size, MAX_BATCH_SIZE)
})),
)
.into_response());
}
let mut all_successful: Vec<String> = Vec::new();
let mut all_failed: Vec<FailedOperation> = Vec::new();
@@ -597,6 +674,15 @@ pub async fn move_folders_batch(
)
.into_response());
}
if request.folder_ids.len() > MAX_BATCH_SIZE {
return Ok((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": format!("Batch size {} exceeds maximum of {}", request.folder_ids.len(), MAX_BATCH_SIZE)
})),
)
.into_response());
}
let result = state
.batch_service
@@ -637,6 +723,13 @@ pub async fn download_batch(
"No file or folder IDs provided".to_string(),
));
}
let combined_size = request.file_ids.len() + request.folder_ids.len();
if combined_size > MAX_BATCH_SIZE {
return Err((
StatusCode::BAD_REQUEST,
format!("Batch size {} exceeds maximum of {}", combined_size, MAX_BATCH_SIZE),
));
}
let temp_file = state
.batch_service