<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Luan Silveira</title>
    <description>The latest articles on DEV Community by Luan Silveira (@luanmacea).</description>
    <link>https://dev.to/luanmacea</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4152736%2F6ff52d91-b4ea-4553-ba74-0dbaff581cca.jpg</url>
      <title>DEV Community: Luan Silveira</title>
      <link>https://dev.to/luanmacea</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/luanmacea"/>
    <language>en</language>
    <item>
      <title>What feature would you want in a Roblox account manager?</title>
      <dc:creator>Luan Silveira</dc:creator>
      <pubDate>Fri, 09 Oct 2026 04:45:16 +0000</pubDate>
      <link>https://dev.to/luanmacea/what-feature-would-you-want-in-a-roblox-account-manager-6al</link>
      <guid>https://dev.to/luanmacea/what-feature-would-you-want-in-a-roblox-account-manager-6al</guid>
      <description>&lt;p&gt;I maintain &lt;a href="https://multialt.pages.dev" rel="noopener noreferrer"&gt;MultiAlt&lt;/a&gt; (&lt;a href="https://github.com/luanmacea/MultiAlt" rel="noopener noreferrer"&gt;source on GitHub&lt;/a&gt;), a free, open-source (GPL-3.0) Windows app that keeps several Roblox accounts signed in and opens them side by side. It started as a rewrite of the Roblox Account Manager by ic3w0lf22, which was archived in October 2024 after about 2.2 million downloads. MultiAlt is built with Tauri 2 (Rust and React/TypeScript).&lt;/p&gt;

&lt;p&gt;I'm deciding what to build next, and I'd like that list to come from the people who use these tools, not from me guessing.&lt;/p&gt;

&lt;p&gt;If you run more than one Roblox account (to farm, trade, test your own game, or play with friends on a second account):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;What feature would you want in an account manager that no tool gives you today?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;What did the old Roblox Account Manager do that you still miss?&lt;/li&gt;
&lt;li&gt;What's the most annoying part of juggling accounts right now?&lt;/li&gt;
&lt;li&gt;What do you use your extra accounts for?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One line is enough. I read every answer, here or in the &lt;a href="https://github.com/luanmacea/MultiAlt/discussions/21" rel="noopener noreferrer"&gt;GitHub discussion&lt;/a&gt;, where 👍 votes decide what goes on the roadmap.&lt;/p&gt;

&lt;p&gt;What's already planned: putting many accounts into a Roblox group at once, and a FAQ inside the app. One rule stays fixed: no cheats, no exploits, nothing that breaks Roblox's rules.&lt;/p&gt;

&lt;p&gt;You can also drop ideas in the &lt;a href="https://discord.gg/NPvyWgKbfH" rel="noopener noreferrer"&gt;MultiAlt Discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Download and docs: &lt;a href="https://multialt.pages.dev" rel="noopener noreferrer"&gt;multialt.pages.dev&lt;/a&gt;. Not affiliated with or endorsed by Roblox Corporation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>opensource</category>
      <category>gamedev</category>
      <category>roblox</category>
    </item>
    <item>
      <title>Keeping an archived 800-star Roblox tool alive: MultiAlt, in Rust and Tauri</title>
      <dc:creator>Luan Silveira</dc:creator>
      <pubDate>Mon, 05 Oct 2026 09:30:48 +0000</pubDate>
      <link>https://dev.to/luanmacea/keeping-an-archived-800-star-roblox-tool-alive-multialt-in-rust-and-tauri-30g</link>
      <guid>https://dev.to/luanmacea/keeping-an-archived-800-star-roblox-tool-alive-multialt-in-rust-and-tauri-30g</guid>
      <description>&lt;p&gt;The original &lt;strong&gt;Roblox Account Manager&lt;/strong&gt; by &lt;a href="https://github.com/ic3w0lf22/Roblox-Account-Manager" rel="noopener noreferrer"&gt;ic3w0lf22&lt;/a&gt; helped millions of players run several Roblox accounts on one PC: its last release has over 2 million downloads. In October 2024 the repository was archived.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://multialt.pages.dev" rel="noopener noreferrer"&gt;MultiAlt&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/luanmacea/MultiAlt" rel="noopener noreferrer"&gt;source on GitHub&lt;/a&gt;) continues that work, now in Rust + TypeScript with Tauri, and it's actively maintained.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7whf58edlhs1er35b4zd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7whf58edlhs1er35b4zd.png" alt="MultiAlt: choose a game and join with every selected account" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep every Roblox account signed in, in one list&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi Roblox&lt;/strong&gt;: open several Roblox clients at once&lt;/li&gt;
