When a team ships a desktop app built with JavaScript or TypeScript, it often ships more than just the user interface. The bundle may contain business rules, licensing checks, configuration, offline workflows, and integration logic.

In a typical web application, much of that logic can stay on the server. In a desktop app, at least part of it often ends up on the user’s machine, where it can be inspected, patched, or repackaged.

In this article, I want to look at why this problem is hard to avoid, what “source code protection” actually means in practice, and how Electron, Tauri, and MōBrowser approach it today.

Why source code protection matters 

For commercial desktop software, the concern is usually not limited to someone reading a few source files out of curiosity. Teams worry about several things at once:

  • exposure of proprietary logic
  • analysis of licensing and validation flows
  • tampering with application behavior
  • repackaging of modified builds
  • extraction of configuration or integration details

JavaScript makes this concern sharper, because shipped code is often much closer to the original source than native machine code. That does not mean native apps are immune to reverse engineering. It means JavaScript desktop apps often start from a weaker position if the goal is to hide implementation details.

Before comparing frameworks, it helps to separate several very different ideas that are often grouped together under the phrase “source code protection”.

What “source code protection” means 

In practice, teams usually need some combination of five things:

  1. Packaging. Put application files into a bundle or archive.
  2. Obfuscation. Make code harder to read.
  3. Transformation. Use bytecode or encryption-like mechanisms to make casual extraction harder.
  4. Integrity. Detect that packaged files have been modified.
  5. Architecture. Keep the most sensitive logic out of exposed JavaScript in the first place.

These layers solve different problems. Packaging is not the same as protection. Code signing is not the same as encryption. Obfuscation is not the same as tamper resistance. When these ideas get mixed together, teams often think they have stronger protection than they really do.

Diagram showing the layers of source code protection in desktop apps

Protection layers solve different problems.

Why this is hard 

There is a structural reason why this problem never fully goes away. The application must run on the user’s machine, and the runtime must eventually get some usable form of the code or assets. A determined attacker can study files on disk, instrument the runtime, inspect memory, or patch the app binary.

That is why strong, absolute secrecy is the wrong target for most client-side JavaScript applications. A more realistic target is to raise the cost of inspection and tampering, reduce the number of easy attack paths, and move the most sensitive logic somewhere less exposed.

This is also why “just obfuscate it” is rarely a satisfying answer. Obfuscation can help against casual inspection, but it does not change the fact that the code still has to execute locally.

Source code protection in Electron 

Electron makes the problem easy to see, because its default packaging format, ASAR, is explicitly about packaging, not secrecy. The archive format is public, standard tools can extract it, and JavaScript files remain readable after extraction.

That does not mean Electron developers have no defensive tools. In practice, teams layer several mechanisms:

  • ASAR packaging to bundle files into app.asar.
  • V8 bytecode to make parts of the JavaScript code harder to read.
  • ASAR integrity to detect modification of the packaged archive at runtime.
  • Code signing so operating systems can verify the app’s origin.

These mechanisms help, but each solves a narrower problem than the phrase “source code protection” suggests.

Bytecode is the closest thing to code hiding in the common Electron toolchain. For example, electron-vite can compile the main process and preload scripts to V8 bytecode in production. At the same time, its own documentation is careful about the limits: the feature does not cover everything, it has compatibility constraints, and sensitive strings need additional handling.

ASAR integrity solves a different problem. Electron can validate the integrity metadata of app.asar at runtime and terminate the app when validation fails. That is useful for tamper detection, but it does not make the code unreadable.

Code signing solves yet another problem. It helps operating systems and users trust that the distributed binary came from the expected publisher. It does not hide JavaScript source code inside the bundle.

So the practical Electron answer today is layered hardening, not a single built-in protection model. It can raise the bar, especially for the main and preload parts of the app, but it does not turn shipped JavaScript into a sealed artifact.

Source code protection in Tauri 

Tauri approaches the problem from a different direction. Instead of shipping a bundled Chromium runtime, it relies on the system WebView and focuses its security model on trust boundaries between the frontend and the privileged core.

That security model has several important pieces:

  • the frontend runs in the WebView and is treated as a less trusted boundary
  • access to privileged commands is controlled through capabilities
  • communication goes through an IPC layer based on message passing
  • Content Security Policy can reduce the impact of script injection and unwanted resource loading.

This is a meaningful security posture, but it answers a slightly different question. Tauri’s public security guidance is mainly about restricting what the frontend is allowed to do and keeping privileged logic in Rust. It does not present frontend source code hiding as a built-in protection feature.

That distinction matters. If your sensitive logic can move from the web layer into Rust commands, Tauri gives you a clearer boundary. If your product still ships a significant amount of valuable frontend logic as web assets, that frontend code remains a client-side artifact and should not be treated as secret just because the overall framework has a strong security story.

In other words, Tauri’s strongest answer is architectural. It helps most when teams can reduce how much high-value logic remains in the frontend at all.

App integrity in MōBrowser 

During the build, MōBrowser packs compiled application sources and assets into a single app.bin file. At runtime, the application reads these files from app.bin in memory. Any code shipped to the user’s machine can ultimately be inspected, so secrets and sensitive logic should stay on a server.

MōBrowser verifies app.bin before loading any sources. A signed application on macOS or Windows refuses to start if the file has been modified after signing. Linux and unsigned builds use a SHA-256 checksum that detects corruption, but not deliberate modification.

MōBrowser also gives teams another architectural option. If some part of the logic is too sensitive or too platform-specific to leave in TypeScript, the app can use a native module written in C++ or Rust and shipped in compiled form. This moves selected logic out of JavaScript, but compiled code can still be reverse engineered.

Files that must remain accessible on the file system go in the resources directory, outside app.bin. The macOS bundle signature covers these files, but the MōBrowser runtime does not verify them on Windows or Linux. Do not store secrets or sensitive logic anywhere in the application bundle.

What developers should pay attention to 

The framework choice matters, but architecture still matters more. A few rules stay true across all three options.

Do not treat frontend JavaScript as a safe place for secrets. API keys, licensing decisions, anti-tamper rules, and other high-value checks should stay on a backend or move into a less exposed boundary whenever possible.

Do not confuse packaging with protection. A single archive may make deployment cleaner, but it does not automatically make the contents secret.

Do not confuse signing with secrecy. Signing is still important, because it helps with trust, distribution, and integrity, but it does not make extracted code unreadable.

Use the framework’s actual strength. In Electron, that usually means combining hardening techniques instead of relying on ASAR. In Tauri, it means taking the trust boundary seriously and moving sensitive logic into Rust. In MōBrowser, it means signing applications distributed on macOS and Windows so the runtime can verify their integrity.

Most important, decide what you are defending against. Casual inspection, competitor curiosity, patching by power users, and determined reverse engineering are not the same threat model. A tool that is reasonable for the first two may be weak for the last one.

Conclusion 

Source code protection in JavaScript desktop apps is not a yes-or-no property. It is a mix of packaging, obscurity, integrity, and architecture.

Electron gives you a flexible ecosystem and several hardening techniques, but you have to compose them yourself, and ASAR alone is not protection. Tauri improves the security story mainly through trust boundaries and Rust-side privileged logic, not through built-in protection of shipped frontend assets. MōBrowser packages sources and assets into app.bin and verifies the file before loading it.

That still does not change the deepest constraint: code that must execute on the client can only be protected up to a point. The practical question is how much effort extraction and tampering should require, and how much sensitive logic you can avoid shipping as JavaScript at all.