From 642cd747effe0117e1021f1edfaa80d8f470accb Mon Sep 17 00:00:00 2001 From: Nawid3333 <102778966+Nawid3333@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:18:17 +0200 Subject: [PATCH] mpv host 8: one exit, after the message is handled (code scanning #183) Each branch of main() ended with its own process.exit(0) behind a check of the message, and CodeQL counts process.exit as a sensitive action: the check before it - whether an mpv exists for the options page's path - read as user input deciding it (js/user-controlled-bypass; #111 the same at the http(s) check). handleMessage() now only returns, and main() exits once after it. Same behaviour: every branch sends its answer first, then the host ends. HostVersion and RequiredHostVersion 8, hashes recorded. Co-Authored-By: Claude Opus 5.5 --- chrome/background/MpvBackend.mjs | 2 +- native-host/faststream-mpv-host.mjs | 26 ++++++++++++++++---------- tests/unit/mpvHostVersion.test.mjs | 4 ++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/chrome/background/MpvBackend.mjs b/chrome/background/MpvBackend.mjs index 7ba458749..b49790c83 100644 --- a/chrome/background/MpvBackend.mjs +++ b/chrome/background/MpvBackend.mjs @@ -47,7 +47,7 @@ const NativeHostName = 'com.faststream.mpv'; // An answer with a lower version, or none (a host from before 2026-10-04), is an // outdated host: the stream still goes to it, and the toolbar button, the player's mpv // button and "Test mpv connection" say to install the host again. -export const RequiredHostVersion = 7; +export const RequiredHostVersion = 8; // The largest message the host reads (MaxMessageBytes in native-host/faststream-mpv-host.mjs), // as Firefox sends it: the JSON in UTF-8. A bigger one was never read: the host quit diff --git a/native-host/faststream-mpv-host.mjs b/native-host/faststream-mpv-host.mjs index f7e01c11b..c01d25e11 100644 --- a/native-host/faststream-mpv-host.mjs +++ b/native-host/faststream-mpv-host.mjs @@ -71,7 +71,7 @@ const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); // install.ps1 (what it installs is part of the host a PC has), together with // RequiredHostVersion and the hashes in tests/unit/mpvHostVersion.test.mjs, which fails // until they agree. -export const HostVersion = 7; +export const HostVersion = 8; // No mpv path from the environment (FASTSTREAM_MPV_PATH until 2026-10-04): config.json's // mpvPath and the options page's path name one, and an environment variable reached @@ -1638,18 +1638,20 @@ export function launchDirect(mpvPath, args) { }); } -async function main() { +/** + * Answers the one message the extension sends this run (a watch: for as long as its port + * stays open). It only returns; main() ends the process. + */ +async function handleMessage() { const config = readConfig(); const message = await readMessage(); if (message === TooLarge) { debugLog(config, 'too-large', {}); await sendMessage({ok: false, error: 'the message was too large for the mpv host'}); - process.exit(0); return; } debugLog(config, 'received', loggedMessage(message)); if (!message || typeof message !== 'object') { - process.exit(0); return; } @@ -1661,7 +1663,6 @@ async function main() { } else { await sendMessage({ok: true, mpv: false}); } - process.exit(0); return; } @@ -1671,7 +1672,6 @@ async function main() { const status = await queryDecoder(message.waitMs); debugLog(config, 'status', status); await sendMessage({ok: true, ...status}); - process.exit(0); return; } @@ -1687,7 +1687,6 @@ async function main() { await watch.done; // The last message ('closed') out before the host goes. await sendMessage({type: 'bye'}); - process.exit(0); return; } @@ -1695,23 +1694,30 @@ async function main() { if (!isStreamUrl(message.url)) { debugLog(config, 'refused', {url: message.url}); await sendMessage({ok: false, error: 'mpv is only given http(s) streams'}); - process.exit(0); return; } const mpvPath = resolveMpvPath(message.mpvPath); if (!mpvPath) { await sendMessage({ok: false, error: mpvNotFoundError(message.mpvPath)}); - process.exit(0); return; } const result = await launchMpv(mpvPath, message, config); debugLog(config, 'launched', {mpvPath, url: message.url, result}); await sendMessage(result); - process.exit(0); return; } await sendMessage({ok: false, error: 'unknown message'}); +} + +/** + * The host: one message, then it ends - in this one place, after whatever the message + * asked for. With an exit in each branch, CodeQL read the checks before them as input + * deciding a sensitive action (js/user-controlled-bypass, #111 and #183): process.exit + * is one to it, and the "check" was whether an mpv exists. + */ +async function main() { + await handleMessage(); process.exit(0); } diff --git a/tests/unit/mpvHostVersion.test.mjs b/tests/unit/mpvHostVersion.test.mjs index 3124c0691..37ef730b9 100644 --- a/tests/unit/mpvHostVersion.test.mjs +++ b/tests/unit/mpvHostVersion.test.mjs @@ -32,8 +32,8 @@ const installerFile = path.join(root, 'native-host/install.ps1'); // chrome/background/MpvBackend.mjs by one, then put the new values here (a failure prints // the hashes). const RECORDED = { - version: 7, - host: '7777800746d182f667a6177581c1e6ef48a3e72ae16c9d1b977d8829888bf769', + version: 8, + host: '3c0dcc5a3c2c2a0280e3771475aec468773c0eb4334c720101ec0433a2470945', installer: 'db3b8027b508556b8af66eb4b8188460c2a5a6e1feb32d32b490f2e234b29b3a', };