From 64df90595d6f11ae8ff1bb0f903d080216f1f404 Mon Sep 17 00:00:00 2001 From: BurntToasters <61037367+BurntToasters@users.noreply.github.com> Date: Sat, 13 Jun 2026 22:14:18 -0700 Subject: [PATCH 1/3] Update platform.env.test.ts --- src/tests/platform.env.test.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/tests/platform.env.test.ts b/src/tests/platform.env.test.ts index c6ba81e..90cebc6 100644 --- a/src/tests/platform.env.test.ts +++ b/src/tests/platform.env.test.ts @@ -5,14 +5,15 @@ import * as path from 'path'; const initialResourcesPath = (process as NodeJS.Process & { resourcesPath?: string }).resourcesPath; const initialPlatform = process.platform; const initialArch = process.arch; -const ffmpegBinaryName = process.platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg'; -const ffprobeBinaryName = process.platform === 'win32' ? 'ffprobe.exe' : 'ffprobe'; - -function bundledHelperExists(target: string): boolean { +function bundledHelperExists( + target: string, + platform: NodeJS.Platform = process.platform +): boolean { + const ffmpegName = platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg'; + const ffprobeName = platform === 'win32' ? 'ffprobe.exe' : 'ffprobe'; const normalized = target.replace(/\\/g, '/'); return ( - normalized.endsWith(`/ffmpeg/${ffmpegBinaryName}`) || - normalized.endsWith(`/ffmpeg/${ffprobeBinaryName}`) + normalized.endsWith(`/ffmpeg/${ffmpegName}`) || normalized.endsWith(`/ffmpeg/${ffprobeName}`) ); } @@ -306,7 +307,7 @@ describe('platform env and ffmpeg verification', () => { it('chmods bundled ffmpeg on Linux when not executable', async () => { const { mod, mocks } = await loadPlatform((m) => { - m.existsSyncMock.mockImplementation((target: string) => bundledHelperExists(target)); + m.existsSyncMock.mockImplementation((target: string) => bundledHelperExists(target, 'linux')); m.statSyncMock.mockReturnValue({ isDirectory: () => false, mode: 0o644 }); }, 'linux'); @@ -319,7 +320,7 @@ describe('platform env and ffmpeg verification', () => { it('copies bundled ffmpeg to temp bin when chmod and access fail on Linux', async () => { const { mod, mocks } = await loadPlatform((m) => { - m.existsSyncMock.mockImplementation((target: string) => bundledHelperExists(target)); + m.existsSyncMock.mockImplementation((target: string) => bundledHelperExists(target, 'linux')); m.statSyncMock.mockReturnValue({ isDirectory: () => false, mode: 0o644 }); m.chmodSyncMock.mockImplementationOnce(() => { const err = new Error('readonly') as NodeJS.ErrnoException; @@ -344,7 +345,7 @@ describe('platform env and ffmpeg verification', () => { it('copies bundled ffmpeg to app data bin inside Flatpak', async () => { vi.stubEnv('FLATPAK_ID', 'com.burnttoasters.rosi'); const { mod, mocks } = await loadPlatform((m) => { - m.existsSyncMock.mockImplementation((target: string) => bundledHelperExists(target)); + m.existsSyncMock.mockImplementation((target: string) => bundledHelperExists(target, 'linux')); m.statSyncMock.mockReturnValue({ isDirectory: () => false, mode: 0o644 }); m.chmodSyncMock.mockImplementationOnce(() => { const err = new Error('readonly') as NodeJS.ErrnoException; From 07248e8286b109b258dcfe88d83132f0b4b14c50 Mon Sep 17 00:00:00 2001 From: BurntToasters <61037367+BurntToasters@users.noreply.github.com> Date: Sat, 13 Jun 2026 22:56:21 -0700 Subject: [PATCH 2/3] fix: point in-app browser cookies help to canonical docs URL Use /rosi/en-us/about-browser-cookies instead of the legacy root path. --- src/renderer/rosiEngine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/rosiEngine.ts b/src/renderer/rosiEngine.ts index 0841def..c8e57ab 100644 --- a/src/renderer/rosiEngine.ts +++ b/src/renderer/rosiEngine.ts @@ -2162,7 +2162,7 @@ document.addEventListener('DOMContentLoaded', async () => { } }; - bindExternalLink(browserCookiesHelp, 'https://help.rosie.run/about-browser-cookies'); + bindExternalLink(browserCookiesHelp, 'https://help.rosie.run/rosi/en-us/about-browser-cookies'); bindExternalLink(helpLink, 'https://help.rosie.run/rosi/en-us/faq'); bindExternalLink(supportLink, 'https://rosie.run/support'); bindExternalLink(websiteLink, 'https://rosie.run'); From e487ced206e0a79661dfda0045f1faebde72d6aa Mon Sep 17 00:00:00 2001 From: BurntToasters <61037367+BurntToasters@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:56:40 -0700 Subject: [PATCH 3/3] b2 --- .github/workflows/test-all.yml | 43 ++++++ .gitignore | 3 + CHANGELOG.md | 12 +- README.md | 4 +- ...0\221NOTICES.md" => THIRD-PARTY-NOTICES.md | 95 +++++++++++- assets/YT-DLP-NOTICES.txt | 51 +++++++ assets/ytdlp-checksums.json | 11 ++ build-scripts/check-coverage-thresholds.js | 45 ++++-- build-scripts/check-ytdlp.js | 136 ++++++++++++++++++ build-scripts/dist-tools.js | 2 +- build-scripts/gpg-sign.js | 16 ++- build-scripts/sign-mac-helpers.js | 4 +- build-scripts/update-metainfo.js | 18 +-- build/entitlements.mac.plist | 2 - com.burnttoasters.rosi.metainfo.xml | 4 +- com.burnttoasters.rosi.yml | 8 +- electron-builder.base.yml | 13 ++ electron-builder.github.yml | 5 +- eslint.config.mjs | 20 +++ package-lock.json | 88 +++++++----- package.json | 17 ++- src/main/downloader.ts | 1 + src/main/platform.ts | 2 +- src/renderer/css/01-base.css | 8 +- src/renderer/fonts/OFL.txt | 97 +++++++++++++ src/renderer/index.html | 18 +-- src/renderer/licenses-iframe.html | 14 +- src/renderer/modules/queue.ts | 48 +++++-- src/renderer/modules/ui.ts | 2 + src/renderer/rosiEngine.ts | 60 +++++--- src/renderer/splash-init.js | 8 ++ src/renderer/splash.html | 9 +- src/renderer/theme-init.js | 18 +++ src/tests/rendererModules.test.ts | 26 ++-- src/tests/validation.edge.test.ts | 33 +++++ src/utils/validation.ts | 91 +++++++++--- tsconfig.json | 1 + 37 files changed, 848 insertions(+), 185 deletions(-) rename "THIRD\342\200\221PARTY\342\200\221NOTICES.md" => THIRD-PARTY-NOTICES.md (92%) create mode 100644 assets/YT-DLP-NOTICES.txt create mode 100644 assets/ytdlp-checksums.json create mode 100644 build-scripts/check-ytdlp.js create mode 100644 src/renderer/fonts/OFL.txt create mode 100644 src/renderer/splash-init.js create mode 100644 src/renderer/theme-init.js diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 83aa145..9385414 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -10,6 +10,11 @@ on: permissions: contents: read +# Avoid burning the 3-OS matrix on superseded pushes/PR updates. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: NODE_VERSION: '24' @@ -123,3 +128,41 @@ jobs: - name: Dependency Review uses: actions/dependency-review-action@v5 + + coverage: + name: Coverage thresholds + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run coverage and enforce per-file thresholds + run: npm run test:cov + + audit: + name: npm audit + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Audit dependencies (fails on high/critical) + run: npm audit --audit-level=high diff --git a/.gitignore b/.gitignore index 294e3cb..3d4e095 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,6 @@ resources/ffmpeg/**/ffplay.exe # REPL history .node_repl_history + +# Test scratch dirs +.tmp-*/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c04051..d6ab9bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,9 @@ | Windows | macOS | Linux | | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **EXE:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-Windows-arm64.exe) | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-Linux-x86_64.AppImage) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-Linux-arm64.AppImage) | -|
| **[Universal ZIP](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-MacOS-universal.zip)** | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-Linux-amd64.deb) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-Linux-arm64.deb) | -| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-Linux-x86_64.rpm) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.1/ROSI-Linux-aarch64.rpm) | +| **EXE:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-Windows-arm64.exe) | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-Linux-x86_64.AppImage) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-Linux-arm64.AppImage) | +|
| **[Universal ZIP](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-MacOS-universal.zip)** | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-Linux-amd64.deb) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-Linux-arm64.deb) | +| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-Linux-x86_64.rpm) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.3-beta.2/ROSI-Linux-aarch64.rpm) | > [!IMPORTANT] > The `.sig` files in this repo are NOT normal GPG signatures — they are for ROSI's built-in updater to verify the integrity of updates before downloading and installing. @@ -29,6 +29,12 @@ --- +## Changes in `v4.1.3-beta.2:` + +- **escapeHtml:** Fixed some issues with the html sanitizer. +- **Codebase:** Addressed multiple back-end building issues. +- **Misc:** Many bug fixes and improvements. + ## Changes in `v4.1.3-beta.1:` - **FFMPEG:** Ensure bundled ffprobe is executable and always pass the bundled helper directory to yt-dlp so metadata extraction can find it. diff --git a/README.md b/README.md index ed1d9bc..ee0c118 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ROSI is an Electron GUI for yt-dlp # LICENSES -- Rosi includes the official YT-DLP binary which on its own uses the [unlicense] license, however there are bundled third party packages. Read [THIRD‑PARTY‑NOTICES](THIRD‑PARTY‑NOTICES.md) for more. +- Rosi includes the official YT-DLP binary which on its own uses the [unlicense] license, however there are bundled third party packages. Read [THIRD-PARTY-NOTICES](THIRD-PARTY-NOTICES.md) for more. - Please make sure to also read the [license](LICENSE) for the source of this project (excluding third part binaries and packages). # Requirements @@ -38,7 +38,7 @@ Download ROSI source code from source (main) 1. Download zip of release source code (non-release source code are not recommended as they may contain issues not yet fixed for a release). 2. Unzip the folder folder, place it in a good location on your computer. 3. Install [NodeJS](https://nodejs.org/en/download) and [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) _(Required to build ROSI)_. -4. Run `npm i --save-dev` to download the required electron packages. +4. Run `npm install` to download the required build dependencies (including Electron). 5. View the package.json file to see the `npm run build` commands available. # ROSI LTS Version diff --git "a/THIRD\342\200\221PARTY\342\200\221NOTICES.md" b/THIRD-PARTY-NOTICES.md similarity index 92% rename from "THIRD\342\200\221PARTY\342\200\221NOTICES.md" rename to THIRD-PARTY-NOTICES.md index 30cd738..1fc6d2b 100644 --- "a/THIRD\342\200\221PARTY\342\200\221NOTICES.md" +++ b/THIRD-PARTY-NOTICES.md @@ -558,7 +558,7 @@ Jean-Philippe Aumasson (https://131002.net/siphash/siphash24.c) The file Python/dtoa.c, which supplies C functions dtoa and strtod for conversion of C doubles to and from strings, is derived from the file of the same name by David M. Gay, currently available from https://web.archive.org/web/20220517033456/http://www.netlib.org/fp/dtoa.c. The original file, as retrieved on March 16, 2009, contains the following copyright and licensing notice: -/**************\*\***************\*\*\*\***************\*\*************** +/******\*\*******\*\*******\*\*******\*\*\*\*******\*\*******\*\*******\*\******* - - The author of this software is David M. Gay. @@ -575,7 +575,7 @@ The file Python/dtoa.c, which supplies C functions dtoa and strtod for conversio - WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY - REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY - OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. -- **************\*\***************\*\*\***************\*\***************/ +- ******\*\*******\*\*******\*\*******\*\*\*******\*\*******\*\*******\*\*******/ --- @@ -1032,3 +1032,94 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +# Application framework and runtime + +> This section is maintained by hand. The `licenses.json` produced by +> `npm-license-crawler --production` only covers ROSI's runtime npm +> dependencies and **cannot** capture Electron (a devDependency that is shipped +> at runtime), Chromium, Node.js, or the bundled native binaries. Those are +> enumerated here. + +## Electron + +License: MIT +Copyright (c) Electron contributors +Copyright (c) 2013-present GitHub Inc. +Link: https://github.com/electron/electron/blob/main/LICENSE + +ROSI is built on Electron, which is redistributed in every ROSI build. Electron +bundles Chromium and Node.js (see below). + +``` +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +## Chromium + +License: BSD-3-Clause (plus a large set of bundled third-party components with +their own licenses). +Link: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/LICENSE + +The complete, version-specific Chromium credits/license set for the Electron +release ROSI ships is published with each Electron release as +`LICENSES.chromium.html`. See the Electron release that matches this build at +https://github.com/electron/electron/releases. + +### Chromium bundled FFmpeg (libffmpeg) — LGPL-2.1 + +Electron's Chromium ships an LGPL-2.1 build of FFmpeg (`libffmpeg`), which is +**distinct** from the standalone GPL FFmpeg ROSI bundles for conversion (see the +FFmpeg entry above and `ffmpeg/SOURCE_OFFER.txt`). In compliance with LGPL-2.1, +the corresponding source for Electron's `libffmpeg` is available from the +matching Electron release (Electron publishes FFmpeg source/relink material with +its release artifacts) and from the upstream FFmpeg project at +https://git.ffmpeg.org/ffmpeg.git. License text: +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + +## Node.js + +License: MIT (Node.js core), plus incorporated components under their own +licenses (OpenSSL — Apache-2.0; ICU — Unicode license; libuv — MIT; zlib — zlib +license; and others). +Link: https://github.com/nodejs/node/blob/main/LICENSE + +Node.js is bundled inside Electron and redistributed with ROSI. The full, +version-specific Node.js license (including all incorporated components) is in +the `LICENSE` file of the matching Node.js release at +https://github.com/nodejs/node/releases. + +--- + +# Bundled yt-dlp binary + +ROSI ships the official yt-dlp standalone binaries (`assets/yt-dlp*`). yt-dlp +itself is released into the public domain under The Unlicense, but the standalone +binary statically bundles additional components — including GPL/LGPL-licensed +code (e.g. mutagen, GPL-2.0) and a Python interpreter — whose notices are listed +in the yt-dlp / Python sections above. + +A consolidated notice and a written source offer for the GPL/LGPL components +bundled inside the yt-dlp binary are shipped alongside the binary at +`assets/YT-DLP-NOTICES.txt`. The complete corresponding source is available from +the yt-dlp project (https://github.com/yt-dlp/yt-dlp) and the respective upstream +component projects. diff --git a/assets/YT-DLP-NOTICES.txt b/assets/YT-DLP-NOTICES.txt new file mode 100644 index 0000000..b19c705 --- /dev/null +++ b/assets/YT-DLP-NOTICES.txt @@ -0,0 +1,51 @@ +yt-dlp Binary Distribution Notice +================================= + +This application (ROSI) bundles and redistributes the official yt-dlp +standalone binary. + +yt-dlp itself +------------- +License: The Unlicense (public domain dedication) +Project: https://github.com/yt-dlp/yt-dlp +License text: https://github.com/yt-dlp/yt-dlp/blob/master/LICENSE + +Bundled components inside the standalone binary +----------------------------------------------- +The yt-dlp standalone binary is produced with PyInstaller and statically +bundles a Python interpreter plus several third-party components. Some of these +are licensed under the GPL/LGPL and therefore carry a source-availability +obligation: + + Component License Source + --------------- ---------------------- -------------------------------------- + yt-dlp The Unlicense https://github.com/yt-dlp/yt-dlp + mutagen GPL-2.0-or-later https://github.com/quodlibet/mutagen + AtomicParsley GPL-2.0-or-later https://github.com/wez/atomicparsley + pycryptodome Public domain + BSD-2 https://github.com/Legrandin/pycryptodome + websockets BSD-3-Clause https://github.com/python-websockets/websockets + certifi MPL-2.0 https://github.com/certifi/python-certifi + brotli/brotlicffi MIT https://github.com/google/brotli + Python PSF License v2 https://github.com/python/cpython + +The authoritative, version-specific list of bundled components and their +licenses is published by the yt-dlp project at: + https://github.com/yt-dlp/yt-dlp/blob/master/THIRD_PARTY_LICENSES.txt +(When updating the bundled yt-dlp binary, drop the matching upstream +THIRD_PARTY_LICENSES.txt next to this file.) + +Written Source Offer (GPLv2 Section 3(b)) +----------------------------------------- +For the GPL-licensed components statically bundled in the yt-dlp binary +distributed with ROSI (e.g. mutagen, AtomicParsley), BurntToasters offers to +provide, for a charge no more than the cost of physically performing +distribution, the complete corresponding source code. This offer is valid for at +least three years from the date you received the binaries. + +The corresponding source is also freely available from the upstream projects +listed above. To request source on physical media, contact: + BurntToasters + +ROSI invokes yt-dlp as an external process and is not a derivative work of +yt-dlp or any of its bundled components. ROSI is licensed under the Mozilla +Public License 2.0 (MPL-2.0). diff --git a/assets/ytdlp-checksums.json b/assets/ytdlp-checksums.json new file mode 100644 index 0000000..67d7e0f --- /dev/null +++ b/assets/ytdlp-checksums.json @@ -0,0 +1,11 @@ +{ + "_comment": "SHA-256 of the committed yt-dlp binaries. Regenerate ONLY after verifying the binaries against yt-dlp upstream SHA2-256SUMS. See build-scripts/check-ytdlp.js.", + "generatedAt": "2026-06-17T21:25:31.250Z", + "binaries": { + "yt-dlp.exe": "3a48cb955d55c8821b60ccbdbbc6f61bc958f2f3d3b7ad5eaf3d83a543293a27", + "yt-dlp_arm64.exe": "847583f91bb6d26479c1dc9643c2f4b8857a90b40d619da97b0cfabccb9138d0", + "yt-dlp_macos": "b82c3626952e6c14eaf654cc565866775ffd0b9ffb7021628ac59b42c2f4f244", + "yt-dlp_linux": "bf8aac79b72287a6d2043074415132558b43743a8f9461a22b0141e90f16ce66", + "yt-dlp_linux_aarch64": "cabd246445bdfde0eda0dfe68bbe90354be83f3fdbbf077df11a2ea55f41cdbd" + } +} diff --git a/build-scripts/check-coverage-thresholds.js b/build-scripts/check-coverage-thresholds.js index 9e2cf62..70b0e48 100644 --- a/build-scripts/check-coverage-thresholds.js +++ b/build-scripts/check-coverage-thresholds.js @@ -10,14 +10,37 @@ if (!fs.existsSync(summaryPath)) { const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8')); +// Per-file floors for the security-critical / non-trivial main-process and +// shared modules. Each is set a few points below the currently-measured +// coverage so the gate catches regressions without being flaky. All four +// metrics (lines/statements/branches/functions) are enforced. +// +// NOTE: renderer modules (rosiEngine.ts, modules/*.ts) are intentionally absent. +// They are exercised via on-the-fly transpile+eval in the test suite, which v8 +// coverage cannot attribute to the source files, so they report 0% here. Add +// floors for them only once the renderer tests import the compiled artifact. const thresholds = { - 'src/main/main.ts': { lines: 12, statements: 12 }, - 'src/main/preload.ts': { lines: 80, statements: 80 }, - 'src/main/processKill.ts': { lines: 80, statements: 80 }, - 'src/utils/validation.ts': { lines: 85, statements: 85 }, - 'src/utils/downloadLifecycle.ts': { lines: 90, statements: 90 }, + 'src/main/main.ts': { lines: 80, statements: 80, branches: 70, functions: 80 }, + 'src/main/downloader.ts': { lines: 88, statements: 88, branches: 72, functions: 88 }, + 'src/main/platform.ts': { lines: 68, statements: 68, branches: 58, functions: 58 }, + 'src/main/settings.ts': { lines: 88, statements: 88, branches: 82, functions: 90 }, + 'src/main/updater.ts': { lines: 92, statements: 92, branches: 85, functions: 92 }, + 'src/main/download/commandBuilders.ts': { + lines: 88, + statements: 88, + branches: 86, + functions: 90, + }, + 'src/main/download/videoInfo.ts': { lines: 70, statements: 70, branches: 60, functions: 70 }, + 'src/main/preload.ts': { lines: 90, statements: 90, branches: 90, functions: 90 }, + 'src/main/processKill.ts': { lines: 90, statements: 90, branches: 85, functions: 55 }, + 'src/utils/ipcValidation.ts': { lines: 85, statements: 85, branches: 85, functions: 88 }, + 'src/utils/validation.ts': { lines: 85, statements: 82, branches: 72, functions: 90 }, + 'src/utils/downloadLifecycle.ts': { lines: 95, statements: 95, branches: 95, functions: 95 }, }; +const METRICS = ['lines', 'statements', 'branches', 'functions']; + function findCoverageEntry(suffix) { const normalizedSuffix = suffix.replace(/\\/g, '/'); return Object.entries(summary).find(([key]) => @@ -35,11 +58,13 @@ for (const [file, threshold] of Object.entries(thresholds)) { } const [, metrics] = match; - if (metrics.lines.pct < threshold.lines) { - failures.push(`${file}: lines ${metrics.lines.pct}% < ${threshold.lines}%`); - } - if (metrics.statements.pct < threshold.statements) { - failures.push(`${file}: statements ${metrics.statements.pct}% < ${threshold.statements}%`); + for (const metric of METRICS) { + if (typeof threshold[metric] !== 'number') continue; + const actual = + metrics[metric] && typeof metrics[metric].pct === 'number' ? metrics[metric].pct : 0; + if (actual < threshold[metric]) { + failures.push(`${file}: ${metric} ${actual}% < ${threshold[metric]}%`); + } } } diff --git a/build-scripts/check-ytdlp.js b/build-scripts/check-ytdlp.js new file mode 100644 index 0000000..a238a81 --- /dev/null +++ b/build-scripts/check-ytdlp.js @@ -0,0 +1,136 @@ +'use strict'; + +/** + * yt-dlp binary integrity gate. + * + * The yt-dlp binaries are committed under assets/ and shipped verbatim inside + * the signed installers. This script verifies each committed binary against a + * checksum manifest (assets/ytdlp-checksums.json) so a corrupted, truncated, or + * tampered binary cannot be packaged unnoticed. + * + * Usage: + * node build-scripts/check-ytdlp.js verify (default; fails on mismatch/missing manifest) + * node build-scripts/check-ytdlp.js --generate (re)write the manifest from the current binaries + * + * NOTE ON PROVENANCE: --generate records the hashes of whatever binaries are + * currently on disk (a self-attested baseline that detects later drift). It does + * NOT prove the binaries match an upstream yt-dlp release. When updating yt-dlp, + * download the official binaries, verify them against yt-dlp's published + * SHA2-256SUMS (and its GPG signature), then run --generate to record the new + * baseline. + */ + +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const assetsDir = path.join(__dirname, '..', 'assets'); +const manifestPath = path.join(assetsDir, 'ytdlp-checksums.json'); + +// All per-platform yt-dlp binaries ROSI ships (mirrors getYtdlpBinaryName()). +const BINARY_NAMES = [ + 'yt-dlp.exe', + 'yt-dlp_arm64.exe', + 'yt-dlp_macos', + 'yt-dlp_linux', + 'yt-dlp_linux_aarch64', +]; + +function sha256(filePath) { + const hash = crypto.createHash('sha256'); + hash.update(fs.readFileSync(filePath)); + return hash.digest('hex'); +} + +function presentBinaries() { + return BINARY_NAMES.filter((name) => fs.existsSync(path.join(assetsDir, name))); +} + +function generate() { + const present = presentBinaries(); + if (present.length === 0) { + console.error('✗ No yt-dlp binaries found in assets/; nothing to record.'); + process.exit(1); + } + const binaries = {}; + for (const name of present) { + binaries[name] = sha256(path.join(assetsDir, name)); + } + const manifest = { + _comment: + 'SHA-256 of the committed yt-dlp binaries. Regenerate ONLY after verifying ' + + 'the binaries against yt-dlp upstream SHA2-256SUMS. See build-scripts/check-ytdlp.js.', + generatedAt: new Date().toISOString(), + binaries, + }; + fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8'); + console.log(`✓ Wrote ${path.basename(manifestPath)} for ${present.length} binaries.`); +} + +function verify() { + if (!fs.existsSync(manifestPath)) { + console.error(`✗ yt-dlp checksum manifest not found: ${manifestPath}`); + console.error(' Generate it with: node build-scripts/check-ytdlp.js --generate'); + process.exit(1); + } + + let manifest; + try { + manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + } catch (error) { + console.error(`✗ Could not parse ${manifestPath}: ${error.message}`); + process.exit(1); + } + + const expected = (manifest && manifest.binaries) || {}; + const errors = []; + const present = presentBinaries(); + + if (present.length === 0) { + console.error('✗ No yt-dlp binaries found in assets/.'); + process.exit(1); + } + + for (const name of present) { + const expectedHash = expected[name]; + if (!expectedHash) { + errors.push(`${name}: present on disk but missing from the manifest`); + continue; + } + const actual = sha256(path.join(assetsDir, name)); + if (actual !== expectedHash) { + errors.push( + `${name}: SHA-256 mismatch\n expected: ${expectedHash}\n actual: ${actual}` + ); + } + } + + // A manifest entry without a binary on disk is only an error when that binary + // is required for the current build; here we just warn so single-arch checkouts + // (where other-arch binaries were temporarily removed) do not fail. + for (const name of Object.keys(expected)) { + if (!present.includes(name)) { + console.warn(` (note) ${name} is in the manifest but not present on disk.`); + } + } + + if (errors.length > 0) { + console.error('\n✗ yt-dlp integrity check failed:'); + for (const item of errors) { + console.error(`- ${item}`); + } + console.error( + '\nIf you intentionally updated yt-dlp, verify the new binaries against upstream\n' + + 'SHA2-256SUMS, then run: node build-scripts/check-ytdlp.js --generate' + ); + process.exit(1); + } + + console.log(`✓ yt-dlp integrity verified (${present.length} binaries).`); +} + +if (process.argv.includes('--generate')) { + generate(); +} else { + verify(); +} diff --git a/build-scripts/dist-tools.js b/build-scripts/dist-tools.js index 7d70fa7..2e4c6ae 100644 --- a/build-scripts/dist-tools.js +++ b/build-scripts/dist-tools.js @@ -48,7 +48,7 @@ function cleanReleaseArtifacts() { } function cleanRendererModuleArtifacts() { - let entries = []; + let entries; try { entries = fs.readdirSync(RENDERER_MODULES_DIR, { withFileTypes: true }); } catch (error) { diff --git a/build-scripts/gpg-sign.js b/build-scripts/gpg-sign.js index 42bf6f4..be368a1 100644 --- a/build-scripts/gpg-sign.js +++ b/build-scripts/gpg-sign.js @@ -117,17 +117,25 @@ function signFile(filePath) { gpgArgs.push('--local-user', GPG_KEY_ID); } + // Pass the passphrase over stdin (--passphrase-fd 0) instead of argv so it + // never appears in the process table or in any error.message (which, for + // execFileSync, embeds the full argv). + const execOptions = { stdio: ['pipe', 'pipe', 'pipe'] }; if (GPG_PASSPHRASE) { - gpgArgs.push('--pinentry-mode', 'loopback', '--passphrase', GPG_PASSPHRASE); + gpgArgs.push('--pinentry-mode', 'loopback', '--passphrase-fd', '0'); + execOptions.input = GPG_PASSPHRASE + '\n'; } gpgArgs.push('--output', ascFile, filePath); - execFileSync('gpg', gpgArgs, { stdio: 'pipe' }); + execFileSync('gpg', gpgArgs, execOptions); console.log(' ✓ Created ' + path.basename(ascFile)); return ascFile; } catch (error) { - console.error(' ✗ FAILED: ' + fileName + ':', error.message); + // Do not log error.message: it can contain the full gpg argv. Surface only + // the exit status so a secret on the command line can never reach the log. + const status = typeof error.status === 'number' ? ` (exit code ${error.status})` : ''; + console.error(' ✗ FAILED to sign ' + fileName + status); return null; } } @@ -355,7 +363,7 @@ async function getOrCreateRelease() { ); if (!Array.isArray(releases)) { - throw new Error('Unexpected releases payload type'); + throw new Error('Unexpected releases payload type', { cause: error }); } const matchingReleases = releases.filter(function (r) { diff --git a/build-scripts/sign-mac-helpers.js b/build-scripts/sign-mac-helpers.js index b06d33f..9b602cb 100644 --- a/build-scripts/sign-mac-helpers.js +++ b/build-scripts/sign-mac-helpers.js @@ -5,7 +5,9 @@ const path = require('path'); const { execFileSync } = require('child_process'); /** - * Re-sign bundled helper binaries after electron-builder signs the app. + * Sign bundled helper binaries during the afterPack hook, BEFORE electron-builder + * signs and seals the .app bundle. Signing nested Mach-O after the bundle is + * sealed would invalidate the parent signature / notarization. * PyInstaller sidecars (yt-dlp) extract a Python runtime at launch; without * disable-library-validation they fail with Team ID mismatches on macOS. */ diff --git a/build-scripts/update-metainfo.js b/build-scripts/update-metainfo.js index e778c90..2cd1268 100644 --- a/build-scripts/update-metainfo.js +++ b/build-scripts/update-metainfo.js @@ -57,7 +57,6 @@ if (!releasesSectionMatch) { } const releaseSelfClosingRegex = /]*\/>/; -const releasePairedRegex = /]*>[\s\S]*?<\/release>/; const currentReleaseMatch = releasesSectionMatch[0].match(releaseSelfClosingRegex) || @@ -77,17 +76,12 @@ if (currentReleaseMatch) { } } -let updatedSection = releasesSectionMatch[0]; -if (releaseSelfClosingRegex.test(updatedSection)) { - updatedSection = updatedSection.replace(releaseSelfClosingRegex, newReleaseTag); -} else if (releasePairedRegex.test(updatedSection)) { - updatedSection = updatedSection.replace(releasePairedRegex, newReleaseTag); -} else { - updatedSection = updatedSection.replace( - /\s*/, - `\n${newReleaseTag}\n${baseIndent}` - ); -} +// Prepend the new release so version history is preserved (AppStream / Flathub +// expect a newest-first history rather than a single repeatedly-replaced entry). +const updatedSection = releasesSectionMatch[0].replace( + /[^\S\r\n]*\r?\n?\s*/, + `\n${releaseIndent}${newReleaseTag.trim()}\n` +); if (updatedSection === releasesSectionMatch[0]) { console.log('✓ AppStream metadata already up to date'); diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist index 8999547..97cafe4 100644 --- a/build/entitlements.mac.plist +++ b/build/entitlements.mac.plist @@ -4,8 +4,6 @@ com.apple.security.cs.allow-jit - com.apple.security.cs.allow-unsigned-executable-memory - com.apple.security.app-sandbox diff --git a/com.burnttoasters.rosi.metainfo.xml b/com.burnttoasters.rosi.metainfo.xml index ad5187e..8faa0a1 100644 --- a/com.burnttoasters.rosi.metainfo.xml +++ b/com.burnttoasters.rosi.metainfo.xml @@ -24,6 +24,7 @@ https://github.com/BurntToasters/ROSI https://github.com/BurntToasters/ROSI/issues + https://github.com/BurntToasters/ROSI BurntToasters com.burnttoasters.rosi.desktop @@ -33,7 +34,8 @@ - + + diff --git a/com.burnttoasters.rosi.yml b/com.burnttoasters.rosi.yml index 90d3a7f..f53e1e1 100644 --- a/com.burnttoasters.rosi.yml +++ b/com.burnttoasters.rosi.yml @@ -10,17 +10,17 @@ command: rosi separate-locales: false finish-args: - # X11 + XShm access + # X11 + XShm access (only used as a fallback when Wayland is unavailable) - --share=ipc - - --socket=x11 + - --socket=fallback-x11 # Wayland access - --socket=wayland # Network access (for downloading videos & updates) - --share=network # GPU acceleration (for hardware-accelerated encoding) - --device=dri - # File system access for saving downloads - - --filesystem=home + # File system access for saving downloads (least privilege; arbitrary + # user-chosen locations go through the file-chooser portal, not --filesystem=home) - --filesystem=xdg-videos - --filesystem=xdg-download # Notifications diff --git a/electron-builder.base.yml b/electron-builder.base.yml index fb673f7..486aecf 100644 --- a/electron-builder.base.yml +++ b/electron-builder.base.yml @@ -1,6 +1,15 @@ appId: com.burnttoasters.rosi productName: Rosi asar: true +# Harden the packaged Electron binary. electron-builder flips these via +# @electron/fuses during packaging and re-signs the binary afterwards. +electronFuses: + runAsNode: false + enableCookieEncryption: true + enableNodeOptionsEnvironmentVariable: false + enableNodeCliInspectArguments: false + enableEmbeddedAsarIntegrityValidation: true + onlyLoadAppFromAsar: true files: - 'dist/**/*' - 'src/renderer/**/*' @@ -23,6 +32,7 @@ win: to: assets/ filter: - 'yt-dlp*.exe' + - 'YT-DLP-NOTICES.txt' - from: resources/ffmpeg/win/${arch} to: ffmpeg filter: @@ -48,6 +58,8 @@ mac: extraResources: - from: assets/yt-dlp_macos to: assets/yt-dlp_macos + - from: assets/YT-DLP-NOTICES.txt + to: assets/YT-DLP-NOTICES.txt - from: resources/ffmpeg/mac/${arch} to: ffmpeg filter: @@ -72,6 +84,7 @@ linux: to: assets/ filter: - 'yt-dlp_linux*' + - 'YT-DLP-NOTICES.txt' - from: resources/ffmpeg/linux/${arch} to: ffmpeg filter: diff --git a/electron-builder.github.yml b/electron-builder.github.yml index a8d33c2..035b8c3 100644 --- a/electron-builder.github.yml +++ b/electron-builder.github.yml @@ -12,4 +12,7 @@ mac: entitlements: build/entitlements.mac.plist entitlementsInherit: build/entitlements.mac.plist notarize: true -afterSign: build-scripts/sign-mac-helpers.js +# Sign the bundled helper binaries (yt-dlp, ffmpeg, ffprobe) during afterPack — +# BEFORE electron-builder signs/seals the .app — so the parent signature stays +# valid and notarization is not invalidated by re-signing nested code. +afterPack: build-scripts/sign-mac-helpers.js diff --git a/eslint.config.mjs b/eslint.config.mjs index 192e432..1feb0de 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,6 @@ import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; +import nounsanitized from 'eslint-plugin-no-unsanitized'; export default tseslint.config( eslint.configs.recommended, @@ -45,6 +46,25 @@ export default tseslint.config( 'no-empty': ['error', { allowEmptyCatch: true }], '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }], + + // Type-aware unsafe-data-flow rules. Kept as warnings so they surface + // risky `any` flows without breaking the build; promote to 'error' as the + // remaining warnings are burned down. + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + }, + }, + { + // Flag unsanitized DOM sinks (innerHTML/insertAdjacentHTML/etc.) in the + // renderer, where untrusted yt-dlp/queue data is rendered. + files: ['src/renderer/**/*.ts'], + plugins: { 'no-unsanitized': nounsanitized }, + rules: { + 'no-unsanitized/method': 'error', + 'no-unsanitized/property': 'error', }, }, { diff --git a/package-lock.json b/package-lock.json index 3591efd..89ab41b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rosi", - "version": "4.1.3-beta.1", + "version": "4.1.3-beta.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rosi", - "version": "4.1.3-beta.1", + "version": "4.1.3-beta.2", "license": "MPL-2.0", "dependencies": { "electron-log": "^5.3.4", @@ -22,6 +22,7 @@ "electron": "^42.0.0", "electron-builder": "^26.7.0", "eslint": "^10.3.0", + "eslint-plugin-no-unsanitized": "^4.1.2", "husky": "^9.1.7", "js-yaml": "^4.1.0", "jsdom": "^29.1.1", @@ -32,8 +33,8 @@ "vitest": "^4.0.16" }, "engines": { - "node": ">=24.x", - "npm": ">=10.x" + "node": ">=24", + "npm": ">=11" } }, "node_modules/@asamuzakjp/css-color": { @@ -205,9 +206,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", - "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.4.tgz", + "integrity": "sha512-yI8kNhHiOrLb8Rlulsk07DeQz0PwyT69FX9dkz5rAp7p9RUwFKEXnZpBGzURiLHgi66YqIWxOHn1nij8Lrg27Q==", "dev": true, "funding": [ { @@ -301,9 +302,9 @@ } }, "node_modules/@electron-internal/extract-zip": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.2.tgz", - "integrity": "sha512-VJuNETNPEhrmQEZezeTZO5TZMV+dobBRyJ7zHjGJWIhMS7m7W1UeClt69u4hkUxv9ZZVxuli/E9Yvc4gDNHGsg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.3.tgz", + "integrity": "sha512-OjKpjB7gohtEjZiq6nDx1egqjZJhGPN1iFOIED+NFhB/MMkXw/XRcHjh1DGXKT5z2W9eW7Jy2UKU3gpjvusFTQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -996,14 +997,14 @@ } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.7.0.tgz", - "integrity": "sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", + "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/utils": "^2.0.2", - "asn1js": "^3.0.6", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, @@ -1470,9 +1471,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", - "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1884,9 +1885,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -2228,9 +2229,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz", - "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz", + "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==", "dev": true, "license": "MIT", "dependencies": { @@ -3244,9 +3245,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "24.13.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", - "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==", + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", "dev": true, "license": "MIT", "dependencies": { @@ -3411,11 +3412,14 @@ } }, "node_modules/eslint": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", - "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -3466,6 +3470,16 @@ } } }, + "node_modules/eslint-plugin-no-unsanitized": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.5.tgz", + "integrity": "sha512-MSB4hXPVFQrI8weqzs6gzl7reP2k/qSjtCoL2vUMSDejIIq9YL1ZKvq5/ORBXab/PvfBBrWO2jWviYpL+4Ghfg==", + "dev": true, + "license": "MPL-2.0", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, "node_modules/eslint-scope": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", @@ -3768,17 +3782,17 @@ "license": "ISC" }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -5395,9 +5409,9 @@ } }, "node_modules/obug": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz", - "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", diff --git a/package.json b/package.json index 6ce0448..8f19e24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rosi", - "version": "4.1.3-beta.1", + "version": "4.1.3-beta.2", "private": true, "description": "Electron GUI for yt-dlp", "desktopName": "com.burnttoasters.rosi.desktop", @@ -13,8 +13,8 @@ "main": "dist/main/main.js", "packageManager": "npm@11.10.1", "engines": { - "node": ">=24.x", - "npm": ">=10.x" + "node": ">=24", + "npm": ">=11" }, "scripts": { "gitprune": "node build-scripts/git-prune-local-branches.js", @@ -24,6 +24,8 @@ "hooks:install": "node build-scripts/install-hooks.js", "clean": "node build-scripts/dist-tools.js clean && node build-scripts/dist-tools.js clean-release", "licenses": "npx npm-license-crawler --production --json licenses.json", + "ytdlp:check": "node build-scripts/check-ytdlp.js", + "ytdlp:check:generate": "node build-scripts/check-ytdlp.js --generate", "compile:main": "tsc --project tsconfig.main.json", "compile:renderer": "tsc --project tsconfig.renderer.json", "compile": "node build-scripts/dist-tools.js clean && npm run compile:main && npm run compile:renderer && node build-scripts/dist-tools.js copy", @@ -38,7 +40,7 @@ "ffmpeg:check:linux:x64": "npm run ffmpeg:check -- --target linux:x64", "ffmpeg:check:linux:arm64": "npm run ffmpeg:check -- --target linux:arm64", "prerelease:prepare": "node build-scripts/release-warning.js", - "prebuild:base": "npm run compile && npm run licenses", + "prebuild:base": "npm run compile && npm run licenses && npm run ytdlp:check", "prebuild": "npm run prebuild:base && npm run ffmpeg:check:current", "prebuild:win": "npm run prebuild:base && npm run ffmpeg:check:win", "prebuild:win:x64": "npm run prebuild:base && npm run ffmpeg:check:win:x64", @@ -61,8 +63,8 @@ "test:watch": "vitest", "test:all": "node build-scripts/test-all.js", "test:cov": "vitest run --coverage && node build-scripts/check-coverage-thresholds.js", - "lint": "eslint src/", - "lint:fix": "eslint src/ --fix", + "lint": "eslint src/ build-scripts/", + "lint:fix": "eslint src/ build-scripts/ --fix", "format": "prettier --write src/", "format:check": "prettier --check src/", "start": "npm run clean && npm run compile && electron .", @@ -119,7 +121,7 @@ "setup:deb": "sudo apt update && sudo apt install -y build-essential libgtk-3-dev libnotify-dev libnss3-dev libxss-dev libxtst-dev libatspi2.0-dev uuid-dev libsecret-1-dev libx11-dev rpm flatpak flatpak-builder && sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo" }, "overrides": { - "tar": "^7.5.3" + "tar": "^7.5.8" }, "repository": { "type": "git", @@ -140,6 +142,7 @@ "electron": "^42.0.0", "electron-builder": "^26.7.0", "eslint": "^10.3.0", + "eslint-plugin-no-unsanitized": "^4.1.2", "husky": "^9.1.7", "js-yaml": "^4.1.0", "jsdom": "^29.1.1", diff --git a/src/main/downloader.ts b/src/main/downloader.ts index 8dee011..1bfe36e 100644 --- a/src/main/downloader.ts +++ b/src/main/downloader.ts @@ -642,6 +642,7 @@ export function startDownload( : resolvedDownloadDir; const relativePath = path.relative(compareDownloadDir, compareFilePath); if ( + path.isAbsolute(relativePath) || relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || relativePath.startsWith('../') diff --git a/src/main/platform.ts b/src/main/platform.ts index 931bc37..96c36ad 100644 --- a/src/main/platform.ts +++ b/src/main/platform.ts @@ -393,7 +393,7 @@ function probeYtdlpBinary(ytdlpPath: string): Promise<{ ok: boolean; detail: str let stderr = ''; let stdout = ''; const proc = spawn(ytdlpPath, ['--version'], { - env: { ...process.env, PATH: buildEnhancedPath() }, + env: { ...buildSafeEnv(), PATH: buildEnhancedPath() }, shell: false, }); diff --git a/src/renderer/css/01-base.css b/src/renderer/css/01-base.css index 97da7f4..6771d59 100644 --- a/src/renderer/css/01-base.css +++ b/src/renderer/css/01-base.css @@ -45,7 +45,7 @@ --font-sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-mono: 'IBM Plex Mono', 'SF Mono', 'Fira Code', 'Consolas', monospace; - --text-xs: 0.6rem; + --text-xs: 0.72rem; --text-sm: 0.75rem; --text-base: 0.92rem; --text-md: 1rem; @@ -72,7 +72,7 @@ --text-primary: #ffffff; --text-secondary: rgba(255, 255, 255, 0.8); --text-tertiary: rgba(255, 255, 255, 0.7); - --text-muted: rgba(255, 255, 255, 0.45); + --text-muted: rgba(255, 255, 255, 0.62); --accent: #22d3ee; --accent-light: #67e8f9; @@ -194,7 +194,7 @@ --text-primary: #ffffff; --text-secondary: rgba(255, 255, 255, 0.78); --text-tertiary: rgba(255, 255, 255, 0.7); - --text-muted: rgba(255, 255, 255, 0.45); + --text-muted: rgba(255, 255, 255, 0.62); --accent: #8b5cf6; --accent-light: #a78bfa; --accent-dark: #7c3aed; @@ -237,7 +237,7 @@ --text-primary: #0f172a; --text-secondary: rgba(15, 23, 42, 0.82); --text-tertiary: rgba(15, 23, 42, 0.7); - --text-muted: rgba(15, 23, 42, 0.52); + --text-muted: rgba(15, 23, 42, 0.66); --accent: #2563eb; --accent-light: #3b82f6; --accent-dark: #1d4ed8; diff --git a/src/renderer/fonts/OFL.txt b/src/renderer/fonts/OFL.txt new file mode 100644 index 0000000..1e479dc --- /dev/null +++ b/src/renderer/fonts/OFL.txt @@ -0,0 +1,97 @@ +ROSI bundles the following fonts, both licensed under the SIL Open Font +License, Version 1.1: + + Manrope + Copyright 2018 The Manrope Project Authors + (https://github.com/sharanda/manrope) + + IBM Plex Mono + Copyright (c) 2017 IBM Corp. with Reserved Font Name "Plex" + (https://github.com/IBM/plex) + +The full license text follows. + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to any +document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may include +source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or +other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or in +the appropriate machine-readable metadata fields within text or binary +files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any Modified +Version, except to acknowledge the contribution(s) of the Copyright +Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be +distributed entirely under this license, and must not be distributed under +any other license. The requirement for fonts to remain under this license +does not apply to any document created using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are not +met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER +DEALINGS IN THE FONT SOFTWARE. diff --git a/src/renderer/index.html b/src/renderer/index.html index 5264d02..d3e128a 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -7,23 +7,7 @@ content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: https:; connect-src 'self'; frame-src 'self'; object-src 'none'; base-uri 'self';" /> ROSI - + diff --git a/src/renderer/licenses-iframe.html b/src/renderer/licenses-iframe.html index 5f5a1e6..7747c0e 100644 --- a/src/renderer/licenses-iframe.html +++ b/src/renderer/licenses-iframe.html @@ -4,7 +4,7 @@ ROSI License & 3rd Party Licenses/Credits