Tauri · Desktop distribution

Your Tauri binary embeds the frontend. Embedded is not the same as protected.

Tauri produces a small native executable with your web assets inside it, and that packaging is genuinely tidier than an Electron archive. It is still packaging. The webview has to receive your JavaScript as text in order to run it, so the bundle comes back out. What makes Tauri different — and better — is the other half: a Rust command layer that is real compiled code, and therefore a real place to put the logic you did not want to ship in the first place.

The Short Version

Move it, then protect what is left

Tauri is the one popular desktop stack where “move the logic somewhere it cannot be read” is actually available to you.

Rust is the vaultCommands compile to machine code. Secrets, licensing and rules belong there.
The webview is a shop windowEverything in it is readable, including the list of commands it calls.
Protect the dist folderOne step between your web build and the Tauri bundle, no Rust changes.
What You Actually Shipped

A single executable feels opaque, and that feeling is the problem

The appeal of Tauri over Electron is obvious the first time you look at the output: a handful of megabytes instead of a hundred and fifty, one binary instead of a directory tree, no bundled runtime. It looks compiled, and people reason about it as though it were. But the executable contains two very different kinds of content. The Rust half genuinely is compiled — optimised machine code with no source text and, in a release build, not much in the way of names either. The frontend half is your dist directory, stored as data, handed to the system webview as text at startup because a webview cannot execute anything else.

Assets are stored, not transformed

Your HTML, CSS and JavaScript are embedded in the binary essentially as they came out of the bundler. Recovering them is a matter of knowing where to look, and the tooling for looking inside executables is old, free and widely understood.

The developer tools are the easy route

Nobody serious starts with the binary. If the packaged build allows the inspector to open, the whole frontend is browsable with syntax highlighting and a debugger attached. Turning the inspector off in release builds is worth doing and is not a barrier to anyone determined.

It is a nicer container, not a different category

Compared with an Electron asar archive, which unpacks with one documented command, a Tauri binary raises the effort a little. Both are containers for readable text. Neither is a protection layer, and treating either as one is the mistake this page exists to prevent.

The Real Advantage

Tauri hands you something Electron cannot: a compiled place to put things

Almost every page on this site eventually says the same thing — move authority to somewhere the user does not control, and protect what has to remain in reach. On the web that somewhere is your server, which means a network round trip and an online requirement. In Electron there is no such place at all, because the main process is JavaScript too. Tauri is the interesting exception: the Rust side is on the customer’s machine, works offline, and is still compiled code. That combination is rare enough to be worth designing around.

What belongs in a command

Licence validation and entitlement state. Any credential, token or signing key, and every request that carries one. Proprietary algorithms, pricing, scoring and matching rules. File system, database and native API access. The authoritative copy of any validation your interface also performs. The rule of thumb: the frontend should ask for an outcome, not compute one and report it.

Commands are an API, so treat them like one

Anything running in the webview can invoke any command your capability configuration permits, with any arguments. A modified frontend, an injected script, or a developer with the inspector open all reach the same surface. Validate every argument in Rust, authorise the action rather than assuming the caller is your interface, and keep the permitted set as small as the application genuinely needs.

Do not smuggle secrets through the boundary

A common half-measure is to keep the key in Rust but hand it to the frontend when it is needed. That defeats the exercise: the value now exists in the webview, in memory a debugger can read, and often in a variable a modified build can log. Send the result of the operation across the boundary, never the material that performed it.

Offline licensing is a Rust problem

Desktop products usually need entitlement checks that survive without a network. Implementing that check in the frontend gives you something a reader can find and edit; implementing it in Rust gives you something that has to be patched in a binary. This is the single highest-value relocation for most commercial Tauri apps.

What Stays In The Webview

You cannot move it all, and the remainder is worth protecting

Relocating logic to Rust has costs — a serialisation boundary, an async hop, and a rewrite in a different language. In practice teams move the things that clearly warrant it and leave a substantial application in the webview. That remainder is where a protection step earns its place, and there is a second reason beyond the obvious one.

The interface logic itself

Workflow state machines, offline queueing and conflict handling, layout and rendering work that took years to get right, integration glue for third-party services. None of it belongs in Rust, and all of it is a readable description of how your product works.

The map of your Rust surface

This is the underrated one. An unprotected bundle lists every command name your backend exposes, the shape of each argument, and the events it emits — a complete, accurate guide to driving your native layer, written by you. Name mangling and string handling do not remove that surface, but they stop it reading like documentation.

Tamper resistance for what remains

Client-side gating that exists for responsiveness is still worth making awkward to edit, and runtime defense can route tamper signals into your monitoring. The caveat is the usual one: this raises cost rather than settling the question, which is why the authoritative check lives in Rust.

Build Integration

One step, between your web build and the Tauri bundle

Tauri embeds whatever is in your frontend distribution directory at bundle time. That makes the integration point unusually clean: protect the directory after your web build writes it and before Tauri reads it, and the Rust project never needs to know the step exists.

Order of operations
1. vite build / webpack / rollup      -> writes dist/
2. jso-protector on dist/**/*.js      -> protects in place
3. remove any *.map from dist/        -> do not package source maps
4. tauri build                        -> embeds dist/ into the binary

Hook it where Tauri already calls out

Tauri runs a configured command to produce the frontend before bundling. Extending that command, or the npm script it points at, is the least invasive place to add the protection pass — it then runs for every packaged build and cannot be forgotten on a release day.