&lt;li&gt;Send a group of accounts into the &lt;strong&gt;same server&lt;/strong&gt;, or follow a friend into theirs&lt;/li&gt;
&lt;li&gt;Keep alts in game with &lt;strong&gt;AFK mode&lt;/strong&gt; and &lt;strong&gt;Auto Rejoin&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Import the old Roblox Account Manager account file&lt;/li&gt;
&lt;li&gt;English, Portuguese and Spanish UI&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changed under the hood
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encrypted storage by default.&lt;/strong&gt; Accounts are encrypted with your password, or with a device key kept next to the data file, so copying the file alone exposes nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No admin, auto-update.&lt;/strong&gt; A per-user MSI installer that updates itself; it scans 0/75 on VirusTotal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi Roblox that doesn't kill other clients.&lt;/strong&gt; Holding Roblox's singleton mutex is opt-in, and nothing in the launch flow closes other accounts' windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A server finder that respects groups.&lt;/strong&gt; "Best fit" picks the fullest server where the whole group still fits, with one spare seat, and asks the API for the right sort order instead of re-sorting a single page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three languages&lt;/strong&gt;, because Roblox has a huge audience in Brazil and Latin America.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Things I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Antivirus models are picky.&lt;/strong&gt; At one point a single crypto dependency was enough for Windows Defender's machine-learning engine to flag the build. I found it by bisecting builds against VirusTotal, and now every release is scanned with VirusTotal and Defender before it ships.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roblox picks its build from the launch URL, not only the registry.&lt;/strong&gt; Opening a client build that doesn't match the channel in the launch URL makes Roblox's own installer pop up, and that installer closes every open client. Getting this right took a lot of reading the client's logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never close what you didn't open.&lt;/strong&gt; Closing the manager used to close every Roblox window when more than one was open, including accounts opened from the website. Now closing the app leaves every client running.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's free and GPL-3.0.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download and guides: &lt;a href="https://multialt.pages.dev/" rel="noopener noreferrer"&gt;https://multialt.pages.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/luanmacea/MultiAlt" rel="noopener noreferrer"&gt;https://github.com/luanmacea/MultiAlt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Video walkthroughs: &lt;a href="https://www.youtube.com/@MultiAltApp" rel="noopener noreferrer"&gt;https://www.youtube.com/@MultiAltApp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Community and help: &lt;a href="https://discord.gg/NPvyWgKbfH" rel="noopener noreferrer"&gt;https://discord.gg/NPvyWgKbfH&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback and issues are very welcome.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not affiliated with or endorsed by Roblox Corporation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>tauri</category>
      <category>opensource</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>How a crypto library got my Tauri app flagged as a trojan (and how I found it by bisection)</title>
      <dc:creator>Luan Silveira</dc:creator>
      <pubDate>Wed, 30 Sep 2026 16:57:50 +0000</pubDate>
      <link>https://dev.to/luanmacea/how-a-crypto-library-got-my-tauri-app-flagged-as-a-trojan-and-how-i-found-it-by-bisection-11fl</link>
      <guid>https://dev.to/luanmacea/how-a-crypto-library-got-my-tauri-app-flagged-as-a-trojan-and-how-i-found-it-by-bisection-11fl</guid>
      <description>&lt;p&gt;My desktop app started getting flagged by Microsoft as &lt;code&gt;Trojan:Win32/Wacatac.B!ml&lt;/code&gt;. The code hadn't learned any new tricks. The culprit turned out to be the encryption library I had just added, and I found it by bisecting builds against the VirusTotal API.&lt;/p&gt;

&lt;p&gt;This post covers how I tracked it down, how I replaced the library without breaking the files users already had on disk, and what I'd do differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/luanmacea/roblox-account-manager" rel="noopener noreferrer"&gt;RAM — Roblox Account Manager&lt;/a&gt; is an open-source (GPL-3.0) Windows app built with &lt;strong&gt;Rust + Tauri 2&lt;/strong&gt; and React. It manages many Roblox accounts, launches several game clients at once and automates things like rejoining servers.&lt;/p&gt;

&lt;p&gt;That last part matters. An app that stores session cookies, spawns processes, brings windows to the front and sends synthetic input already &lt;em&gt;looks&lt;/em&gt; suspicious to a heuristic engine. There is no margin for anything else to look off.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;After one release, Windows started warning users about the &lt;code&gt;.exe&lt;/code&gt;, and VirusTotal showed &lt;strong&gt;1/75&lt;/strong&gt;: Microsoft, &lt;code&gt;Trojan:Win32/Wacatac.B!ml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;!ml&lt;/code&gt; suffix is the important part: it's a &lt;strong&gt;machine-learning verdict&lt;/strong&gt;, not a signature match. No known malware pattern was found. A model just decided the binary resembles malware.&lt;/p&gt;

