Audit fixes #8

Merged
kleb merged 38 commits from audit-fixes into main 2026-06-27 21:00:10 +02:00
Owner

Purpose

Package the audit-fixes branch, including fixes for duplicate paste delivery, minimize-to-tray behavior, and recording start/stop cues.

Verification

  • bun run typecheck
  • bun run lint
  • bun run test
## Purpose Package the audit-fixes branch, including fixes for duplicate paste delivery, minimize-to-tray behavior, and recording start/stop cues. ## Verification - bun run typecheck - bun run lint - bun run test
kleb added 37 commits 2026-06-27 20:56:30 +02:00
- Populate SHA-256 for all Whisper catalog entries (from HF LFS metadata) so
  the existing checksum gate actually fires; fail closed when a hash is absent.
- Add download-security helper: enforce HTTPS + a registrable-domain allowlist
  on the FINAL (post-redirect) URL for every model/archive download.
- Stream-hash Parakeet/.tar.bz2 downloads and verify the optional archiveSha256
  before extraction; reject truncated downloads.
- Add download-security unit tests.
Without a .gitattributes, autocrlf=true checks the tree out with CRLF on
Windows, which makes oxfmt's format:check fail on ~90 otherwise-clean files
and would break the new CI on non-matching runners. Pin LF for text and mark
binary assets so formatting/lint behave identically across platforms. Also
collapse a short array literal to satisfy oxfmt.
These files had lines over the 120-col printWidth (function signatures, long
strings, markdown tables) that oxfmt --check rejected, so 'bun run check' could
not pass. Pure formatting, no behavior change.
40.6.0 predated 40.7.0, which patched CVE-2026-34780 (context-isolation
bypass, CVSS 8.4), CVE-2026-34774, and CVE-2026-34769. Pin the latest 40.x
(same Node ABI, no native rebuild) and document the EOL-2026-06-30 major
upgrade follow-up.
A crash or power loss mid-write previously truncated settings.json/history.json,
and the loaders silently fall back to defaults/empty, so the user lost all
settings or their entire transcription history. Write to a temp file then rename
over the target (atomic on the same filesystem).
Runs typecheck, lint, format:check, knip, tests, and build on push to main and
on PRs, so the existing quality gates are enforced automatically. Uses
--ignore-scripts (the gates do not need the native engine binaries).
Add higher-contrast --primary-strong/--success-strong/--warning-strong accent
tokens for small text on neutral surfaces (the base accents are tuned for
fills and fail 4.5:1 as text), and switch the accent-text usages (ratings,
low-confidence label, model/queue labels, loading notes, CUDA/GPU warnings,
hotkey hint, load-model link, warn log lines) to them. Stop opacity-stacking
muted-foreground on meaningful text (log timestamps, hints, skip-setup link).
Native addon / GPU / V8 crashes never raise a JS exception, so the existing
uncaughtException and process-gone handlers cannot capture them. Start
crashReporter early with uploadToServer:false (local-only, preserving privacy)
so a minidump is written to app.getPath('crashDumps'), and log that path at
startup so users can find it for bug reports.
README declared MIT but no LICENSE file or package.json license field existed,
leaving the grant legally incomplete.
- REL-3: the per-platform exclude globs assumed package dirs named like 'win32-*'
  but the real names are node-whisper-win32-x64, sherpa-onnx-win-x64,
  lzma-win32-x64-msvc (and macOS sherpa uses 'osx', not 'darwin'). The old globs
  matched nothing, so foreign-platform .node binaries leaked into mac/linux builds.
  Rewrite to substring patterns with '/**' content variants that match the real
  names; matching-platform binaries are never excluded.
- REL-5: stop packaging @fugood/whisper.node's ~14 MB whisper.cpp source tree
  (build-only; runtime is lib/ + the prebuilt .node).
- REL-2: add afterAllArtifactBuild hook that writes SHA256SUMS.txt next to the
  installers so users can verify unsigned downloads.
- REL-1: document how to enable Windows/macOS code signing + notarization via
  env vars, and the manual-update/EOL policy, in DEVELOPMENT.md.
- A11Y-3: wrap primary content in a <main> landmark and add an sr-only <h1>.
- A11Y-4: forward value to Radix Progress.Root (was rendering indeterminate)
  and give the download progress bars an aria-label.
- A11Y-5: add role=alert to the missing-dependencies banner so it is announced.
- Add a11y i18n keys (en + de).
- I18N-3: render language names via Intl.DisplayNames (localized endonyms) in the
  language picker, per-language hotkey list, and history (badge title + use/switch
  buttons), with English+code fallback for search. Add localized auto-detect label.
