From b9ff99f3acf7a8e08828438e8657e004358f8886 Mon Sep 17 00:00:00 2001 From: Julius Olsson Date: Sat, 12 Sep 2026 22:06:16 -0700 Subject: [PATCH 1/3] fix(release): update vulnerable MCP SDK transitive dependencies npm audit --omit=dev --audit-level=high, the release workflow's audit gate, failed on main with two high advisories in dependencies of @modelcontextprotocol/sdk that ship in the packaged app: fast-uri 3.1.4 (via ajv) and ip-address 10.2.0 (via express-rate-limit). That stopped release.yml before any packaging ran. Only the lockfile changes, and only the version, resolved and integrity of five packages, all within the ranges their dependents already declare: fast-uri 3.1.7, ip-address 10.7.0, hono 4.13.7, @hono/node-server 1.19.17 and qs 6.16.0. The last three clear moderate advisories on the same dependency path. A plain npm update also pruned 27 unrelated nested vitest esbuild entries, so the edit is applied surgically to keep test tooling exactly as main has it. Fixes #965 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01MpFfu5xMd77Y2sNUeytvCG --- package-lock.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6029f68d..4d2cc70af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1301,9 +1301,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "version": "1.19.17", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz", + "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -6163,9 +6163,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "funding": [ { "type": "github", @@ -6699,9 +6699,9 @@ } }, "node_modules/hono": { - "version": "4.12.31", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.31.tgz", - "integrity": "sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==", + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", + "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -6861,9 +6861,9 @@ "license": "MIT" }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", + "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", "license": "MIT", "engines": { "node": ">= 12" @@ -9390,9 +9390,9 @@ } }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", From a82f59a02a29bf63d999d097cb1449a449c9d3b8 Mon Sep 17 00:00:00 2001 From: Julius Olsson Date: Sat, 12 Sep 2026 22:18:35 -0700 Subject: [PATCH 2/3] fix(release): treat empty signing secrets as unset when packaging GitHub Actions passes a missing secret as an empty string, and electron-builder treats CSC_LINK="" as a certificate path relative to the project root, so the unsigned fallback died with " not a file" in release run 34739476044 once the audit gate passed. The wrapper already chose the unsigned branch; it now deletes empty CSC_LINK, CSC_NAME and CSC_KEY_PASSWORD before anything reads them. Refs #965 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01MpFfu5xMd77Y2sNUeytvCG --- scripts/package-mac.mjs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/package-mac.mjs b/scripts/package-mac.mjs index b7d3d0972..bbc37a0f1 100644 --- a/scripts/package-mac.mjs +++ b/scripts/package-mac.mjs @@ -34,6 +34,22 @@ import { spawnSync } from 'node:child_process' const env = { ...process.env } + +// WHY empty signing vars are deleted, not just treated as falsy: GitHub +// Actions materialises `${{ secrets.CSC_LINK }}` as an EMPTY STRING when the +// secret does not exist, and release.yml passes every signing secret that way. +// electron-builder deliberately treats "" as a set value +// (platformPackager.getCscLink: chooseNotNull + "allow to specify as empty +// string"), so an empty CSC_LINK is resolved as a certificate PATH relative to +// the project root and packaging dies with " not a file" (#965, release +// run 34739476044). The `!env.CSC_LINK` test below already chose the unsigned +// branch correctly; the empty variable simply survived into the child env. +// Normalising first makes "secret missing" and "variable unset" identical for +// everything downstream, including the notarization vars. +for (const name of ['CSC_LINK', 'CSC_NAME', 'CSC_KEY_PASSWORD']) { + if (env[name] !== undefined && env[name].trim() === '') delete env[name] +} + if (!env.CSC_LINK && !env.CSC_NAME) { env.CSC_IDENTITY_AUTO_DISCOVERY = 'false' delete env.CSC_KEY_PASSWORD From 980865b624ce18bbeaccaf03745d2593cfc667ea Mon Sep 17 00:00:00 2001 From: Julius Olsson Date: Sat, 12 Sep 2026 22:35:42 -0700 Subject: [PATCH 3/3] fix(release): keep executable bits across the build/package job handoff upload-artifact zips its input without file modes, so the out/ tree build-app hands to package-macos came back with every file 644. The packaged app then carried a non-executable dictation hotkey helper (and would have carried non-executable tmux, cloudflared and mitmproxy binaries), which verify-packaged-mac rejected in release run 34739982565. build-app now uploads out/ as a tar, which records modes and symlinks, and package-macos unpacks it before packaging. Refs #965 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01MpFfu5xMd77Y2sNUeytvCG --- .github/workflows/release.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49afc5405..a7dae52cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -161,11 +161,24 @@ jobs: - name: Build app run: npm run build + - name: Archive app build + # WHY a tar instead of uploading `out/` directly: upload-artifact zips its + # input without file modes, so every file comes back 644 (its README, + # "Permission Loss"). `out/` carries executables that must stay + # executable inside the packaged app: the universal dictation hotkey + # helper from scripts/build-hotkey-helper.mjs and the bundled tmux, + # cloudflared and mitmproxy binaries under out/main/runtime. The + # package-macos job packages the downloaded tree verbatim, so release run + # 34739982565 shipped a non-executable helper and + # scripts/verify-packaged-mac.mjs rejected it (#965). tar records modes + # and symlinks, so the zip wrapped around the tar no longer matters. + run: tar -cf app-build.tar out + - name: Upload app build uses: actions/upload-artifact@v7 with: name: app-build - path: out + path: app-build.tar if-no-files-found: error package-macos: @@ -255,7 +268,11 @@ jobs: uses: actions/download-artifact@v8 with: name: app-build - path: out + + - name: Unpack app build + # Restores `out/` with the file modes recorded in build-app; see + # "Archive app build" there for why the tree travels as a tar. + run: tar -xf app-build.tar && rm app-build.tar - name: Package macOS artifacts env: