Passa al contenuto principale

Trust boundary degli input

Questa pagina documenta i confini di fiducia del Playroll Core (playroll-core.exe) e la validazione applicata su ciascuno di essi. È pensata per chi rivede le pull request, per chi conduce audit di sicurezza e per chi integra la API HTTP locale.

Il Core è un servizio di background per Windows che:

  • Accetta JSON su http://127.0.0.1:8080 da un singolo client locale fidato (la UI Electron).
  • Legge il catalogo dei giochi da Supabase come JSON.
  • Legge file di configurazione del motore di gioco (.ini, .xml) sotto %APPDATA% per i giochi rilevati.
  • Scrive le registrazioni sotto %USERPROFILE%\Videos\PlayrollCaptures (o un percorso configurato dall'utente).
  • Carica le registrazioni completate su S3 tramite URL pre-firmati di breve durata ottenuti da una Edge Function di Supabase.

Il Core non accetta traffico di rete dall'esterno dell'interfaccia loopback, non invoca processi esterni a partire da input controllato dall'utente, e non incorpora alcun motore di scripting.

Cosa consideriamo fidato

SuperficiePerché fidato
Il binario firmato playroll-core.exeCostruito e firmato dalla pipeline dell'installer (DigiCert).
Valori di registro HKCU\Software\Playnite\PlayRoll\*Scritti solo dall'installer / dalla UI sulla stessa macchina.
File sotto %LOCALAPPDATA%\Playroll\* (token di autenticazione, session secret, DB SQLite)Scritti solo dal Core / dalla UI sulla stessa macchina. Le ACL di default sono quelle del profilo utente.
La UI Electron quando presenta un session secret valido su 127.0.0.1:8080Segreto condiviso stabilito all'installazione; vedi Raccolta dati.
Dipendenze vcpkg hard-codedBloccate dal baseline del manifest + hash del manifest vcpkg.

Cosa consideriamo non fidato

SuperficieOriginePerché non fidato
Body delle richieste HTTPUI Electron locale (dopo l'auth con session secret)Difesa in profondità: un'estensione malevola o una UI compromessa non deve poter corrompere il Core.
Catalogo giochi SupabaseTabella games / Edge FunctionIl contenuto del database è controllato lato server ma entra nel processo C++ come JSON; trattato come non fidato per mantenere il Core robusto contro dati corrotti, drift di schema o future lacune di moderazione dei contenuti.
File di configurazione del motore di gioco (.ini, .xml) sotto %APPDATA%\<Game>\Scritti dal gioco stessoI file dei giochi non sono sotto il nostro controllo; li leggiamo solo per sondare le impostazioni di risoluzione.
Query string HTTP e parametri di pathUI localeStessa motivazione dei body.

Superfici di input e difese attuali

API HTTP

Tutte le route sono definite in src/service/http_server.cpp e nei file *_http_callbacks.cpp. Difese:

DifesaDoveNote
Bind solo su loopbackhttp_server.cppIl listener è su 127.0.0.1:8080; il traffico LAN viene rifiutato a livello di socket.
Autenticazione con session secretrequireSessionSecret()Ogni endpoint che modifica stato richiede il segreto da %LOCALAPPDATA%\Playroll\core-session-secret.txt.
Bearer JWT per le chiamate autenticaterequireAuth()Valida la presenza del token e il suo hint di scadenza locale.
Parsing JSON racchiuso in try/catchhandleJsonRequest()JSON malformato restituisce 400 ed è loggato come http.invalid_json (con throttling).
Estrazione tipata con fallbackjsonGet<T>()Campi mancanti o con tipo sbagliato ricadono su un valore di default, senza mai propagare eccezioni fuori dall'handler.
Cap sulla lunghezza delle stringhejsonGetStringCapped() in include/util/json_validation.hppApplicato ai campi JWT (8 KB), agli ID encoder (128 B), agli identificatori di lobby (256 B). I valori oversize vengono rifiutati come se mancassero.
Clamp dei range numericijsonGetClamped<T>() in include/util/json_validation.hppHelper disponibile per qualunque futuro campo numerico del body che governi sizing o allocazione.
Enum su whitelist/api/uploader/action, /api/encoders/selectVerbi di azione e ID encoder sono validati contro liste fisse prima di raggiungere il codice della macchina a stati.

Catalogo giochi Supabase

Il parsing è in src/service/game_catalog_parser.cpp. Difese:

DifesaDove
.value(key, default) e check .is_array() invece di .get<T>() nudoparseSupabaseGameCatalog()
display_name viene sanitizzato prima di essere usato come nome cartellasrc/service/recording_paths.cpp via util::sanitizeFilenameComponent
Boundary check con weakly_normal sul path di registrazione finalesrc/service/recording_paths.cpp via util::pathIsWithin
Comportamento del sanitizer — vedi la sezione successivasrc/util/path_sanitize.cpp

Path derivati da input esterno

Un singolo helper governa ogni componente di un path di registrazione che proviene dall'esterno del Core: util::sanitizeFilenameComponent in include/util/path_sanitize.hpp. È intenzionalmente un sanitizer di un singolo componente (non di path multi-segmento) e produce risultati che:

  • Non contengono separatori di path (/, \), separatori di drive (:), wildcard (*, ?), virgolette ("), parentesi angolari (<, >), pipe (|) o caratteri di controllo ASCII.
  • Non sono mai uguali a un nome di device riservato Windows (CON, PRN, AUX, NUL, COM1..COM9, LPT1..LPT9), nemmeno con un'estensione.
  • Non hanno punti o spazi iniziali o finali (che Windows rimuove silenziosamente).
  • Sono limitati a 100 caratteri wide.
  • Non sono mai vuoti (restituisce unnamed per un input che si sanitizza a vuoto).

Dopo la sanitizzazione, recording_paths.cpp ricostruisce il path completo e verifica con util::pathIsWithin che il risultato sia un discendente stretto della root delle registrazioni dell'utente. Se non lo è (bug del sanitizer o regressione futura), ricade su un nome letterale _unsafe_game_folder ed emette l'evento di log recording.unsafe_game_folder.

File di configurazione dei motori di gioco

src/service/resolution_probe.cpp parsa file XML e JSON scritti dai giochi. Il parser:

  • Carica file solo da path derivati da %PROGRAMFILES%, %USERPROFILE% o %LOCALAPPDATA% dopo ExpandEnvironmentStringsW e un check looksResolved() che rifiuta stringhe con placeholder %TOKEN% non risolti.
  • Usa pugixml per l'XML e lo stesso pattern .find() / .is_object() / .is_string() per il JSON.

Questi file provengono da giochi installati sulla macchina dell'utente stesso, quindi il threat model assume un produttore benigno-ma-buggato anziché un attaccante.

Query SQL

Tutti gli accessi a SQLite passano da src/service/sqlite_state.cpp. Ogni query usa sqlite3_prepare_v2() con parametri sqlite3_bind_*() — non c'è concatenazione di stringhe SQL da nessuna parte nel Core.

Superfici del sistema operativo che non esponiamo

SuperficieStato
CreateProcess / ShellExecute / system da input utenteNon usati. La pipeline di cattura inizializza NVENC e il muxer ffmpeg in-process solo via chiamate di libreria.
Caricamento di DLL fornite dall'utenteNon usato.
Motore di scripting incorporato (Lua, JS, ...)Non presente.
Traffico in ingresso non-loopbackNon in bind.

Cosa deliberatamente non distribuiamo

  • libx264 non è linkato. L'encoding H.264 passa direttamente dal Video SDK NVIDIA (NVENC); il muxer fa riferimento a AV_CODEC_ID_H264 solo come costante.
  • Le feature ffmpeg[nonfree] (es. libfdk-aac, libnpp) non sono abilitate. L'encoding AAC è fatto da Windows Media Foundation; nessun codec nonfree finisce nel binario.
  • spdlog e fmt non sono linkati. Il logger è implementato direttamente sopra std::ofstream e nlohmann::json.

Queste esclusioni riducono la superficie di attacco e mantengono il binario ridistribuibile sotto i termini LGPL.

Segnalare una vulnerabilità

Inviare segnalazioni di sicurezza a security@extrinsiccognition.com. Includere:

  • Una descrizione chiara del problema e dell'impatto presunto.
  • Passi per riprodurlo, idealmente contro una build locale di playroll-core.exe.
  • L'etichetta di versione restituita da GET http://127.0.0.1:8080/status.

L'obiettivo è confermare la ricezione entro due giorni lavorativi. Non aprire issue di sicurezza come issue pubbliche su GitHub.