Files
punktfunk/crates/punktfunk-host/src/mgmt/session.rs
T
enricobuehler 600693914f fix(security): land the 2026-07 audit fixes — SSRF guards, roster lane, SYSTEM path hygiene
The low/medium findings from the July host+Windows security review, as
implemented in the audit session's working tree:

- Webhooks and library-art fetches refuse loopback/link-local/metadata
  targets and no longer follow redirects (SSRF pivots from the privileged
  host process).
- The paired-client rosters (/clients, /native/clients) move off the
  streaming-client auth lane — one paired device could enumerate every other
  device's name + fingerprint; only the bearer/loopback console keeps them.
- Device-name sanitizing extends to bidi/format control characters
  (spoofable rendering) via the shared native_pairing::is_spoofy_char;
  stream-marker quoting uses the same set.
- The SYSTEM service resolves powershell by its full System32 path —
  CreateProcess checks the launching EXE's own directory first, so a planted
  powershell.exe beside the host binary would have run as SYSTEM.
- The pf-vdisplay driver's opt-in file log moves from world-writable
  C:\Users\Public to WUDFHost's own temp dir.
- GameStream pairing sessions are single-use (removed whatever the outcome).
- Uninstall also removes the pf_mouse driver-store entry (rider from the
  virtual-HID-mouse work).
- openapi.json regenerated (hardened-config-dir doc wording).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:20:15 +02:00

66 lines
2.5 KiB
Rust

//! Session-tagged management endpoints: stop the active session, force an IDR. Split out of the
//! `mgmt` facade (plan §W5).
use super::shared::*;
use std::sync::atomic::Ordering;
/// Stop the active session
///
/// Kicks the connected client: stops the video/audio stream threads and clears the launch
/// state. Idempotent — succeeds even when nothing is streaming.
#[utoipa::path(
delete,
path = "/session",
tag = "session",
operation_id = "stopSession",
responses(
(status = NO_CONTENT, description = "Session stopped (or none was active)"),
(status = UNAUTHORIZED, description = "Missing or invalid bearer token", body = ApiError),
)
)]
pub(crate) async fn stop_session(State(st): State<Arc<MgmtState>>) -> StatusCode {
let was_streaming = st.app.streaming.swap(false, Ordering::SeqCst);
st.app.audio_streaming.store(false, Ordering::SeqCst);
*st.app.launch.lock().unwrap_or_else(|e| e.into_inner()) = None;
*st.app.stream.lock().unwrap_or_else(|e| e.into_inner()) = None;
// Native plane: the GameStream flags above don't reach it (it runs its own loops off the shared
// session registry), so signal every live native session to tear down too.
let native = crate::session_status::count();
crate::session_status::stop_all();
tracing::info!(
was_streaming,
native_sessions = native,
"management API: session stopped"
);
StatusCode::NO_CONTENT
}
/// Force a keyframe
///
/// Asks the encoder for an IDR frame on the active video stream (what a client requests
/// after unrecoverable loss — exposed for debugging).
#[utoipa::path(
post,
path = "/session/idr",
tag = "session",
operation_id = "requestIdr",
responses(
(status = ACCEPTED, description = "Keyframe requested"),
(status = UNAUTHORIZED, description = "Missing or invalid bearer token", body = ApiError),
(status = CONFLICT, description = "No active video stream", body = ApiError),
)
)]
pub(crate) async fn request_idr(State(st): State<Arc<MgmtState>>) -> Response {
let gs = st.app.streaming.load(Ordering::SeqCst);
let native = crate::session_status::count();
if !gs && native == 0 {
return api_error(StatusCode::CONFLICT, "no active video stream");
}
if gs {
st.app.force_idr.store(true, Ordering::SeqCst);
}
// Native sessions get the keyframe request through their registry flag (see `session_status`).
crate::session_status::force_idr_all();
StatusCode::ACCEPTED.into_response()
}