fix(windows): remove the bundled installer and uninstaller (#466) - #481
Merged
Conversation
VirusTotal scores the shipped binary `Trojan:Win32/Wacatac.B!ml`, and #334 already identified the cause: the uninstall path writes a batch file to %TEMP%, writes a .vbs that runs it with a hidden window and then deletes itself, and launches the pair through wscript.exe. That sequence is close to the definition of the behaviour classifier that flags it. It is a false positive, but the pattern it matches is really there, so the only way to clear it is to remove the pattern. The install half was already unreachable. `get_app_mode` entered installer mode only on a filename containing `installer` or an explicit `--install`, and #355 stopped publishing any installer-named artifact, so nothing shipped could reach `install_app`, `create_shortcut`, `check_install_status` or Installer.svelte. The uninstall half was reachable, through `UninstallString = "…\Markpad.exe" --uninstall` written by custom installs from 2.6.x. Two things now cover those: * NSIS rewrites that value. `Section Install` in the bundler template writes `UninstallString` to `$INSTDIR\uninstall.exe` with no `$UpdateMode` guard anywhere in the section, so it lands on a plain install and on an updater run alike. Both sides address the same key -- the custom installer used APP_NAME and the template uses ${PRODUCTNAME}, which are both "Markpad". * A hook drops the stale entry when the two disagree on a hive. Section Install writes through SHCTX, so an old install that chose the other hive keeps its row in Add/Remove Programs. The hook matches on the `--uninstall` tail, which only the custom installer ever wrote, and deletes just that key. `forward_legacy_uninstall` is the backstop for the case both miss: a stray `--uninstall` hands off to uninstall.exe beside the binary and exits. If there is nothing to hand off to it starts normally, because a window the user did not ask for is still a better answer than a click that does nothing. Signing would not have helped. A certificate addresses SmartScreen reputation, not a behaviour classifier's score. Drops mslnk and chrono, which setup.rs was the only user of. Verified: `npm run check` 0 errors, `npm test` 728/728, `cargo test` 125/125 (down 32, the count of `#[test]` inside the deleted setup.rs). The Windows-only `forward_legacy_uninstall` is not built by a macOS `cargo check`, so it was type -checked separately against x86_64-pc-windows-msvc. The NSIS macro cannot be compiled here at all; makensis runs in the Windows build-test leg, which is where a malformed macro would surface. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 6, 2026
Closed
Closed
PathGao
added a commit
that referenced
this pull request
Aug 7, 2026
* fix(windows): remove dead installer code that survives in the binary The code that triggered Trojan:Win32/Wacatac.B!ml was removed in #481, but three things that still look like an installer to a behaviour classifier were left in the binary: - forward_legacy_uninstall() — answered --uninstall from pre-2.7 custom installs. The NSIS template writes UninstallString = uninstall.exe unconditionally, and the auto-updater passes /UPDATE which runs Section Install, so every install that has ever updated through 2.7.1 already has the corrected registry entry. The hooks.nsi macro also drops stray --uninstall entries from both hives as a backstop. - is_installer_mode detection — checked for --install or 'installer' in the exe name. No shipped artifact has triggered either since #355 stopped publishing an installer-named file, and #481 removed Installer.svelte, install_app, and the invoke_handler registrations that were the only consumers of the resulting window label. - The 'installer' window label in capabilities/default.json — window that no code can create any more. None of these do anything observable today, but a scanner that reads the file rather than tracing reachability still sees them. Removing them shrinks the binary by the 55 lines of Rust that remained from the old custom installer. Addresses the setup.exe detection reported in #466 (comment). * chore(deps): bump mermaid 11.16.0 -> 11.16.1 to fix npm audit npm audit reports 5 moderate vulnerabilities in mermaid <= 11.16.0: - GHSA-c4c3-pg64-4m4v (prototype pollution) - GHSA-6x64-9x62-f2gx (CSS injection) - GHSA-3rrr-jr9j-h3q3 (prototype pollution) - GHSA-2v8p-3f2j-5mp7 (infinite loop DoS) - GHSA-rhh3-jpg6-66xh (DoS) All fixed in 11.16.1. --------- Co-authored-by: PathGao <PathGao@users.noreply.github.com>
PathGao
added a commit
that referenced
this pull request
Aug 7, 2026
Installer.svelte was removed in #481. These keys are never referenced by any t() call outside i18n.ts itself. Removes 302 lines (27 English keys × 25 locales, minus languages that already lacked the translations). English key count drops from 335 to 308. Co-authored-by: PathGao <PathGao@users.noreply.github.com>
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #466. Implements the route you picked in #466 (comment) — strip the install/uninstall code and let NSIS own it.
What the scanner reacts to
The uninstall path in
setup.rswrote a batch file to%TEMP%, wrote a.vbsthat ran it with a hidden window and then deleted itself, and launched the pair throughwscript.exe. Hidden script → self-delete → launch an executable is close to the definition ofTrojan:Win32/Wacatac.B!ml, which scores behaviour rather than matching a signature. Each property had a reason (the batch waits on the PID rather than the image name so a portable copy survives an uninstall, #358), but the shape is what gets scored — so the only thing that clears it is removing the shape.The install half was already dead code
get_app_modeentered installer mode only on a filename containinginstalleror an explicit--install, and #355 stopped publishing any installer-named artifact. Nothing shipped could reachinstall_app,create_shortcut,check_install_statusorInstaller.svelte. Removing them changes no observable behaviour for anyone on 2.7.0.The uninstall half needed a migration — and it turned out not to need a Windows box
I asked you to verify on Windows whether the updater's
/UPDATErun still writes the registry entry. That question doesn't arise. Reading the bundler template at the version that built 2.7.0 (tauri-cli-v2.9.6):The write isn't behind a mode check at all — the
$UpdateModeguards in that file sit inSection WebView2andSection Uninstall. So it lands whether or not/UPDATEis passed.Both sides also address the same key: the custom installer used
APP_NAME(setup.rs:47) and the template uses${PRODUCTNAME}(:58), and both areMarkpad.One gap is left, and the hook closes it.
Section Installwrites throughSHCTX, so an old install that chose the other hive keeps a second Add/Remove Programs row pointing at a command the binary no longer answers.MARKPAD_DROP_LEGACY_UNINSTALL_ENTRYchecks both hives and deletes the key only when the value ends in--uninstall— a tail only the custom installer ever wrote, so it cannot match an entry this installer owns.DeleteRegKeyunder HKLM without elevation is a silent no-op, which is why there is also a backstop in the binary: a stray--uninstallhands off touninstall.exebeside it and exits, and starts normally if there is none.Also
mslnkandchrono—setup.rswas their only user.Verification
npm run check646 files 0 errors ·npm test728/728 ·cargo test125/125.The Rust count is down 32 from master, which is exactly the number of
#[test]inside the deletedsetup.rs— the tests went with the code they covered, and nothing else moved.What I could not verify here, stated plainly:
forward_legacy_uninstallis#[cfg(target_os = "windows")], so a macOScargo checkdoes not compile it. I type-checked it separately againstx86_64-pc-windows-msvc. It has not been run.makensisruns in the Windowsbuild-testleg, so a malformed macro fails there — please treat that leg as the real check on the hook.🤖 Generated with Claude Code