Tauri gives a desktop app's web frontend no system access by default, but every capability, scope and plugin a team adds widens that boundary, and the Rust side is not sandboxed at all. Tauri 2 apps pair a frontend running in the operating system's WebView with a Rust core that has full access to the machine. The framework's security model rests on capabilities, permissions and command scopes enforced by a runtime authority at the IPC layer, plus a Content Security Policy, an optional isolation pattern and mandatory signing for updates. This guide explains how those controls fit together, where teams loosen them by mistake, and how to harden, test and monitor a Tauri application. It is for developers, application security engineers and reviewers, and goes deeper on Tauri than the Electron and Desktop App Security Guide, which compares the two frameworks.
This is an independent guide from NHI Mgmt Group, based on Tauri's public documentation as of November 2026. It is not sponsored or reviewed by Tauri. Product features change, so confirm settings against Tauri's current documentation.
Key takeaways
- The IPC layer is the trust boundary. Code in the WebView reaches system resources only through commands that a capability grants to a named window; plugin and core Rust code has unrestricted access.
- Capabilities merge, so keep windows separate. A window that appears in several capabilities gets the union of all their permissions, and boundaries are tied to window labels, not titles.
- Scopes are enforced by the command, not the framework. Tauri passes allow and deny scopes to each command, with deny always winning, but a custom command that ignores its scope has no protection.
- Set a strict CSP and consider the isolation pattern. CSP protection applies only when configured, and Tauri recommends the isolation pattern wherever it can be used to validate every IPC message.
- Protect the updater key like production credentials. Update signatures cannot be turned off; the private signing key and the CI pipeline that holds it decide what every installed copy will trust.
How identity and access work in Tauri
Tauri's model separates two trust groups: the Rust application core and plugins, which run with the user's full privileges, and frontend code in the system WebView, which can only use resources exposed through the IPC layer. Tauri's documentation states that it assumes the WebView is insecure. Four building blocks decide what the frontend may do.
| Building block | What it is | Where it lives | Security point |
|---|---|---|---|
| Permission | An explicit privilege: commands allowed or denied, optionally with scopes | Plugin or app permissions directory (TOML for apps); identifiers like fs:read-files or core:window:allow-set-title | Plugin default permissions are added automatically when the Tauri CLI adds a plugin: review them |
| Scope | Allow and deny rules passed to a command, such as file paths or URLs | Inside permissions; for example path = "$APPLOCALDATA/**" | Deny always supersedes allow; the command implementation must enforce it |
| Capability | Grants or denies permissions to specific windows or webviews, optionally per platform or for remote URLs | src-tauri/capabilities as JSON, JSON5 or TOML, or inline in tauri.conf.json | All files in the directory are enabled unless app.security.capabilities lists specific ones |
| Runtime authority | The core component that checks each invoke request | Tauri core | If the origin is not allowed the command is never invoked |
When the frontend invokes a command, the runtime authority checks that the calling origin and window belong to a capability that grants the command, injects any applicable scopes and only then calls the Rust function. Tauri lists what this protects against: limiting the impact of a frontend compromise, reducing accidental exposure of local interfaces and data, and reducing privilege escalation from frontend to system. It also lists what it does not protect against: malicious or insecure Rust code, scopes that are too lax, incorrect scope checks in a command, deliberate bypasses in Rust, zero-days or unpatched flaws in the system WebView, and supply chain or developer machine compromise.
Commands registered in Rust
By default, every command an app registers with the invoke handler is callable from all of its windows and webviews. Tauri documents restricting this with AppManifest::commands in build.rs so that application commands also go through the permission system. Do this for any app with more than one window or any remote content.
The WebView is not bundled
Tauri relies on the operating system's WebView (WebView2 on Windows, WKWebView on macOS and WebKitGTK on Linux) instead of shipping its own browser engine. The Tauri team's reasoning is that operating system and WebView maintainers patch faster than application developers who bundle an engine. The consequence for security teams is that WebView patching is an operating system responsibility: an unpatched Linux distribution or an old WebView2 runtime leaves every Tauri app on that device exposed, and rendering differs by platform, so test controls on each target.
Admin roles and privileged access
A Tauri app has no administrator console, but it does have privileged parties: windows with more capabilities than others, remote origins granted API access, and the people and pipelines who can change the configuration.
- Privileged and unprivileged windows: give each window a label and its own capability. A settings window that can write files should not share a capability with a window that renders user content or documentation. Tauri recommends exposing window creation only to higher-privileged windows, because boundaries depend on labels.
- Remote API access: by default only bundled code can call the API. A capability with a remote block (for example remote.urls set to a specific HTTPS origin) lets that site invoke the granted commands. Tauri warns that on Linux and Android it cannot distinguish requests from an embedded iframe and the window itself, so a remote origin effectively shares its rights with anything it frames. Keep remote capabilities minimal or avoid them.
- Developer tools: the web inspector works in debug builds and needs the devtools feature flag to work in release builds. Leave that feature off for production so users and malware on the device cannot use an inspector to call commands.
- Configuration owners: tauri.conf.json, the capabilities directory, permission files and build.rs define the security boundary. Protect them with code owner review in source control so a single pull request cannot add fs:default to every window.
Choosing patterns and options
| Decision | Option A | Option B | Choose B when |
|---|---|---|---|
| IPC pattern (app.security.pattern) | brownfield (default): frontend talks to core directly | isolation: every message passes through your isolation app in a sandboxed iframe, encrypted with AES-GCM using keys generated at each start | The frontend loads third-party scripts or has a large dependency tree; Tauri recommends isolation wherever it can be used |
| Global API object | withGlobalTauri off (default): API imported as modules | withGlobalTauri on: API injected as window.__TAURI__ | Only for simple apps without a bundler; any injected script can then find the API more easily |
| Prototype pollution hardening | freezePrototype false (default) | freezePrototype true: runs Object.freeze(Object.prototype) before frontend code | Almost always, after testing that libraries do not extend built-in prototypes |
| Asset protocol | Disabled (default) | assetProtocol.enable with a scope | Only when the app must display local files, with the narrowest scope possible |
The isolation pattern has costs: Tauri notes that on Windows external files do not load inside sandboxed iframes, so scripts are inlined at build time and ES modules will not load in the isolation app. Keep the isolation application small, with minimal dependencies, so it does not become a new supply chain risk.
Integrations, API tokens and service accounts
Desktop apps routinely hold user tokens, call APIs and rely on plugins and build pipelines with their own secrets. Each is a credential decision.
- Plugins: official plugins such as fs, http and shell ship pre-defined permissions. Grant individual commands (fs:allow-mkdir, fs:read-files with a scope) rather than broad defaults. The fs plugin includes deny scopes for the WebView data folders ($APPLOCALDATA/EBWebView on Windows, $APPLOCALDATA on Linux) because they hold sensitive WebView data and configuration; keep those denies in place.
- HTTP access: the http plugin uses scopes to filter which URLs the frontend may reach. Restrict it to your own API hosts. Pair it with a CSP connect-src that lists the same hosts.
- User tokens and API keys: do not store access tokens, refresh tokens or API keys in WebView storage, where any script running in the frontend can read them. Keep them in the Rust core, preferably in the operating system's credential store, and expose narrow commands that use the token rather than return it. Never embed a shared API key in the binary: anyone can extract it. See the Secrets Management Guide.
- Updater signing key: Tauri's updater requires a signature and this cannot be disabled. The public key goes in tauri.conf.json; the private key, generated with the tauri signer generate command, is supplied at build time through TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD (Tauri notes that .env files do not work). Lose the key and you cannot ship updates to existing installs; leak it and an attacker who controls your update endpoint can ship trusted malware. Store it in a CI secret store or HSM-backed signing service, scope it to the release workflow only, and back it up offline.
- Update endpoints: TLS is enforced for updater endpoints in production. The dangerousInsecureTransportProtocol option allows plain HTTP and should never be set in a release build. Tauri's own guidance is that all bets are off if you lose control of the manifest server, the build server or the binary hosting. See the Desktop Application Auto-Update Security Guide.
- Platform code signing: sign Windows and macOS binaries with platform certificates in addition to the updater signature. Tauri notes that keys held on hardware tokens cannot be leaked by a compromised build system, but could still be used to sign malicious releases, so restrict who and what can trigger signing.
- CI pipeline identity: the release workflow holds the updater key, platform certificates and publishing tokens. The Tensorlake npm worm showed a project's own release pipeline publishing a malicious version with valid provenance after a malicious commit reached the main branch: protect branch rights as carefully as the keys. Pin third-party actions to full commit hashes, as Tauri advises.
Logging and detection
Tauri does not produce security logs on its own, so detection comes from the build pipeline, the update service and whatever the app logs from its Rust core.
| What to watch | How | Why |
|---|---|---|
| Changes to capabilities, permissions, CSP and pattern settings | Code owner rules on src-tauri/capabilities, permissions and tauri.conf.json; a CI check that diffs granted permissions per window | Silent widening of the boundary is the most common regression |
| Dependency vulnerabilities | cargo audit and npm audit in CI, with cargo-vet or cargo crev for review (all named by Tauri) | Rust crates and npm packages run inside the trust boundary |
| Dangerous options in release builds | CI fails on devtools feature, dangerousInsecureTransportProtocol, dangerousDisableAssetCspModification or a missing csp | These undo core protections |
| Denied IPC calls | Log in Rust when a custom command rejects input or a scope check fails, without logging secrets | Repeated denials suggest frontend compromise or probing |
| Updater activity | Server logs for manifest and artifact downloads; alerts on manifest changes outside a release | A changed manifest is the first sign of distribution compromise |
| Signing key use | Audit logs from the secret store or signing service | Any signature outside a tagged release is an incident |
Metrics: number of permissions granted per window (trend down); windows sharing a capability with a remote origin (target zero); release builds passing the dangerous-option check (target 100%); median days to ship a Tauri or dependency security fix; share of release artefacts with both platform and updater signatures verified in CI.
| Threat | ATT&CK | Tauri control |
|---|---|---|
| Injected script in the WebView calls system commands | T1059.007 JavaScript | Strict CSP, capabilities per window, isolation pattern, freezePrototype |
| Malicious dependency or compromised release pipeline | T1195.002 Compromise Software Supply Chain | Dependency auditing, pinned actions, signed commits, protected branches, isolation pattern for frontend dependencies |
| Tokens read from app storage | T1555 Credentials from Password Stores | Keep tokens in the Rust core and OS credential store; deny scopes over WebView data folders |
Hardening baseline
- Inventory the boundary. List every window label, capability, permission and scope, plus every registered Rust command. Generate the JSON schemas with tauri-build so reviewers see the full permission set.
- Restrict commands. Use AppManifest::commands so app commands require permissions, and remove plugin default permissions you do not need.
- Split capabilities by window and platform. One capability per window class, with the platforms array to keep mobile-only or desktop-only permissions where they belong. List enabled capabilities explicitly in app.security.capabilities so stray files are not picked up.
- Write tight scopes and enforce them. Allow only the folders and URLs needed, add deny scopes for sensitive paths, and review custom command code for scope bypasses such as path traversal or symbolic links; Tauri says scope validation should be audited.
- Set a strict CSP. Configure app.security.csp (and devCsp) with default-src 'self' plus your own custom protocol, connect-src limited to IPC and your API hosts, and no remote scripts from CDNs. Tauri adds nonces and hashes for bundled assets at compile time; do not disable that with dangerousDisableAssetCspModification. Add 'wasm-unsafe-eval' only if the frontend uses WebAssembly. See the Content Security Policy Guide.
- Turn on extra frontend hardening. Enable freezePrototype after testing, leave withGlobalTauri off, consider the isolation pattern, and set response headers such as X-Content-Type-Options and Cross-Origin-Opener-Policy through app.security.headers (available since Tauri 2.1.0).
- Secure the build and release. Keep the updater private key and platform signing keys out of developer machines, in the release workflow's secret store or a signing service; enforce TLS update endpoints; sign commits; pin dependencies by hash or tag as Tauri recommends.
- Protect the development loop. Tauri warns that its development server is neither encrypted nor authenticated and exposes the frontend to the local network, where an attacker can push code to development devices. Develop only on trusted networks or use mutual TLS, and never use production secrets on development machines.
- Test and roll back. Add tests that call each command from a window that should not have it and expect a denial. Ship hardening changes to a beta channel first; if a CSP or scope change breaks users, roll back through a normal signed update rather than by loosening production settings by hand.
Common misconfigurations
- One capability for every window: using windows: ["*"] with broad permissions means a single cross-site scripting flaw in any window gains every privilege the app has.
- No CSP configured: CSP protection in Tauri is only enabled when set in the configuration. Apps that skip it rely entirely on the frontend having no injection flaws. See the Cross-Site Scripting (XSS) Prevention Guide.
- Custom commands that trust their input: a command that takes a file path or URL from the frontend and uses it without checking the scope or normalising the path turns a minor frontend bug into arbitrary file access.
- Remote capabilities on wildcard domains: granting commands to https://*.example.com hands system access to every subdomain, including forgotten ones, and on Linux and Android to anything those pages frame.
- Updater key on a laptop: keys generated into a developer's home folder and copied into CI by hand are often left behind, backed up to cloud storage or shared. Treat key location as an audit item.
- Stale WebView and framework versions: Tauri's guidance is to keep Tauri, rustc and Node.js up to date, because vulnerabilities in Tauri ship inside your bundle. Apps pinned to an old minor version miss security fixes.
Framework mapping
| Control area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | ISO/IEC 27001:2022 Annex A |
|---|---|---|---|
| Capabilities, permissions and scopes | AC-6 Least Privilege; CM-7 Least Functionality | PR.AA-05 | A.8.2 Privileged access rights |
| Secure development and testing | SA-11 Developer Testing and Evaluation; SA-15 Development Process, Standards, and Tools | PR.PS-06 | A.8.25 Secure development life cycle; A.8.28 Secure coding |
| Update signing and integrity | SI-7 Software, Firmware, and Information Integrity | PR.PS-06 | A.8.32 Change management |
| Dependencies and pipeline | SR-3 Supply Chain Controls and Processes | GV.SC-01 | A.5.21 Managing information security in the ICT supply chain |
Practitioner checklist
- Give every window a label and its own capability; no window should share a capability with a remote origin.
- Restrict application commands with AppManifest::commands and remove unused plugin default permissions.
- Review every scope, keep the fs plugin deny scopes for WebView data, and audit custom commands for scope bypasses.
- Configure a strict app.security.csp without remote scripts and never set dangerousDisableAssetCspModification.
- Enable freezePrototype and evaluate the isolation pattern for frontends with many dependencies.
- Keep tokens in the Rust core and the OS credential store, never in WebView storage or the binary.
- Store the updater private key in a CI secret store or signing service, back it up offline, and alert on any use outside a release.
- Fail release builds that enable devtools, insecure updater transport or a missing CSP.
- Run cargo audit and npm audit in CI and pin third-party actions and critical dependencies.
- Publish a coordinated disclosure route for your app, as Tauri does for its own projects.
Standards and references
- Tauri: Security overview
- Tauri: Capabilities
- Tauri: Permissions
- Tauri: Command Scopes
- Tauri: Content Security Policy
- Tauri: Isolation Pattern
- Tauri: Application Lifecycle Threats
- Tauri: Updater plugin
Related NHI Mgmt Group resources: Electron and Desktop App Security Guide · Desktop Application Auto-Update Security Guide · crates.io and Cargo Supply Chain Guide · Content Security Policy Guide