- I18N-4: route the download ETA 'left' suffix through a translation key instead of
  hardcoding it.
- OBS-2: stamp the file log and the in-memory/UI buffer from a single timestamp
  so panel entries line up with app.log; carry 'fatal' through to the UI as a
  distinct level instead of folding it into 'error'.
- TEST-2: tests/main/security.test.ts covers isTrustedAppUrl (origin allowlist)
  and resolveAppAssetPath (the app: protocol traversal guard) — asserting it
  never resolves outside the renderer dir.
- TEST-3: extract the tar zip-slip guard into archive-path.ts (resolveTarEntryTarget,
  wired into the JS extractor) and test traversal/absolute/crafted entry names.
- TEST-4: assert every Whisper model carries a valid, unique sha256 so the
  integrity gate cannot silently go inert.
Add manualChunks so react/react-dom, Radix, and lucide-react land in stable
vendor chunks. The app entry drops from ~383KB to ~99KB, so app-code changes no
longer invalidate the cached vendor payload.
These activation controls were only disabled during loading_model, so a click
while recording/transcribing/typing flipped the control, got rejected by the
main process (idle-only), and snapped back with a transient error. Disable them
whenever the app is not idle.
Branch getUserMedia failures on err.name (NotAllowed/NotFound/NotReadable/...)
to show actionable, translated copy instead of the raw browser string. (A
persistent banner with an 'Open microphone settings' button needs an error-code
protocol change and is left as a follow-up.)
readPcm16WavInfo derived duration from a per-sample loop and ran twice per
recording (validate + a disk re-read in processTranscription). Derive duration
from the data-chunk size, make the expensive peak/RMS scan opt-in (computeLevels,
used only for the diagnostic log), and thread the duration into processTranscription
instead of re-reading the file.
Engine/backend selectors were a plain button group. Add role=radiogroup +
role=radio/aria-checked, roving tabindex, and arrow-key navigation so screen
readers announce it as a single-select group and keyboard users can arrow
between options. Label the engine and backend groups.
The workers always sent English 'message' strings, so the renderer's localized
t() fallback was dead code and German users saw English during the most common
flow. Send a stable 'step' id (+ detector name) alongside the English message
and translate it in the renderer (modelLoadingLabel); message remains the
fallback so any unlabelled step degrades to prior behavior.
A single non-modifier key (e.g. 'a') becomes a global hotkey that fires on
every press of that key system-wide. Show a warning hint when a bare key with
no modifiers is captured (function keys exempt, since they are commonly used
bare). Cross-hotkey conflict detection (record vs cancel vs per-language) needs
parent wiring and is left as a follow-up.
- UI-1: centralize theme logic in lib/theme.ts (used by theme-init and the
  toggle), stop persisting on mount so 'follow the OS' survives, and react to
  runtime OS theme changes while no explicit choice is stored.
- UI-4: align card radius to rounded-2xl (Card primitive + history rows) to match
  the settings sections.
