feat(security): pin and verify every model download with secured UX - #575
feat(security): pin and verify every model download with secured UX#575jatinkrmalik wants to merge 7 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #575 +/- ##
==========================================
+ Coverage 87.58% 87.88% +0.30%
==========================================
Files 35 37 +2
Lines 6547 6703 +156
Branches 1088 1116 +28
==========================================
+ Hits 5734 5891 +157
+ Misses 586 583 -3
- Partials 227 229 +2
🚀 New features to boost your workflow:
|
| digests[f"{size}.pt"] = { | ||
| "sha256": sha256, | ||
| "size": int(response.headers["content-length"]), | ||
| } |
There was a problem hiding this comment.
Whisper hash refresh crashes
Low Severity
In collect_whisper, directly accessing response.headers["content-length"] can raise a KeyError if the HTTP HEAD response from OpenAI's CDN omits this header. This causes the script to abort, preventing Whisper model pins from being refreshed.
Reviewed by Cursor Bugbot for commit e19c7bc. Configure here.
| return 1 | ||
| fi | ||
|
|
||
| return 0 |
There was a problem hiding this comment.
Installer skips symlink zip checks
Medium Severity
verify_zip_members_safe only greps unzip -Z1 output for absolute paths and .. segments. It never detects symlink members, yet install.sh still runs unzip after that check. Runtime VOSK extraction uses safe_extract_zip, which rejects symlinks. SECURITY.md states symlinks are refused for VOSK archives, but the installer path does not enforce that.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e19c7bc. Configure here.
Ready for review — secured model downloadsTook over this PR, rebased onto latest What’s new vs the original draft
Demo (validated on the desktop)Secured download dialog with lock badge secured_model_download_sha256_verification_ux.mp4 Stages the user sees: looking up pin → connecting → downloading over HTTPS → verifying SHA256 → hash matches → ready. |
34c18e8 to
e96e6ba
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
There are 4 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e96e6ba. Configure here.
| status = ( | ||
| f"<span foreground='#e5a50a'>↓ Will download ~{_format_size(info['size_mb'])}</span>" | ||
| " · SHA256-pinned" | ||
| ) |
There was a problem hiding this comment.
Failed apply still shows success
High Severity
When a secured model download fails during reconfigure (for example SHA256 mismatch), _apply_settings_internal catches the error and returns False, but download_and_apply still calls set_complete(True). The secured download dialog then shows success even though settings were not applied and the model was discarded.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit e96e6ba. Configure here.
| verify_downloaded_model(filepath, model_type, filename) | ||
| self._report_download_status("Hash matches — integrity verified", 1.0) | ||
| if watching: | ||
| self._interruptible_pause(0.5) |
There was a problem hiding this comment.
Cancel ignored during hash verify
Medium Severity
After the byte transfer finishes, _verify_download_with_status runs a full-file SHA256 pass and the VOSK path continues with extraction without ever reading _download_cancelled. Cancel only works in _interruptible_pause and in the streaming loops, so a user who cancels while the dialog shows “Verifying SHA256 checksum…” still completes verification, renames or extracts the model, and reports success.
Additional Locations (3)
Reviewed by Cursor Bugbot for commit e96e6ba. Configure here.
Replaces the placeholder hash registry with pinned SHA256 digests for all 72 models the app can download, and hardens the paths that fetch and unpack them. - model_integrity.py: digest verification, HTTPS/known-host enforcement and a zip extractor that refuses members escaping the models directory - model_hashes.json: 29 whisper.cpp + 5 Whisper + 38 VOSK models, no gaps and no placeholder values - scripts/update_model_hashes.py: regenerates the registry from Hugging Face LFS metadata, OpenAI's digest-bearing URLs, and streamed VOSK archives - recognition_manager: verify before rename/extract, reject plain-HTTP redirects, add the missing timeout on Whisper downloads, honest verify status messaging - install.sh: shared verify_model_sha256 helper reading the same registry, plus an unsafe-path check before unzip - ship model_hashes.json in the wheel and sdist; document in SECURITY.md Co-authored-by: jatinkrmalik <jatinkrmalik@gmail.com>
- tests/test_model_integrity.py: registry completeness, digest/size mismatch, strict mode, URL trust rules, and zip extraction safety - tests/test_recognition_manager_downloads.py: tampered downloads are deleted, plain-HTTP redirects abort, VOSK traversal archives never escape models dir, and verify status messaging stays honest for pinned vs unpinned models - tests/test_installer_model_verification.py: install.sh helpers driven through bash Co-authored-by: jatinkrmalik <jatinkrmalik@gmail.com>
Make the integrity checks visible so a download feels secured, not silent: - Download dialog titled "Secured Download" with a channel-secure lock badge - Staged status: looking up pin → connecting → downloading → verifying → match - Honest completion copy: "SHA256 verified" only after an actual pin check - Speech Model page notes that downloads are SHA256-pinned without claiming on-disk files were re-hashed Co-authored-by: jatinkrmalik <jatinkrmalik@gmail.com>
Failed downloads no longer report success: the settings worker re-raises so the dialog can show the error. Cancel aborts SHA256 hashing and skips install/extract. The installer now refuses symlink zip members, Whisper pin refresh tolerates a missing Content-Length, and Portuguese VOSK keeps the published Facebook zip because vosk-model-pt-0.4 404s on Alphacephei. Co-authored-by: jatinkrmalik <jatinkrmalik@gmail.com>
e96e6ba to
8ad59bb
Compare
Catch RuntimeError (including user cancel) before RequestException so a cancelled verify still deletes the temp file and does not trip mocked requests exception types. Co-authored-by: jatinkrmalik <jatinkrmalik@gmail.com>
|
Rebased onto latest Rebase: 19 commits behind Fixes:
The existing screenshot in the description still matches the secured-download UI, so I did not add another. Pytest ( |
…gaps The Speech Model info box stacked title, specs, status, a recommendation, a glyph legend, and a SHA256 essay. Keep name, description, one status line, and a recommendation only when the current model is not the one we would pick. Integrity messaging stays on the download dialog. Also persist settings only after reconfigure succeeds, write Whisper large as large-v3.pt, refuse symlink model paths, constrain VOSK extract to the model folder, and hash installer downloads with Python when sha256sum is missing.
|
Uncluttered the Speech Model info card and closed the remaining download-path gaps. Card UX: name, one-line description, a single status line ( Also in this revision: persist settings only after reconfigure succeeds; write OpenAI Whisper |
Selecting a missing model in a settings window without a live engine was opening the download dialog and crashing with 'NoneType' has no attribute set_download_progress_callback.
|
The Settings now skips auto-apply/download when there is no engine, instead of opening a doomed dialog. |


What
Adds SHA256 hash verification for all model downloads (whisper.cpp, whisper, VOSK) to detect tampering or corruption.
Why
Hugging Face has had a rough time with security lately:
The common thread: you can't trust that a model you download is what you think it is. Hash pinning doesn't solve the authenticity problem (if upstream is already malicious, a matching hash just confirms you got the right malicious file), but it does catch:
How
Graceful degradation: if a model isn't in the hash registry, we log a warning but don't block the download. This keeps things working while we build out the hash database.
What this doesn't do
This isn't a complete solution. It's defense-in-depth. For full supply chain security we'd also want:
But hash pinning is low-effort, high-impact, and doesn't require infrastructure changes.
Testing
Checklist
Note
Medium Risk
Touches the critical model download and install path for large binaries loaded into the process; failures block installs/downloads but reduce supply-chain and zip-slip risk.
Overview
Adds SHA256-pinned model integrity for whisper.cpp, Whisper, and VOSK across the app, installer, and docs.
A shipped
model_hashes.jsonregistry (refreshed viascripts/update_model_hashes.py) records digest and size for every UI-downloadable model.model_integrity.pyenforces HTTPS-only URLs on known hosts, rejects HTTP-downgrade redirects, verifies bytes after download (optionalVOCALINUX_STRICT_MODEL_VERIFICATIONfor unpinned models), and replaces naive VOSKextractallwithsafe_extract_zip(path traversal, symlinks, expansion cap).recognition_managerwires these checks into all three download paths, cleans up onModelIntegrityError, and surfaces pin lookup / verify stages to the UI.install.shshares the same registry for Whisper, whisper.cpp, and VOSK installs and validates zip members before extract.ModelDownloadDialogand settings copy now present a secured-download flow (stages, lock badge, honest handling when no pin exists). Whisper URLs move towhisper_model_info.py; VOSK usesvosk_model_urlwith a Portuguese model ID fix. Tests cover registry coverage, installer bash helpers, and tampered / malicious download cases.Reviewed by Cursor Bugbot for commit e96e6ba. Bugbot is set up for automated code reviews on this repo. Configure here.