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>
This commit is contained in:
2026-07-17 17:14:57 +02:00
parent 4d89dcd3d7
commit 600693914f
21 changed files with 292 additions and 75 deletions
+4 -3
View File
@@ -157,11 +157,12 @@ mod imp {
}
/// Make a client name safe to place inside single quotes in a sourceable file: drop the single
/// quote itself and any control characters (newlines especially), and cap the length so a hostile
/// or absurd name can't bloat the file. Empty stays empty.
/// quote itself, any control characters (newlines especially), and any bidi/format control that
/// could spoof the displayed name (the shared [`crate::native_pairing::is_spoofy_char`] set), and
/// cap the length so a hostile or absurd name can't bloat the file. Empty stays empty.
fn sanitize(name: &str) -> String {
name.chars()
.filter(|c| *c != '\'' && !c.is_control())
.filter(|c| *c != '\'' && !c.is_control() && !crate::native_pairing::is_spoofy_char(*c))
.take(96)
.collect()
}