whisper.ts and parakeet.ts each duplicated ~90 lines of worker-thread RPC
scaffolding (lifecycle, message switch, crash/exit cleanup, sendRequest,
terminate/restart, backend-event plumbing). Move it into engines/worker-host.ts;
the two engines now instantiate it and keep only engine-specific behavior
(parakeet's transcription cancel uses host.hasPendingOfType). Behavior-preserving.
Both engine workers duplicated wlog/emitEvent/sendResult/sendError, the
WorkerMessage type, getModelsDir, and the fetch→stream→hash→progress download
pump. Extract worker-rpc.ts (message-port primitives) and worker-download.ts
(getModelsDir + streamDownloadToFile with the HTTPS allowlist, sha256, and
truncation check). whisper/parakeet workers now call the shared code; integrity
behavior is unchanged and applied uniformly.
Move the settings sanitizer + type guards + model/backend allowlists out of the
ipc.ts god-file into a pure settings-validation.ts (windowBounds passed in, no
global reads), and add tests covering enum/model/type/size/window-bounds
rejection and prototype-pollution resistance.
Add babel-plugin-react-compiler to the Vite React plugin so components are
auto-memoized at build time (React 19). Build verified; runtime behavior should
be validated by running the app (the compiler bails on rule-of-React violations,
so existing manual memo() calls remain valid).
- UX-5: add an optional severity to the error event; model load/restore failures
  are 'high' and no longer auto-dismiss after 5s (manual dismiss still works).
- UX-6: when keystroke injection fails, copy the text to the clipboard and tell
  the user (it is also in History) instead of silently logging.
Persist the wizard step in localStorage and restore it on mount, so quitting
mid-setup no longer drops the user back to model selection; cleared on finish.
- UI-3: the compute-backend info popover was an absolutely-positioned div with
  manual onBlur dismissal; use the Radix Popover (portal, collision handling,
  consistent styling) already used by the language picker.
- UX-3: drop the post-stop audio-wait timeout from 30s to 10s so a lost renderer
  submit no longer strands the user on 'Processing' (cancel works throughout).
Add a shadcn Tooltip primitive + root TooltipProvider and use it for the window
min/max/close controls and the title-bar engine chip instead of native title
tooltips (1s delay, OS-styled). aria-labels are retained for screen readers.
Other title= controls can adopt the same primitive incrementally.
When the window is first hidden to the tray, show a one-time OS notification so
users know the app is still running (closing otherwise looks like a quit).
Persisted via a userData marker file; localized via the tray catalog.
- Abort the in-flight download via an AbortController in worker-download
  (abortActiveDownload), wired through a cancel_download worker message and a new
  engine.cancelDownload(); cancelOperation now aborts during loading_model.
- Add a Cancel control to the download progress in settings and the setup wizard,
  and surface download failures in the wizard with an actionable message (the
  Download button retries) instead of console.error.
streamDownloadToFile now resumes a partial download with a Range request
(appending, seeding the running hash from the partial), and restarts cleanly
when the server ignores Range or the partial is stale (416). Whisper keeps its
.tmp partial on network/abort failure so a retry resumes a multi-GB model
instead of restarting from zero; a corrupt resume fails the sha256 and self-heals
(partial discarded). Parakeet uses per-attempt temp paths so it starts fresh.
Backend 'error' events can now carry a stable code (+ vars); the renderer
localizes via errors.<code> and falls back to the English message when a code
has no translation. Adopt it for the typing-failure and native-insert-unsupported
errors (en + de); other emit sites can migrate incrementally.
The renderer re-declared 11 domain types (AppState, Settings, DeviceInfo,
TranscriptionEntry, etc.) that duplicated src/main/types and could silently
drift. Import them type-only from the single source of truth (erased at build,
no main code bundled); typecheck + build + knip verified.
Fix tray minimize paste and recording cues
Some checks failed
CI / check (pull_request) Has been cancelled
4d22bd6f78
Collaborator

kReview review

Verdict: no findings

No findings to address in the reviewed diff.

Overall risk is low; no confirmed correctness or security regressions survived the evidence checks in the visible diff.

Excluded as generated or vendored (not reviewed): bun.lock, ui/bun.lock.

Reviewed by kReview at c5abb4791a. This comment is conservative and based only on the PR diff, metadata, and supplied repository context.

Est. cost ~$0.99 total (194.3k in / 1.3k out) · this run ~$0.49 (97.2k in / 556 out) / gpt-5.5.

<!-- codex-forgejo-review --> <!-- codex-forgejo-review-head:c5abb4791a23d34dbb6e37b14f43c8fed7d314f7 --> ## kReview review **Verdict:** no findings No findings to address in the reviewed diff. Overall risk is low; no confirmed correctness or security regressions survived the evidence checks in the visible diff. _Excluded as generated or vendored (not reviewed):_ `bun.lock`, `ui/bun.lock`. _Reviewed by kReview at `c5abb4791a`. This comment is conservative and based only on the PR diff, metadata, and supplied repository context._ _Est. cost ~$0.99 total (194.3k in / 1.3k out) · this run ~$0.49 (97.2k in / 556 out) / gpt-5.5._ <!-- codex-forgejo-review-state:eyJoZWFkU2hhIjoiYzVhYmI0NzkxYTIzZDM0ZGJiNmUzN2IxNGY0M2M4ZmVkN2QzMTRmNyIsInN1bW1hcnkiOiJPdmVyYWxsIHJpc2sgaXMgbG93OyBubyBjb25maXJtZWQgY29ycmVjdG5lc3Mgb3Igc2VjdXJpdHkgcmVncmVzc2lvbnMgc3Vydml2ZWQgdGhlIGV2aWRlbmNlIGNoZWNrcyBpbiB0aGUgdmlzaWJsZSBkaWZmLiIsImZpbmRpbmdzIjpbXSwiY3VtdWxhdGl2ZUNvc3QiOnsidXNkIjowLjk4OTgxNzAwMDAwMDAwMDEsImlucHV0VG9rZW5zIjoxOTQzMzcsIm91dHB1dFRva2VucyI6MTMzNCwibW9kZWwiOiJncHQtNS41In19 -->
Fix cancelled download state
Some checks failed
CI / check (pull_request) Has been cancelled
c5abb4791a
kleb merged commit e74400fa1a into main 2026-06-27 21:00:10 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kleb/VoiceTyper!8
No description provided.