From eb3205a1969fe4afbdce1f49313de3df3045b078 Mon Sep 17 00:00:00 2001 From: easonysliu Date: Thu, 9 Apr 2026 14:35:51 +0800 Subject: [PATCH] fix: parse_date() panics on invalid date input from HTTP query params The parse_date() utility function called .unwrap() directly on user- controlled date strings from HTTP query parameters, allowing any authenticated user to crash the server with a malformed RFC3339 date. Change parse_date() to return Result and update all callers in events.rs to propagate the error with ?, returning a 400 Bad Request instead of panicking. The one call-site using a hardcoded literal (sends.rs) uses .expect() with an explanatory message. Affected endpoints: - GET /organizations/{id}/events?start=...&end=... - GET /ciphers/{id}/events?start=...&end=... - GET /organizations/{id}/users/{id}/events?start=...&end=... - POST /collect (event collection endpoint) --- src/api/core/events.rs | 20 ++++++++++---------- src/api/core/sends.rs | 2 +- src/util.rs | 7 +++++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/api/core/events.rs b/src/api/core/events.rs index 2f33a407..d74344f7 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -41,11 +41,11 @@ async fn get_org_events(org_id: OrganizationId, data: EventRange, headers: Admin let events_json: Vec = if !CONFIG.org_events_enabled() { Vec::with_capacity(0) } else { - let start_date = parse_date(&data.start); + let start_date = parse_date(&data.start)?; let end_date = if let Some(before_date) = &data.continuation_token { - parse_date(before_date) + parse_date(before_date)? } else { - parse_date(&data.end) + parse_date(&data.end)? }; Event::find_by_organization_uuid(&org_id, &start_date, &end_date, &conn) @@ -71,11 +71,11 @@ async fn get_cipher_events(cipher_id: CipherId, data: EventRange, headers: Heade } else { let mut events_json = Vec::with_capacity(0); if Membership::user_has_ge_admin_access_to_cipher(&headers.user.uuid, &cipher_id, &conn).await { - let start_date = parse_date(&data.start); + let start_date = parse_date(&data.start)?; let end_date = if let Some(before_date) = &data.continuation_token { - parse_date(before_date) + parse_date(before_date)? } else { - parse_date(&data.end) + parse_date(&data.end)? }; events_json = Event::find_by_cipher_uuid(&cipher_id, &start_date, &end_date, &conn) @@ -110,11 +110,11 @@ async fn get_user_events( let events_json: Vec = if !CONFIG.org_events_enabled() { Vec::with_capacity(0) } else { - let start_date = parse_date(&data.start); + let start_date = parse_date(&data.start)?; let end_date = if let Some(before_date) = &data.continuation_token { - parse_date(before_date) + parse_date(before_date)? } else { - parse_date(&data.end) + parse_date(&data.end)? }; Event::find_by_org_and_member(&org_id, &member_id, &start_date, &end_date, &conn) @@ -173,7 +173,7 @@ async fn post_events_collect(data: Json>, headers: Headers, } for event in data.iter() { - let event_date = parse_date(&event.date); + let event_date = parse_date(&event.date)?; match event.r#type { 1000..=1099 => { _log_user_event( diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index 10bf85be..6a1982dc 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -23,7 +23,7 @@ use crate::{ const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available"; static ANON_PUSH_DEVICE: LazyLock = LazyLock::new(|| { - let dt = crate::util::parse_date("1970-01-01T00:00:00.000000Z"); + let dt = crate::util::parse_date("1970-01-01T00:00:00.000000Z").expect("hardcoded epoch date is always valid"); Device { uuid: String::from("00000000-0000-0000-0000-000000000000").into(), created_at: dt, diff --git a/src/util.rs b/src/util.rs index 6da1c3df..e25c550e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -483,8 +483,11 @@ pub fn format_datetime_http(dt: &DateTime) -> String { expiry_time.to_rfc2822().replace("+0000", "GMT") } -pub fn parse_date(date: &str) -> NaiveDateTime { - DateTime::parse_from_rfc3339(date).unwrap().naive_utc() +pub fn parse_date(date: &str) -> Result { + match DateTime::parse_from_rfc3339(date) { + Ok(dt) => Ok(dt.naive_utc()), + Err(e) => err!(format!("Invalid RFC3339 date '{date}': {e}")), + } } /// Returns true or false if an email address is valid or not