fix(tauri): validate game id in get_game_thumbnail and drop dbg!

Security audit finding SEC-IPC-03. The thumbnail IPC command resolved
`assets/{game_id}.jpg` from the resource directory without checking the
ID, unlike every other command that maps a game ID to a path. A crafted
ID containing path separators could therefore read any `.jpg` reachable
from the resource root. The handler also still carried a `dbg!` that
printed the resolved path to stderr in release builds.

The command now rejects anything that is not a single normal path
component with an `InvalidInput` I/O error, using the same
`is_single_component_game_id` gate as run_game and start_server. The
frontend already treats a failed thumbnail request as "no thumbnail".

Test plan: `just clippy`, `just test`. In the app, thumbnails for
catalog games still load; an invoke with game_id "../x" is rejected.

Claude-Session: https://claude.ai/code/session_017C3Nbgwpdm3YNwZhhFLHwg
This commit is contained in:
2026-09-02 22:27:39 +02:00
parent 3c15810676
commit a080f93ec1
@@ -1539,13 +1539,17 @@ async fn get_game_thumbnail(
use base64::Engine;
let _app_invoke = enter_app_invoke(state.inner())?;
if !is_single_component_game_id(&game_id) {
log::warn!("Ignoring thumbnail request for invalid game id: {game_id}");
return Err(
std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid game id").into(),
);
}
let resource_path = app_handle.path().resolve(
format!("assets/{game_id}.jpg"),
tauri::path::BaseDirectory::Resource,
)?;
dbg!(&resource_path);
let image_data = scoped_blocking(|| std::fs::read(&resource_path))?;
let base64_data = base64::engine::general_purpose::STANDARD.encode(&image_data);
Ok(format!("data:image/jpeg;base64,{base64_data}"))