&lt;h2&gt;
  
  
  First guess: wrong
&lt;/h2&gt;

&lt;p&gt;My first instinct was to compare the last clean build with the flagged one. I dumped both with &lt;a href="https://github.com/erocarrera/pefile" rel="noopener noreferrer"&gt;&lt;code&gt;pefile&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same 16 "heuristically heavy" imports (&lt;code&gt;SendInput&lt;/code&gt;, &lt;code&gt;SetForegroundWindow&lt;/code&gt;, &lt;code&gt;TerminateProcess&lt;/code&gt;, &lt;code&gt;DuplicateHandle&lt;/code&gt;, &lt;code&gt;RegSetValueExW&lt;/code&gt;…)&lt;/li&gt;
&lt;li&gt;same section entropy, same linker&lt;/li&gt;
&lt;li&gt;only four new imports, all boring (&lt;code&gt;FlushFileBuffers&lt;/code&gt;, &lt;code&gt;OpenEventW&lt;/code&gt;, &lt;code&gt;RemoveDirectoryW&lt;/code&gt;, &lt;code&gt;MessageBoxW&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conclusion at the time: &lt;em&gt;"the behavior profile is identical, the model just changed its mind, nothing to fix."&lt;/em&gt; It sounded reasonable, and it was wrong. The import table isn't everything the model looks at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bisecting against VirusTotal
&lt;/h2&gt;

&lt;p&gt;So I stopped guessing and bisected. The VirusTotal API is free (4 requests/minute, 500/day). A small script hashes the file, looks up an existing report and uploads it only if the hash is new:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CryptoHasher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/files/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// already analyzed: read last_analysis_stats&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// upload, then poll /analyses/{id} until "completed"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then plain &lt;code&gt;git bisect&lt;/code&gt;: build the release &lt;code&gt;.exe&lt;/code&gt; at each step, scan it, mark it &lt;code&gt;good&lt;/code&gt; (0/75) or &lt;code&gt;bad&lt;/code&gt; (1/75). Each step takes a few minutes, mostly the Rust release build.&lt;/p&gt;

&lt;p&gt;(A tip: my first attempt bisected &lt;em&gt;by date&lt;/em&gt; and broke on merge commits. Use real &lt;code&gt;git bisect&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;The first bad commit was the one that &lt;strong&gt;integrated encryption for the account file&lt;/strong&gt;, using &lt;a href="https://crates.io/crates/sodiumoxide" rel="noopener noreferrer"&gt;&lt;code&gt;sodiumoxide&lt;/code&gt;&lt;/a&gt;, the Rust bindings to &lt;strong&gt;libsodium&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why libsodium?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sodiumoxide&lt;/code&gt; statically links the libsodium &lt;strong&gt;C library&lt;/strong&gt; into your binary. A blob of optimized native crypto primitives, sitting inside an app that also stores credentials and injects input, is apparently close enough to what ransomware and stealers ship for the model to fire.&lt;/p&gt;

&lt;p&gt;Nothing is wrong with libsodium itself. It's the &lt;em&gt;combination&lt;/em&gt; the classifier learned to dislike.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replacing it without locking anyone out
&lt;/h2&gt;

&lt;p&gt;Users already had encrypted files on disk, and some had a password on them. The replacement had to read those files &lt;strong&gt;byte for byte&lt;/strong&gt;. I went with pure-Rust &lt;a href="https://github.com/RustCrypto" rel="noopener noreferrer"&gt;RustCrypto&lt;/a&gt; crates and matched every libsodium parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;argon2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.6"&lt;/span&gt;
&lt;span class="py"&gt;crypto_secretbox&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;  &lt;span class="c"&gt;# XSalsa20-Poly1305&lt;/span&gt;
&lt;span class="py"&gt;sha2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.10"&lt;/span&gt;             &lt;span class="c"&gt;# SHA-512 of the password, as before&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;/// Same as libsodium's crypto_pwhash (argon2i13, OPSLIMIT/MEMLIMIT_MODERATE):&lt;/span&gt;
&lt;span class="cd"&gt;/// Argon2i v0x13, t_cost = 6, m_cost = 128 MiB, parallelism 1, 32-byte output.&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CryptoError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;131072&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;CryptoError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InvalidData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;argon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Argon2&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Algorithm&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Argon2i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;V0x13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;argon&lt;/span&gt;&lt;span class="nf"&gt;.hash_password_into&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;CryptoError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InvalidPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;crypto_secretbox&lt;/code&gt; from RustCrypto already produces libsodium's layout: the 16-byte Poly1305 MAC in &lt;strong&gt;front&lt;/strong&gt; of the ciphertext.&lt;/p&gt;

&lt;p&gt;What made me trust the swap was a test. Before removing libsodium, I encrypted a tiny payload with the &lt;strong&gt;old implementation&lt;/strong&gt;, pasted the bytes into the test as hex, and the new code must decrypt them exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;decrypts_a_libsodium_fixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Bytes produced by the OLD libsodium/sodiumoxide implementation.&lt;/span&gt;
    &lt;span class="c1"&gt;// If this fails, existing users' files would be locked. Never relax it.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fixture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hex_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;concat!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"526f626c6f78204163636f756e74204d616e6167657220637265617465"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// …the rest of the header, salt, nonce, MAC and ciphertext&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hash_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test-password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fixture must decrypt"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;b"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;account&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;example&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any parameter is off by one — Argon2 version, memory cost, MAC position — this test fails. That's exactly the kind of bug that locks users out of their data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side effect: tests got 17x slower
&lt;/h2&gt;

&lt;p&gt;Pure-Rust Argon2 in a &lt;strong&gt;debug&lt;/strong&gt; build is painfully slow: &lt;strong&gt;5.4 s per derivation&lt;/strong&gt;, against &lt;strong&gt;0.3 s&lt;/strong&gt; optimized. libsodium never had this problem, because it's precompiled C.&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;cargo test&lt;/code&gt; runs in debug, the suite went from minutes to most of the CI time. The fix is one block in &lt;code&gt;Cargo.toml&lt;/code&gt;: optimize &lt;em&gt;dependencies&lt;/em&gt; even in dev builds, keep your own crate unoptimized so it still compiles fast.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[profile.dev.package."*"]&lt;/span&gt;
&lt;span class="py"&gt;opt-level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Rust suite went from &lt;strong&gt;230 s to 41 s&lt;/strong&gt;, faster than it was with libsodium.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App &lt;code&gt;.exe&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1/75 (&lt;code&gt;Wacatac.B!ml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0/75&lt;/strong&gt;, Defender clean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSI installer&lt;/td&gt;
&lt;td&gt;0/61, but required admin&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0/75&lt;/strong&gt;, per-user, no admin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NSIS setup&lt;/td&gt;
&lt;td&gt;3/71&lt;/td&gt;
&lt;td&gt;1/75 (one generic ML engine, on the packager)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No code signing, and no removing the encryption.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone shipping an unsigned desktop app
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;!ml&lt;/code&gt; verdicts are a moving target.&lt;/strong&gt; The same source built with different feature flags got different verdicts from the same model. You can't "win" once; you measure every release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VirusTotal's Microsoft engine and local Defender disagree.&lt;/strong&gt; A build passed local &lt;code&gt;MpCmdRun&lt;/code&gt; and still got flagged on VirusTotal. Check both, because users see both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bisect, don't theorize.&lt;/strong&gt; My careful import-table analysis pointed at the wrong conclusion. A few rounds of &lt;code&gt;git bisect&lt;/code&gt; pointed at the right commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make your scanner fail loudly.&lt;/strong&gt; My own scan script once reported "all clean" while the Defender step had crashed (a UTF-8 em dash in a BOM-less &lt;code&gt;.ps1&lt;/code&gt; breaks Windows PowerShell 5.1) and the VirusTotal step exited 0 on &lt;em&gt;any&lt;/em&gt; verdict. A scanner that didn't run must never count as clean. That rule now has a test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native C dependencies are a heuristic risk.&lt;/strong&gt; If a pure-Rust equivalent exists and you can prove format compatibility with a fixture, it's worth considering.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The durable fix is still code signing (there are free options for open-source projects, like SignPath Foundation). Until then, measuring every release and keeping the recommended download clean is what I can do.&lt;/p&gt;




&lt;p&gt;Download and guides: &lt;a href="https://multialt.pages.dev" rel="noopener noreferrer"&gt;multialt.pages.dev&lt;/a&gt;. Community: &lt;a href="https://discord.gg/NPvyWgKbfH" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;. The project (now called MultiAlt) is open source, and I'd love feedback on the Rust/Tauri side: &lt;a href="https://github.com/luanmacea/MultiAlt" rel="noopener noreferrer"&gt;github.com/luanmacea/MultiAlt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this post with the help of an AI assistant; the investigation, numbers and code are from the project's history.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>tauri</category>
      <category>security</category>
      <category>windows</category>
    </item>
  </channel>
</rss>
