From c90c69f1861f777203a67fed93e6bf32dc8a62ff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 2 Jul 2026 16:21:01 +0900 Subject: [PATCH 1/4] fix: keep disabled nav items tooltipable --- apps/desktop/src/App.test.tsx | 18 +++++++++++++++++- apps/desktop/src/App.tsx | 4 ++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/App.test.tsx b/apps/desktop/src/App.test.tsx index c039dfba..3b12ea30 100644 --- a/apps/desktop/src/App.test.tsx +++ b/apps/desktop/src/App.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { App } from "./App"; @@ -205,6 +205,22 @@ describe("App", () => { expect(screen.getByRole("button", { name: /^Workspace$/i })).toBeTruthy(); expect(screen.getByRole("button", { name: /^Import$/i })).toBeTruthy(); expect(screen.getByRole("button", { name: /^Export$/i })).toBeTruthy(); + const primaryNav = screen.getByRole("navigation", { name: /primary rehearsal views/i }); + for (const name of ["Import", "Export"]) { + const navButton = within(primaryNav).getByRole("button", { name: new RegExp(`^${name}$`, "i") }); + expect(navButton).toHaveAttribute("aria-disabled", "true"); + expect(navButton).toHaveAttribute("title", "Coming soon"); + expect(navButton).not.toBeDisabled(); + } + fireEvent.click(within(primaryNav).getByRole("button", { name: /^Import$/i })); + const compactNav = screen.getByRole("navigation", { name: /compact rehearsal views/i }); + for (const name of ["Import", "Export"]) { + const navButton = within(compactNav).getByRole("button", { name: new RegExp(`${name} compact view`, "i") }); + expect(navButton).toHaveAttribute("aria-disabled", "true"); + expect(navButton).toHaveAttribute("title", "Coming soon"); + expect(navButton).not.toBeDisabled(); + } + fireEvent.click(within(compactNav).getByRole("button", { name: /Import compact view/i })); expect(screen.getByText(/^Tempo$/i)).toBeTruthy(); expect(screen.getByText(/^Key$/i)).toBeTruthy(); expect(screen.getByText(/Local-first/i)).toBeTruthy(); diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 24f5fb09..5534f322 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -498,8 +498,8 @@ export function App() { type="button" aria-current={active ? "page" : undefined} aria-disabled={active ? undefined : true} - disabled={!active} title={active ? undefined : "Coming soon"} + onClick={active ? undefined : (event) => event.preventDefault()} className={`flex min-h-11 w-full items-center gap-3 rounded-xl px-3 text-left text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${ active ? "bg-blue-600/70 text-white shadow-[0_12px_30px_rgba(37,99,235,0.32)]" @@ -560,8 +560,8 @@ export function App() { aria-current={active ? "page" : undefined} aria-label={`${label} compact view`} aria-disabled={active ? undefined : true} - disabled={!active} title={active ? undefined : "Coming soon"} + onClick={active ? undefined : (event) => event.preventDefault()} className={`inline-flex min-h-10 shrink-0 items-center gap-2 rounded-xl px-3 text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${ active ? "bg-blue-600/70 text-white" : "cursor-not-allowed text-slate-500 opacity-70" }`} From bbf13ec326046087262a000d616a0c61823e150d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 2 Jul 2026 19:56:23 +0900 Subject: [PATCH 2/4] fix: block inactive nav activation --- apps/desktop/src/App.test.tsx | 14 ++++++++++---- apps/desktop/src/App.tsx | 12 +++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/App.test.tsx b/apps/desktop/src/App.test.tsx index 3b12ea30..69ad467f 100644 --- a/apps/desktop/src/App.test.tsx +++ b/apps/desktop/src/App.test.tsx @@ -206,21 +206,27 @@ describe("App", () => { expect(screen.getByRole("button", { name: /^Import$/i })).toBeTruthy(); expect(screen.getByRole("button", { name: /^Export$/i })).toBeTruthy(); const primaryNav = screen.getByRole("navigation", { name: /primary rehearsal views/i }); + const activePrimaryNavButton = within(primaryNav).getByRole("button", { name: "Workspace" }); + expect(activePrimaryNavButton).toHaveAttribute("aria-current", "page"); for (const name of ["Import", "Export"]) { - const navButton = within(primaryNav).getByRole("button", { name: new RegExp(`^${name}$`, "i") }); + const navButton = within(primaryNav).getByRole("button", { name }); expect(navButton).toHaveAttribute("aria-disabled", "true"); expect(navButton).toHaveAttribute("title", "Coming soon"); expect(navButton).not.toBeDisabled(); } - fireEvent.click(within(primaryNav).getByRole("button", { name: /^Import$/i })); + fireEvent.click(within(primaryNav).getByRole("button", { name: "Import" })); + expect(activePrimaryNavButton).toHaveAttribute("aria-current", "page"); const compactNav = screen.getByRole("navigation", { name: /compact rehearsal views/i }); + const activeCompactNavButton = within(compactNav).getByRole("button", { name: "Workspace compact view" }); + expect(activeCompactNavButton).toHaveAttribute("aria-current", "page"); for (const name of ["Import", "Export"]) { - const navButton = within(compactNav).getByRole("button", { name: new RegExp(`${name} compact view`, "i") }); + const navButton = within(compactNav).getByRole("button", { name: `${name} compact view` }); expect(navButton).toHaveAttribute("aria-disabled", "true"); expect(navButton).toHaveAttribute("title", "Coming soon"); expect(navButton).not.toBeDisabled(); } - fireEvent.click(within(compactNav).getByRole("button", { name: /Import compact view/i })); + fireEvent.click(within(compactNav).getByRole("button", { name: "Import compact view" })); + expect(activeCompactNavButton).toHaveAttribute("aria-current", "page"); expect(screen.getByText(/^Tempo$/i)).toBeTruthy(); expect(screen.getByText(/^Key$/i)).toBeTruthy(); expect(screen.getByText(/Local-first/i)).toBeTruthy(); diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 5534f322..e8692aee 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent, type ReactNode } from "react"; import { AudioWaveform, CircleHelp, @@ -67,6 +67,12 @@ const NAV_ITEMS = [ const BRAND_BAR_HEIGHTS = ["h-3", "h-5", "h-7", "h-4", "h-6"] as const; +/** Documented. */ +function blockInactiveNavActivation(event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); +} + /** Documented. */ function progressMessage( t: ReturnType, @@ -499,7 +505,7 @@ export function App() { aria-current={active ? "page" : undefined} aria-disabled={active ? undefined : true} title={active ? undefined : "Coming soon"} - onClick={active ? undefined : (event) => event.preventDefault()} + onClick={active ? undefined : blockInactiveNavActivation} className={`flex min-h-11 w-full items-center gap-3 rounded-xl px-3 text-left text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${ active ? "bg-blue-600/70 text-white shadow-[0_12px_30px_rgba(37,99,235,0.32)]" @@ -561,7 +567,7 @@ export function App() { aria-label={`${label} compact view`} aria-disabled={active ? undefined : true} title={active ? undefined : "Coming soon"} - onClick={active ? undefined : (event) => event.preventDefault()} + onClick={active ? undefined : blockInactiveNavActivation} className={`inline-flex min-h-10 shrink-0 items-center gap-2 rounded-xl px-3 text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${ active ? "bg-blue-600/70 text-white" : "cursor-not-allowed text-slate-500 opacity-70" }`} From 459acda7c6e5d6d1ff6a0e49024f1ac77c4ea2a3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 2 Jul 2026 15:20:17 +0900 Subject: [PATCH 3/4] fix: update anyhow for RustSec 2026-0190 --- apps/desktop/src-tauri/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index 4d9ae737..0df254ea 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "atk" From 7aaf86d076315c47a52c0192fc515398ee210a9a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 2 Jul 2026 19:45:56 +0900 Subject: [PATCH 4/4] fix: document quick-xml advisory exceptions --- apps/desktop/src-tauri/.cargo/audit.toml | 2 ++ apps/desktop/src-tauri/osv-scanner.toml | 8 ++++++++ docs/security/dependency-policy.md | 1 + 3 files changed, 11 insertions(+) diff --git a/apps/desktop/src-tauri/.cargo/audit.toml b/apps/desktop/src-tauri/.cargo/audit.toml index 9fc2a4f3..861e0aa5 100644 --- a/apps/desktop/src-tauri/.cargo/audit.toml +++ b/apps/desktop/src-tauri/.cargo/audit.toml @@ -17,4 +17,6 @@ ignore = [ "RUSTSEC-2025-0100", # unic-ucd-ident: unmaintained "RUSTSEC-2025-0098", # unic-ucd-version: unmaintained "RUSTSEC-2024-0429", # glib 0.18.5: VariantStrIter unsoundness, transitive via Tauri/wry/webkit2gtk/gtk GTK3 stack; remove when upstream drops or patches the chain + "RUSTSEC-2026-0194", # quick-xml 0.39.4: inherited via Tauri/plist and rfd/wayland-scanner; no compatible upstream release has moved both chains to quick-xml >=0.41.0 yet + "RUSTSEC-2026-0195", # quick-xml 0.39.4: same owner chain and removal condition as RUSTSEC-2026-0194 ] diff --git a/apps/desktop/src-tauri/osv-scanner.toml b/apps/desktop/src-tauri/osv-scanner.toml index 16b3b20e..c8fc5e44 100644 --- a/apps/desktop/src-tauri/osv-scanner.toml +++ b/apps/desktop/src-tauri/osv-scanner.toml @@ -65,3 +65,11 @@ reason = "Inherited through the current Tauri GTK3 owner chain and already track [[IgnoredVulns]] id = "RUSTSEC-2024-0429" reason = "glib 0.18.5 VariantStrIter advisory inherited through Tauri/wry/webkit2gtk/gtk; allowed only until upstream drops or patches the chain, with scope guarded by scripts/checks/verify_supply_chain.py." + +[[IgnoredVulns]] +id = "RUSTSEC-2026-0194" +reason = "quick-xml 0.39.4 duplicate-attribute advisory is inherited through Tauri/plist and rfd/wayland-scanner; current compatible upstream crates do not yet allow quick-xml >=0.41.0, and this app does not expose those XML parser paths to untrusted user XML." + +[[IgnoredVulns]] +id = "RUSTSEC-2026-0195" +reason = "quick-xml 0.39.4 namespace-allocation advisory is inherited through the same Tauri/plist and rfd/wayland-scanner owner chain as RUSTSEC-2026-0194; remove once compatible upstream crates move to quick-xml >=0.41.0." diff --git a/docs/security/dependency-policy.md b/docs/security/dependency-policy.md index d3a9680e..d7c7acad 100644 --- a/docs/security/dependency-policy.md +++ b/docs/security/dependency-policy.md @@ -104,6 +104,7 @@ Current controlled exceptions: - No Python vulnerability exceptions are active. `GHSA-5239-wwwm-4pmq` (`Pygments <2.20.0`) was removed by locking `Pygments` to `2.20.0`; the CI `security-audit` workflow must run `pip-audit --local --strict` against the synced `uv` environment without a targeted ignore for that advisory. - Cargo audit warnings for legacy `gtk3` vulnerabilities (e.g. `RUSTSEC-2024-0413`) inherited through Tauri v2 `wry`/`webkit2gtk` integration are explicitly allowed. These are deep framework dependencies with no alternative, so they are documented exceptions and ignored by default. - `RUSTSEC-2024-0429` for `glib 0.18.5` is allowed only for the `VariantStrIter` advisory inherited through the Tauri/wry/webkit2gtk/gtk GTK3 stack. A compatible lockfile refresh can move the desktop stack to `tauri 2.11.3`, `wry 0.55.1`, `tao 0.35.3`, `muda 0.19.3`, and related transitive patches, but it still does not move this stack to patched `glib >=0.20.0`; the exception must remain encoded in repo-controlled audit configuration and guarded by `scripts/checks/verify_supply_chain.py`, and it must be removed when upstream drops or patches the chain. +- `RUSTSEC-2026-0194` and `RUSTSEC-2026-0195` for `quick-xml 0.39.4` are allowed only while the current compatible upstream owner chains still require vulnerable `quick-xml`: `plist 1.9.0` through Tauri, and `wayland-scanner 0.31.10` through Linux `rfd`/Wayland dependencies. `quick-xml >=0.41.0` is patched, but `plist 1.9.0` requires `quick-xml ^0.39.2` and the current `wayland-scanner` release also has no compatible patched path. BandScope does not expose either owner chain as a user-controlled XML ingestion surface; the exception must stay encoded in repo-controlled cargo-audit and OSV configuration, and must be removed once compatible upstream crates publish a patched dependency path. Retired third-party deprecation and advisory signal: