MPV.Rocks Installer — Deep Codebase Review & Audit (glm-5.3)

Date: 2026-08-31 · Baseline commit: bdfb31b (docs: record v1.3 native qualification)
Scope: 247 Go files (~90k lines), cmd/, pkg/, internal/, web frontend (templates + ~8.5k lines JS), build/CI/release tooling, docs claims
Method: 8 parallel deep reviews (glm-5.3 max effort) + lead spot-verification of top findings + full dynamic validation · Companion artifacts: .opencode/reviews/*.md
1
Critical
26
High
73
Medium
49
Low
5
Info
154
Total findings

1. Executive summary

The codebase is in notably good shape for its size: all Go tests pass, go vet ./... is clean (re-run by the lead on the reviewed baseline), the web auth/token design is solid, the signed-manifest updater core uses standard crypto correctly, and the 7z extractor is genuinely hardened. However, this audit found 154 findings concentrated in four systemic weaknesses:

  1. Update/relaunch lifecycle is not actually safe end-to-end. The relaunched TUI renders into helper.log instead of the terminal (Critical), health is acknowledged before the TUI initializes, the swap protocol has crash windows with no launchable executable, and integrity checks run after executing staged files.
  2. Destructive file operations are not transactional or ownership-scoped. Windows uninstall recursively deletes a user-chosen (possibly shared) directory; config reset/restore and FFmpeg rollback can destroy the only known-good copy on ordinary I/O failures.
  3. Cancellation and shutdown don't own their workers. Web job cancellation releases the method slot before the worker stops; TUI Escape/Ctrl+C abandon goroutines and child package-manager processes; Web/TUI shutdown can orphan destructive jobs mid-transaction.
  4. Release provenance has gaps. The signed manifest is computed from re-downloaded registry bytes rather than pipeline build artifacts; release immutability/authorization depends on unverified external GitLab settings; BLAKE3SUMS.txt is incompatible with the documented b3sum -c command.
AreaCriticalHighMediumLowInfo
Web server/API/security (pkg/web)02561
Installer (pkg/installer)04841
Self-update / release stack0512101
TUI (pkg/tui)191561
Core utilities (pkg/platform, config, keyring, …)01950
Web frontend (internal/webassets)00640
Supporting packages + main0210110
Build / CI / dependencies / docs03831
Total12673495
VALIDATION Unlike the static-only sub-agent reviews, the lead independently re-verified the Critical and top High findings against source and ran go vet ./... (exit 0) and go test ./... (all packages pass; exit 0). Every finding is therefore a latent defect not currently caught by the suite — the test-gap pattern is itself a documented finding (§7).

2. Critical

CRITICALC-1 — Relaunched self-updated TUI renders into helper.log, stealing terminal input

pkg/version/transaction.go:349-358, 466-475 · pkg/tui/models_update.go:876-896 · cmd/mpv-manager/main.go:209-276

The TUI hands off via LaunchUpdateHelper("tui"); the helper's stdout/stderr are redirected to helper.log, and the updated binary is launched with those same descriptors while stdin remains the terminal. The relaunched Bubble Tea program puts the terminal into raw mode and consumes keystrokes while all frames/escape sequences go invisibly to the log. To the user it looks like a return to the shell whose input is being eaten by a hidden TUI. The commit can already be permanent at this point.

Fix: preserve/reopen the controlling terminal output handles for the relaunched TUI (Windows-safe CONOUT$ path included); keep helper diagnostics on a separate descriptor. (Lead-verified in source.)

3. High findings

3.1 Self-update / release stack

HIGHH-1 — Exec before hash recheck (TOCTOU at target-adjacent staging)

pkg/version/transaction.go:287-293, 512-519, 533-537 · pkg/version/version.go:904-932

The helper executes target.StagedPath and later the installed target.Path for identity validation before verifyUpdateArtifact/post-swap BLAKE3 checks. Anyone able to replace a target-adjacent file during the parent-exit window gets code execution at helper privilege; the mismatch is only detected afterward. Can cross a privilege boundary when an elevated primary update includes a user-writable secondary.

Fix: size+BLAKE3 before every exec; protected staging (open handle / verified inode) and unsafe-directory rejection.

HIGHH-2 — Apply and rollback both create a crash window with no launchable primary executable

pkg/version/transaction.go:521-526, 575-597 · cmd/mpv-manager/main.go:154-160

Apply renames the installed executable to backup, then renames the stage into place; a kill between the two leaves the primary pathname absent, and the advertised startup recovery can't run because there is nothing to launch. Rollback repeats the pattern (remove replacement, then rename backup).

Fix: durably sync the backup without removing the live pathname; platform-atomic replacement (rename over existing path on Unix, ReplaceFile on Windows).

HIGHH-3 — Health acknowledged before the relaunched process is actually ready

cmd/mpv-manager/main.go:113-125, 154-184, 209-276 · pkg/version/transaction.go:620-657

The acknowledgement is written after arg parsing — before platform detection, release fetching (up to 3 manifest attempts), model construction, or tea.NewProgram(...).Run(). With only a 1.5 s stabilization window, the helper can permanently commit an update whose child is still blocked in startup networking or about to fail during TUI init. The qualifier cannot catch this (its fixture only acknowledges and sleeps).

Fix: acknowledge from a real post-first-render readiness event; failures before that trigger rollback. (Also flagged High by two other reviewers — consolidated.)

HIGHH-4 — Missing backups treated as successful rollback

pkg/version/transaction.go:546-572, 575-597, 939-946

rollbackUpdateTargets silently continues when the backup stat reports not-exist, including for applied targets. AV/disk/cleanup removing a backup after apply ⇒ rollback "succeeds", journal says rolled_back, helper relaunches the rejected binary; recovery can claim success while the primary is absent.

Fix: missing backup for an applied target = rollback failure; verify restored bytes/identity before recording state.

HIGHH-5 — v1.1/v1.2 legacy bootstrap remains unauthenticated

pkg/releasemanifest/manifest.go:62-64, 127-139 · cmd/generate-info/main.go:1234-1258 · historical ffad438^

The manifest deliberately emits legacy URL/hash fields for old clients, but the historical v1.2 decoder performs no signature verification and trusts the BLAKE3 from the same document — an attacker controlling the manifest response supplies both binary and matching hash.

Fix: stop describing legacy bootstrap as signed; use pinned detached signatures or manual replacement; retire the automatic path after migration.

3.2 Installer

HIGHH-6 — Windows uninstall recursively deletes an arbitrary writable custom directory and reports success despite deletion failures

pkg/installer/windows.go:103-157 · pkg/config/validation.go:102-168 · pkg/web/api_settings.go:50-101

ValidateCustomInstallPath accepts any existing absolute writable non-root non-%WINDIR% directory; uninstall then enumerates and RemoveAlls every top-level entry except portable_config/updater/manager files. Choosing e.g. C:\Users\name\Downloads as install destination makes uninstall erase unrelated files; per-entry errors are warnings and the function still returns success. (Lead-verified in source.)

Fix: ownership manifest at install time; uninstall only manifest-owned paths; fail if cleanup incomplete.

HIGHH-7 — FFmpeg rollback deletes the only known-good binary when the restore rename fails

pkg/installer/installer.go:525-614 · pkg/installer/real.go:65-72

Active exe is renamed to ffmpeg.old, replacement copied directly (not staged+atomic); on copy failure rollback removes the destination and renames the backup back — but a deferred cleanup unconditionally deletes ffmpeg.old, including when that rename failed (AV/sharing race on Windows).

Fix: retain backup until restoration positively confirmed; destination-adjacent staging + atomic replace.

HIGHH-8 — Destructive config reset proceeds after preservation/backup failures (fail-open) and writes non-atomically

pkg/installer/installer.go:1060-1107 · config_preservation.go:115-234 · windows.go:720-762 · common.go:131-158

InstallMPVConfigWithOutput logs-and-continues on both preservation and backup failures, then os.WriteFiles over mpv.conf; failure reapplying saved settings is downgraded to a warning after commit. Tests encode the continuation.

Fix: fail closed before replacement; staged+fsynced atomic replacement; failure rollback.

HIGHH-9 — IINA install validates one DMG but copies from a hard-coded mount that may be another image

pkg/installer/macos.go:422-467, 199-230, 128-131

hdiutil attach output is ignored; the code assumes /Volumes/IINA/IINA.app. A pre-existing /Volumes/IINA makes macOS mount the verified image as /Volumes/IINA 1, so the installer validates and copies the wrong volume's app (possibly attacker-prepared), then ad-hoc signs and launches it. Cleanup detaches the hard-coded path, not the attached device.

Fix: attach with -plist -nobrowse, parse device/mount point, validate/copy/detach that exact mount.

3.3 Web server/API

HIGHH-10 — Package-version probe timeout is ineffective and holds the cache write lock

pkg/web/server_version_cache.go:183-225, 263-326 (esp. 313), 331-365 · pkg/web/package_version.go:273-829

The timeout branch's unlabeled break exits only the select, not the collection loop — with two probes still blocked, the next iteration waits on resultChan forever; probes use exec.Command (not CommandContext) so the timeout can't kill a stuck package manager. Runs synchronously during startup and under the cache write lock on refresh. (Lead-verified in source.)

Fix: bounded context per probe, labeled timeout exit, build replacement cache outside the lock.

HIGHH-11 — Cancelling a job permits a conflicting destructive job before the worker stops

pkg/web/jobs.go:256-278, 482-541, 591-608 · api_install.go:339-393, 425-540 · api_adopt.go:175-248, 379-476

CancelJob archives/removes the job immediately; CreateJobIfNoneActive then accepts another operation for the same method while the original goroutine still executes (worker calls no-op; several workers have final config writes after their last cancellation check). Cancel-and-retry can overlap install/uninstall/adopt transactions.

Fix: explicit cancelling state; keep job active until worker acknowledges termination; final context check before every commit.

3.4 TUI

HIGHH-12 — Escape abandons live work without cancellation; manager-update abort path unreachable

pkg/tui/models_update.go:227-239, 870-905, 1040-1067 · models_messages.go:20-64

The global Escape branch resets StateInstalling models before the state-specific branch that calls PreparedSelfUpdate.Abort can run; workers use context.Background and keep running; stale completions can be attributed to a new selection; an escaped prepared self-update retains its lock/transaction until process exit.

Fix: single operation state machine that cancels, drains, aborts, and only then returns to menu.

HIGHH-13 — Multi-channel stream can randomly lose completion or trailing output

pkg/tui/models_messages.go:324-375 · models_update.go:480-511, 513-675, 677-678

Worker sends buffered installDone then closes outputChan; once both are ready startStreaming selects nondeterministically — a closed-output win leaves the UI stuck "installing" forever; a completion win silently drops remaining output.

Fix: one ordered event channel (closed only after a final result), or receiver tracks closed channels until drained + exactly one terminal result.

HIGHH-14 — Install/update/uninstall destinations are stale or discarded

pkg/tui/models_update.go:171-187, 438-446 · models_types.go:141-148 · install_path.go:90-103 · contrast pkg/web/operation_state.go:19-28

executeInstallCmd accepts/logs installDir but never applies it (changing the custom path mid-session installs to the startup path while recording the new one); updateItem drops backend AppID/InstallPath; uninstall uses the global destination — on Windows this can update/delete a different MPV installation than selected.

Fix: carry stable app ID + path; CloneForOperation with immutable snapshot for every operation.

HIGHH-15 — "Change MPV UI" performs a full reinstall and records a duplicate installation

pkg/tui/models_update.go:1148-1170, 1724-1740, 606-612 · contrast pkg/web/api_adopt.go:182-249

Selects the first MPV app silently, runs the full install path with isUpdate=false, then saveInstalledApp can add a duplicate record instead of updating the selected app's UI type.

Fix: dedicated TUI UI-change operation calling InstallUISafely against the selected app's config dir.

HIGHH-16 — Applying language preferences can replace the entire active mpv.conf

pkg/tui/language_preferences.go:828-859 · pkg/config/editor.go:144-176

backupMPVConfig renames the active config away before SetConfigValue, which then sees no file and writes fresh embedded defaults — unrelated user settings vanish; a same-day second apply can overwrite the date-named backup with the generated config. Apply returns no error to the state machine, so failures still show "preferences saved".

Fix: unique high-resolution copy backup without removing the source; surface errors before success.

HIGHH-17 — Accepting a list filter also activates the selected action, including destructive ones

pkg/tui/models_update.go:41-224, 1090-1106, 1179-1196, 1280-1311 · guards exist in modernz_options.go:210-224 / uosc_options.go:186-200

Enter intended to accept a Bubbles filter can immediately start an install/uninstall; some handlers pass Enter to list.Update (changing filter state) and process it as an action; async filter matching can act on a stale item.

Fix: centralize list key routing; never dispatch application Enter/Escape while a filter is being set.

HIGHH-18 — Ctrl+C is presented as cancellation but quits without cancelling or joining work

pkg/tui/models_update.go:25-31 · models_views.go:690-694 · models_messages.go:33-64

The UI says Ctrl+C cancels; the handler clears a few fields and tea.Quits — no context cancel, no worker join; child package-manager processes can outlive the TUI; a prepared manager transaction is not aborted. (Consolidated with the Web/TUI shutdown finding — §6 theme 2.)

Fix: first interrupt cancels and waits (bounded) for terminal worker result/rollback; second interrupt force-quits.

HIGHH-19 — Interactive sudo competes with Bubble Tea for the raw terminal

pkg/tui/models_update.go:870-909 · pkg/installer/command_runner.go:64-93, 169-203

Without a keyring password, RunSudoCommand falls back to interactive sudo while Bubble Tea's input reader stays active in raw/alternate-screen mode — missing/split password input, broken masking, or a hung install; exactly the first-Linux-install case.

Fix: pre-authenticate via a secure TUI flow or use Bubble Tea's exec/suspend so the child exclusively owns a restored terminal.

3.5 Core utilities

HIGHH-20 — Config restore can leave mpv.conf missing/partial, and same-day backups collide

pkg/installer/installer.go:1176-1189 · common.go:161-194 · real.go:65-72 · constants/constants.go:89-92

Restore renames the current config away before validating/reading the source, with no rollback on any failure; the copy is ReadFile+non-atomic WriteFile; safety backups are date-precision and can be overwritten by a second same-day operation.

Fix: one restore transaction under the path lock — validate, unique fsynced snapshot, atomic replace, restore snapshot on every failure.

3.6 Build / CI / release

HIGHH-21 — Signed manifest is not cryptographically bound to the pipeline's build artifacts

.gitlab-ci.yml:429-468, 477-506 · cmd/generate-info/main.go:948-977, 1234-1267

The manifest job receives only generator artifacts; it reconstructs registry URLs, re-downloads the bytes, and signs those. A duplicate upload, registry writer, or concurrent publication that changes the registry object before signing makes the protected signer authenticate bytes this pipeline never built (GitLab allows duplicate generic-package files by default).

Fix: pass raw build artifacts to the manifest job, sign those local bytes, upload once, then byte/hash-compare published objects before signing.

HIGHH-22 — Release immutability/authorization/ordering depend on unverified external settings

.gitlab-ci.yml:191-192, 432-433, 480-481, 556-557 · docs/GITLAB_CI.md:60-68, 81-105

Every tag (not only protected semantic v\d+\.\d+\.\d+) matches release rules; no duplicate-file preflight or resource_group; concurrent/retried tag pipelines can republish a tag or race the stable-channel PUT; docs assume protected variables but don't require a protected v* tag rule.

Fix: strict tag grammar + CI_COMMIT_REF_PROTECTED; fail if a tag file already exists; serialize stable publication.

HIGHH-23 — Final release has no enforced native-signing/native-execution gate

.gitlab-ci.yml:551-560 · docs/GITLAB_CI.md:115-121 · docs/SELF_UPDATE_AND_WAILS_REVIEW_2026-08-28.md:36-51

Release needs only publish:release-manifest; Linux cross-compilation can publish Windows/macOS artifacts with no machine-verifiable evidence that Authenticode/notarization/native smoke tests passed (acknowledged v1.3 publication blocker, restated for completeness).

Fix: gate release creation on native jobs that sign/verify/smoke-test the exact artifacts, or a protected manual approval recording immutable qualification evidence.

4. Medium findings (73, condensed)

Web server/API — 5 Medium
IDFindingRefs
W-M1Cached sudo credentials let any submitted password "pass" validation (sudo -S -v without -k)api_keyring.go:84-121; command_runner.go:194-229
W-M2Backup path validation follows intermediate symlinks outside the backup rootconfig/validation.go:17-55, 93-99; api_config.go:586-635
W-M3Failed restore can leave active mpv.conf missing (see H-20)api_config.go:586-608
W-M4Global locale cache not concurrency-safe (unsynchronized lazy load; shared backing slice sorted in place)locale.go:13-33, 57-75, 111-123, 305-313
W-M5Multi-setting writes validated together, committed independently (up to 22 writes; partial apply on mid-failure)api_config.go:113-466; api_languages.go:28-120
Installer — 8 Medium
IDFindingRefs
I-M1Windows discovery executes untrusted candidate binaries during a read-only scan (no context/timeout; PATH/registry-supplied paths)windows_detection.go:69-105, 148-164
I-M2Uncertain probes treated as "not installed" and records synced away; method-keyed identity collapses multiple installs; locale-varying output parsingdetection.go:167-465; config/config.go:711-744
I-M3MPC-QT install uses Start-Process without -Wait -PassThru; returns before installer completeswindows_mpcqt.go:516-572
I-M4Restore moves active config away before validating/staging replacement (dup of H-20)installer.go:1165-1190
I-M5No destination-scoped exclusion between installer operations — different methods can concurrently mutate shared config/UI pathsinstaller.go:72-120; common.go:367-390
I-M6ZIP/tar extraction lacks the 7z preflight policy (no entry/size/symlink policy; direct extraction into live destinations)installer_archive.go:15-68; real.go:191-253
I-M7Runtime rollback transactions not crash-durable (in-memory state; .txn-*/.bak-* debris not recovered at startup)file_transaction.go:23-205; ui_config_update.go:269-488
I-M8Cached sudo timestamp issue (dup of W-M1)command_runner.go:169-229
Self-update / release stack — 12 Medium
IDFindingRefs
U-M1Target-directory metadata not durably ordered with journal (no dir fsync after renames; recovery doesn't revalidate committed targets)transaction.go:491-540, 803-870
U-M2Crash before first journal write permanently blocks future updates (orphan dir never cleaned)transaction.go:225-318, 905-916
U-M3PrepareSelfUpdateFromCheck accepts a forgeable mutable DTO (any non-empty ManifestKeyID = "authenticated"; qualifier proves HTTP/synthetic values accepted)version.go:84-210; transaction.go:175-203
U-M4No freshness/anti-replay on signed metadata (replay older-higher release or suppress security updates)manifest.go:65-71, 229-245
U-M5Static single-key authorization; no revocation/threshold/validity epochsmanifest.go:54-71, 191-224
U-M6Stale/tampered manager_bin_path secondary can overwrite an unrelated regular file (no identity proof)config.go:692-713; transaction.go:151-168, 265-284
U-M7Detached helper failures never reach the initiating UX (Web job completes before any replacement occurs)transaction.go:328-500; api.go:278-301
U-M8Health-failure rollback races a still-running child on Windows (kill without wait)transaction.go:479-488, 620-657
U-M9Release-generator downloads: no whole-transfer deadline, unbounded body, Proxy nilgenerate-info/main.go:56-68, 546-634
U-M10Qualifier's hidden --qualifier-driver mode destructive outside controller (no capability token/containment)qualify-selfupdate/main.go:97-126, 299-381
U-M11BLAKE3SUMS.txt not in b3sum -c format (blake3: prefix; documented verify command unusable)gen-checksums/main.go:49-90; CI :609-623
U-M12Auto publication promotes unreviewed upstream "latest" releases into signed trustgenerate-info/main.go:358-448, 1202-1269
TUI — 15 Medium
IDFindingRefs
T-M1App-update bookkeeping uses stale menu ID "updates"; successful updates stay at old version and are re-offeredmodels_update.go:43-48, 614-618, 999-1037
T-M2Persistence failures ignored or printed straight to stdout (corrupts frames); success rendered anywaymodels_update.go:545-596, 977-989
T-M3Restore removes active config before replacement durable (dup of H-20)models_update.go:1588-1606
T-M4Detached worker panics bypass Bubble Tea terminal recovery (10 raw goroutines, no recover)models_messages.go:180-309
T-M5Progress bar fed 0–100 values to ViewAs, which expects 0–1 (≥1% renders as 100%)models_update.go:491-503
T-M6Output not keyboard-scrollable; mouse mode never enabled; auto-scroll never resetmodels_views.go:124-128; models_update.go:679-689
T-M7Output rendering unbounded and quadratic (full transcript re-joined per line)models_init.go:345-348
T-M8Resize omits many active lists; fixed deductions without minimumsmodels_update.go:360-394
T-M9"Install to PATH" broken on Windows (no .exe, PATH untouched); non-idempotent Unix aliases; failures reported as successmodels_messages.go:77-309
T-M10Job history not safe across TUI/Web processes (§6 theme 4)job_history.go:91-98
T-M11Ctrl+V overlay shows hard-coded 0.1.0/Go 1.21/linux/amd64models_views.go:736-755
T-M12Offline startup not offline-first (~33 s of failed fetches before first render; network actions not gated)main.go:209-238
T-M13Reset erases data after backup failure while claiming "backup saved"reset_data.go:33-47
T-M14Unicode search backspace removes one byte, not one characterlanguage_preferences.go:264-268
T-M15Raw subprocess output rendered without stripping terminal control sequences (CSI/OSC injection)models_update.go:505-511
Core utilities — 9 Medium
IDFindingRefs
P-M1Backup validation symlink/race escape (dup of W-M2)config/validation.go:33-99
P-M2Setters mutate memory then fail to persist without rollback; reset ignores backup failureconfig/config.go:459-1090
P-M3mpv.conf editor truncates quoted values at #; edits first-not-effective duplicateconfig/editor.go:46-213
P-M4Linux GPU detection truncates models at first (; skips other probes when glxinfo returns anything; empty PCI-ID stubplatform/gpu.go:85-1218
P-M5macOS AV1 over-reported (unknown models fail open; M2 wrongly grouped with M3+; CGO-off heuristic shipped)platform/gpu_darwin.go:22-64
P-M6Linux arm64 CPUs can be labeled x86-64-v2 (asimd unrecognized)platform/cpu.go:38-177
P-M7Job history cross-process safety (§6 theme 4)jobhistory/jobhistory.go:64-157
P-M8Hotkey round-trip corrupts quoted # args; # accepted as a key and serializes to a commenthotkeys/inputconf.go:109-220
P-M9Locale data/selection disagree (hif/te/fil unresolvable; wrong regional assignments; es-419 flag)locale/selection.go:49-111; locales.json
Web frontend — 6 Medium
IDFindingRefs
F-M1Stored-password installs dispatch the privileged operation twice (callback invoked and true returned; 409 corrupts button state)password-modal.js:398-416; install.js:287-315
F-M2Lost SSE events never reconciled after reconnect (no IDs/replay/snapshot; stale jobs/badges/buttons until refresh)jobs.js:27-344; sse.go:35-49
F-M3Config apply marks concurrent edits as saved (baseline rebuilt from current controls, not submitted payload)config-page.js:74-108
F-M4UI option save/reset can overwrite newer intent (no generation/abort per key)ui-settings.js:424-590
F-M5Regional-language cancellation loses the AbortController; out-of-order responses render wrong variantslanguages.js:424-887
F-M6Alpine job modal opens focus controller while still hidden (focus targets rejected as invisible)base.html:32-45; jobs-modal.js:49-78
Supporting packages / main — 10 Medium
IDFindingRefs
S-M1Persistent SSE handlers defeat graceful HTTP shutdown (2 s timeout → "failure", exit 1)main.go:345-387; server.go:229-254
S-M2Command parsing misroutes positionals/conflicting modes (--verbose cli starts Web; internal flags leak into usage)main.go:56-99, 162-184
S-M3--verbose/--debug don't produce promised console loggingmain.go:127-135; logger.go:48-196
S-M4Accepted string values not round-trip safe (" #" split corrupts quoted values)scriptopts/options.go:29-75
S-M5BOM misread as part of first key; edits produce mixed CRLF/LFscriptopts.go:73-280
S-M6Atomic replacement destroys symlinks and original metadata (mode forced 0644)fileops.go:66-102
S-M7Config locking doesn't cover stale public snapshots or multiple processesfileops.go:20-64; scriptopts.go:73-159
S-M8Job history cross-process (dup of P-M7)jobhistory.go:64-157
S-M9CLI --path ignored by component dispatch but recorded as InstallPathmain.go:534-663
S-M10Browser launch/banner race the actual bind; httpServer assign/read racemain.go:345-375; server.go:116-235
Build / CI / docs — 8 Medium
IDFindingRefs
B-M1Distributed archives omit LICENSE and third-party notices.gitlab-ci.yml:331-392
B-M2Windows resource generation stale/fail-open/incomplete for arm64 (|| true; committed 1.0.0.0/Win7)Makefile:64-67; winres.json
B-M3Standalone make release-build works without trust ring → release binaries that reject all metadataMakefile:51-54, 197-227
B-M4Release generator unbounded downloads (dup of U-M9)generate-info/main.go:56-67, 594-632
B-M5No live-browser job/SSE E2E in CI (jsdom/fakes only)vitest.config.mjs:3-7; TESTING.md:129-155
B-M6Release jobs execute mutable container-image tags (alpine:latest, release-cli:latest).gitlab-ci.yml:51-553
B-M7Tag grammar validated only after public package publication begins.gitlab-ci.yml:191-335
B-M8BLAKE3SUMS.txt format (dup of U-M11; cmd/gen-checksums has no tests)gen-checksums/main.go:49-90

5. Low / Info findings (49 + 5, one-line index)

LOW Web (6)
  • Route-level method restrictions incomplete (GET can trigger state sync on check-updates/refresh-installed/settings/shortcut)
  • ui_type unvalidated on install/update → invalid durable state
  • Backup listing panics if an entry vanishes mid-enumeration (nil FileInfo)
  • SSE clients/writes unbounded (no cap, no write deadline)
  • Any version difference labeled "update", including downgrades
  • Keyring failures return HTTP 200 with raw backend detail
LOW Installer (4) + INFO (1)
  • OSC edits can create duplicate authoritative entries
  • uOSC temp config leaked on failed install
  • Output capture splits on read chunks, not lines (truncated/merged records)
  • Generated shortcuts embed paths without shell/VBS-safe encoding
  • INFO: multiple legacy/uncalled helpers expand the review surface
LOW Updater (10)
  • Unknown JSON fields outside the signature while verification still succeeds
  • HTTPS not revalidated across redirects
  • PID-reuse confusion in handoff/recovery
  • Signed asset strategy / CPU baseline / NativeSigning fields not enforced
  • Legacy manager fields can diverge from v2 assets
  • Pathname-based advisory lock replaceable on Unix
  • Identity probes: unbounded output, no process-group kill
  • Obsolete in-process updater retained as dead production code (regression risk)
  • Qualification omits the failure boundaries most likely to invalidate its claims
  • Per-attempt timeouts can extend one self-update to ~45 minutes
LOW TUI (6) + INFO (1)
  • Error-screen Enter advertised but unhandled
  • Screenshot inputs can create malformed quoted config; warning never clears (value receiver)
  • Hotkey preset application re-entrant
  • Package-info rows selectable with no action
  • PostInstallMessage normalized for history only, not control flow
  • Resize-independent wrappers unscrollable/blank in tiny terminals
  • INFO: dead states/messages/duplicated branches (StateSuccess, installSuccessMsg, …)
LOW Core utilities (5)
  • Country filter deliberately leaks regionless languages (test-locked bug)
  • Active-UI detection cannot represent ambiguous two-script installs
  • Bursty log lines silently dropped (non-blocking send, empty default)
  • Hotkey backups unbounded, no retention
  • Static catalog IDs contradict descriptions (volume-up describes decrease, etc.)
LOW Frontend (4)
  • Mobile drawer visually modal but not focus-contained
  • formatKeys builds template.HTML from unescaped fragments (trust-boundary hazard)
  • No CSP on a privileged local UI (inline scripts/handlers everywhere)
  • Highest-risk orchestration modules have no behavioral tests — the Medium findings live in those seams
LOW Supporting/main (11)
  • Temp-file sync not fully durable (no parent-dir fsync)
  • Backups best-effort, permission-widening, unbounded
  • Managed scaling preservation guesses implicit defaults
  • uoscconf.KnownOptions exposes mutable global slices (ModernZ deep-copies; divergence)
  • os.Exit bypasses deferred cleanup; cancellation exit codes inconsistent
  • Signals handled only after slow startup work
  • Production panics print full stacks unconditionally
  • --help/--version --json initialize the file logger (stateful identity probes)
  • Browser helper never reaped (zombie)
  • Windows resources hardcoded 1.0.0.0
  • Public snapshot writes have no stale-version detection
LOW Build/CI (3) + INFO (1)
  • Coverage regex matches per-package lines, not the aggregate
  • make lint installs golangci-lint@latest, not mirrored in CI
  • Docs contradictions (macOS "universal", Windows arm64 "future", obsolete URLs, stale AGENTS.md line counts)
  • INFO: dependencies otherwise current and exactly pinned; Alpine 3.17.0 available (no security requirement found)

6. Cross-cutting themes (deduplicated)

  1. Config restore/reset is not transactional (H-8, H-20, W-M3, I-M4, T-M3): every path that replaces mpv.conf moves the original away before the replacement is validated/durable, with fail-open backups and date-collision names.
  2. Worker lifecycle isn't owned by cancellors (H-11, H-12, H-18, S-M1): Web cancel, TUI Escape/Ctrl+C, and process shutdown all return before the goroutines/subprocesses they ostensibly cancel have stopped.
  3. Sudo timestamp validation is fake when cached (W-M1, I-M8): sudo -S -v without -k accepts any password while credentials are cached; both duplicated code paths share the bug.
  4. Job history is per-instance-locked only (P-M7, S-M8, T-M10): Store mutexes don't span processes; fixed .tmp name enables lost records between Web and TUI.
  5. Health/relaunch readiness declared too early (C-1, H-3): one fix — terminal preservation + post-render acknowledgement — addresses both.
  6. Release-tooling trust gaps (H-21, H-22, U-M9/U-M12, B-M4, B-M6): signing re-downloaded bytes, mutable image tags, unbounded generator downloads, unreviewed upstream auto-promotion.
  7. Quote-unaware line parsers (P-M3, P-M8, S-M4): mpv.conf, input.conf, and script-opts each split on # without tracking quotes — three independent implementations of the same bug.

7. Test-coverage pattern

All findings are latent (suite green, vet clean). The reviews consistently traced why: the highest-risk seams are precisely the untested ones — stored-password double dispatch, SSE reconnect reconciliation, cancel/retry overlap, stream channel races, terminal descriptor inheritance, quoted-# round-trips, b3sum -c compatibility (cmd/gen-checksums has no tests), cross-process history, and any browser-level E2E for jobs. Several existing tests encode the defective behavior (backup-failure continuation, glxinfo truncation, #-key acceptance, regionless country-filter leak), so fixes will need expectation changes, not just new tests.

8. Verified-clean strengths

9. Recommended remediation roadmap

P0 — before any further release work

  1. C-1 + H-3: terminal-preserving relaunch + post-render health acknowledgement, with a PTY integration test (render, ack-after-first-frame, rollback on pre-frame failure).
  2. H-1, H-2, H-4: hash-before-exec at every identity probe; platform-atomic replacement that never unlinks the live primary; honest rollback when backup evidence is missing.
  3. H-6: manifest-owned Windows uninstall; refuse unmanaged directories; fail on incomplete cleanup.
  4. H-8, H-20 (+ theme 1): fail-closed, staged, atomic config reset/restore with unique fsynced backups.

P1 — correctness of core flows

  1. Theme 2 (H-11, H-12, H-18, S-M1): worker-acknowledged cancellation; root lifecycle context + bounded wait on shutdown.
  2. H-10: context-bounded package probes; labeled timeout; lock-free cache rebuild.
  3. H-13–H-17: TUI stream protocol, per-app destinations, dedicated UI-change op, backup-without-removal, filter-state guards.
  4. H-7, H-9: FFmpeg staged replacement; IINA mount-bound copy.
  5. Theme 3 (sudo -k), theme 4 (cross-process history lock), locale cache race (W-M4).

P2 — release/pipeline trust

  1. H-21, H-22, H-23: sign pipeline-local artifacts; protected-tag + duplicate-preflight + serialized publication; native gates or recorded approval.
  2. U-M1–U-M6, U-M11/U-M12, B-M1–B-M8: durability barriers, forgeable-DTO removal, anti-replay/revocation design, b3sum -c-compatible checksums, notices in archives, pinned digests, browser E2E.

P3 — quality/consistency

  1. Themes 5–7 remainder, all Low/Info items, docs reconciliation (AGENTS.md line-count table materially stale; several docs claims refuted — see the claims matrix in the build/CI artifact).

10. Per-area artifacts

AreaArtifactCounts
Web security/correctness.opencode/reviews/web-security-correctness-review.md2H/5M/6L/1I
Installer.opencode/reviews/installer-deep-review.md4H/8M/4L/1I
Self-update/release stack.opencode/reviews/self-update-release-version-stack.md5H/12M/10L/1I
TUI.opencode/reviews/tui-deep-correctness.md1C/9H/15M/6L/1I
Core utilities.opencode/reviews/core-utility-packages.md1H/9M/5L
Web frontend.opencode/reviews/web-frontend-review.md6M/4L
Supporting packages + main.opencode/reviews/supporting-packages-main-entrypoint.md2H/10M/11L
Build/CI/deps/docs.opencode/reviews/build-ci-dependency-docs-review.md3H/8M/3L/1I