Keep source maps out of the package

A map file embedded next to the protected bundle undoes the whole exercise, and in a desktop app it ships to the customer permanently rather than sitting on a server you can clean up. Generate maps, archive them where you can symbolicate crash reports, and delete them from the packaged directory. See Symbolication.

Protect the built output, not the sources

Run the protection pass over the bundler’s output rather than your component files. The bundler needs to see real module structure to tree-shake and split correctly, and protecting first fights that. This is the same ordering rule that applies to every framework build, described in build integration.

Pin a seed for reproducible packages

Desktop releases get rebuilt for support reasons long after the fact. A fixed seed makes the protected output deterministic for a given input, so a rebuilt package can be compared against the one you shipped. See Reproducible Builds.

Two Locks That Behave Differently Here

Runtime guards were written for browsers, and a webview is not quite one

Two of the runtime locks make assumptions that hold on the open web and become misleading inside a packaged desktop application. Both are safe to use once you know what they read; both cause confusing support tickets if you enable them by reflex.

Domain locking is inert in a Tauri window

The guard reads location.hostname from the document it is running in. A Tauri window loads your assets over a local custom protocol, not from your web domain, so the hostname it sees is a framework-supplied constant that is identical on every installation. Allowing it authorises nothing about a particular customer. If a second window loads remote content, the guard applies there in the normal way. For desktop entitlement, use a Rust-side check.

Browser and OS locks can fail closed on some platforms

Both guards classify the user agent into a fixed set of names — chrome, edge, firefox, safari, opera, ie — and anything unmatched becomes unknown, which is never in an allowlist and so triggers the failure action. A Tauri window runs the host system webview, which is a different engine on each platform, so one build can classify differently across your targets. The OS guard also fires when the user agent and the reported platform disagree. Test a real packaged build on every platform you ship rather than predicting the strings.

Frequently Asked

Tauri protection, answered

Does compiling a Tauri app into a single binary hide the JavaScript?

No. The frontend assets are embedded in the executable, and embedding is a packaging decision rather than an encryption one. The bundle has to be handed to the webview as text for it to run, so the HTML, CSS and JavaScript are recoverable from the binary with ordinary inspection tools, and simpler still by opening the developer tools in a build that has not disabled them. A Tauri binary is a more inconvenient container than an Electron asar archive, not a fundamentally different one.

Is a Tauri app more secure than an Electron app?

For the code you can move into Rust, meaningfully yes, and that is the real difference. Electron gives you JavaScript on both sides of the process boundary, so relocating logic to the main process moves it out of the page but keeps it readable. Tauri gives you a native compiled backend, so logic moved into a Rust command becomes machine code with no source text to recover. The frontend half of a Tauri app is exactly as exposed as any other web frontend.

What should live in a Rust command instead of the frontend?

Anything whose correctness or secrecy matters. Licence and entitlement checks, credential handling and any calls that carry a key, proprietary algorithms and pricing or scoring rules, file system and database access, and the authoritative version of any validation the interface also performs for responsiveness. The frontend should ask for outcomes rather than compute them. Treat every argument arriving from the webview as untrusted input and validate it in Rust, because a modified frontend can call any command your capability configuration permits.

If the important logic is in Rust, is there any point protecting the frontend?

Usually yes, for two reasons. Most applications cannot move everything: interface state machines, workflow rules, offline behaviour and integration glue stay in the webview because that is where they belong. And the frontend is the map of your backend, since a readable bundle enumerates every command name, argument shape and event your Rust side exposes, which is the fastest way for someone to learn how to drive it. Protection raises the cost of both reading the remaining logic and enumerating that surface.

Does domain locking work inside a Tauri webview?

Not in any useful sense, and it is worth understanding before you enable it. The guard reads location.hostname from the document it runs in, and a Tauri window loads your assets from a local custom protocol rather than from your web domain. Whatever hostname it sees is the same on every installation, so allowing it identifies the framework rather than authorising a customer. If your build also loads remote content in a second window, the guard applies there normally. For desktop distribution, entitlement checks in Rust are the control that fits.

Will the browser and operating system locks behave correctly in a Tauri build?

Verify them on every platform you ship before turning them on. Both guards classify the user agent string into a fixed set of names, and anything unmatched classifies as unknown, which is never in an allowlist and therefore triggers the failure action. A Tauri window runs the host system webview, which differs by platform, so the same build can classify one way on Windows and another elsewhere. The operating system guard additionally fires when the user agent and the reported platform disagree. Test a real packaged build per target rather than reasoning about the strings.

How do I add a protection step to a Tauri build?

Protect the frontend build output before Tauri packages it. Your web build writes a dist directory, Tauri embeds whatever that directory contains, so a protection pass over dist between those two steps needs no change to the Rust project at all. In practice that means running the CLI against the built assets in the script that Tauri invokes, or as a discrete CI step ordered before the bundle command. Keep source maps out of the packaged directory.

Start Now

Open your own release build and read the frontend

Take the packaged binary you last shipped, recover the embedded assets, and read them the way a customer could. Then list which of those files contain a rule you would rather not publish, and decide for each one whether it moves to Rust or gets protected in place. That list is the whole plan.

Related Guides

Protecting other JavaScript targets

Same protection engine, different build pipeline. These guides cover the platforms closest to this one:

Electron apps · Node.js source code · Self-hosted applications · Embedded devices · React Native apps · Protecting JavaScript (overview) · Protecting WebUSB and Web Serial device code