fix(cdp): truncate client-controlled log previews on char boundaries (#397)

server.rs logged previews of attacker-controlled strings with raw byte
slicing: `&text[..text.len().min(200)]` for a malformed CDP frame (inside a
`warn!`, which is active at the worker's default log level) and the same for
an intercepted event JSON. `&s[..n]` panics when byte `n` falls inside a
multi-byte UTF-8 character, so a single WebSocket frame that is invalid JSON,
>=201 bytes, with a multi-byte char straddling byte 200 panics the CDP
processor task — an unauthenticated one-frame crash. page.rs had the same
pattern (behind --verbose debug logging) for evaluated JS expressions.

Add truncate_on_char_boundary (str::floor_char_boundary is still unstable) and
route all four sites through it. Regression test constructs the exact 'a'*199 +
'€' input, asserts the old raw slice panics (via catch_unwind) and the helper
truncates on the boundary instead.
This commit is contained in:
Andrey Mnatsakanov authored and GitHub committed 2026-07-13 22:45:57 +00:00
1 parent d8e9fc2196
commit c915a11f59
3 files changed
+80 -5

No files matched your search

+2 -2
View File
@@ -717,7 +717,7 @@ async fn process_with_interception(
"sessionId": session_for_events,
});
let event_str = event_json.to_string();
tracing::info!("INTERCEPTION event JSON: {}", &event_str[..event_str.len().min(300)]);
tracing::info!("INTERCEPTION event JSON: {}", crate::util::truncate_on_char_boundary(&event_str, 300));
let _ = reply_tx.send(event_str);
intercepted_paused.insert(intercepted.request_id.clone(), intercepted.resolver);
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
@@ -829,7 +829,7 @@ async fn process_cdp_message(
let req: CdpRequest = match serde_json::from_str(text) {
Ok(r) => r,
Err(e) => {
warn!("Invalid CDP: {}: {}", e, &text[..text.len().min(200)]);
warn!("Invalid CDP: {}: {}", e, crate::util::truncate_on_char_boundary(text, 200));
return;
}
};