Brownfield pattern is the default
The Brownfield pattern is the default security pattern in Tauri. It is designed to be the simplest and most straightforward pattern, attempting to maintain compatibility with existing frontend projects and requiring nothing additional to what an existing web frontend might use inside a browser.
Brownfield pattern configuration
To explicitly set the Brownfield pattern in Tauri, add the following configuration to tauri.conf.json: set app > security > pattern > use to "brownfield". There are no additional configuration options for the brownfield pattern.
Brownfield pattern browser compatibility
The Brownfield pattern in Tauri aims to be compatible with existing browser applications, but not everything that works in existing browser applications will work out-of-the-box in Tauri.
Message passing safety compared to shared memory
Message passing is safer than shared memory or direct function access because the recipient is free to reject or discard requests. For example, if the Tauri Core process determines a request to be malicious, it can simply discard it and never execute the corresponding function.
Commands do not share FFI security pitfalls
Although Commands provide a foreign function interface-like abstraction, they do not share the same security pitfalls as real FFI interfaces because they still use message passing under the hood.
Isolation pattern purpose and threat model
The Isolation pattern intercepts and modifies Tauri API messages sent by the frontend before they reach Tauri Core using injected JavaScript. Its purpose is to protect applications from unwanted or malicious frontend calls to Tauri Core, particularly against Development Threats from many nested dependencies in frontend build tools and bundled dependencies.
Isolation pattern recommendation
Tauri highly recommends using the Isolation pattern whenever it can be used. Because it intercepts all messages from the frontend, it can always be used. Tauri strongly suggests locking down applications whenever using external Tauri APIs by utilizing the Isolation application to verify IPC inputs are within expected parameters.
Isolation pattern implementation with iframes
The Isolation pattern injects a secure application between the frontend and Tauri Core using the sandboxing feature of iframes. Tauri enforces routing of all IPC calls through the sandboxed Isolation application first. After the Isolation application processes the message, it is encrypted using the browser's SubtleCrypto implementation with AES-GCM and a runtime-generated key, then passed back to the frontend for delivery to Tauri Core where it is decrypted.
IPC message flow with Isolation pattern
The approximate steps of an IPC message with the Isolation pattern are: 1) Tauri's IPC handler receives a message, 2) IPC handler passes to Isolation application, 3) Isolation application hook runs in sandbox and potentially modifies the message, 4) Message is encrypted with AES-GCM using a runtime-generated key in sandbox, 5) Encrypted message passes from Isolation application to IPC handler, 6) Encrypted message passes from IPC handler to Tauri Core.
Isolation pattern key generation
New cryptographic keys are generated each time the Tauri application is run to prevent someone from manually reading keys for a specific version and using them to modify encrypted messages.
Isolation pattern performance considerations
The Isolation pattern adds encryption overhead compared to the Brownfield pattern. However, most applications should not notice runtime costs of encrypting/decrypting IPC messages as they are relatively small and AES-GCM is relatively fast. Performance-sensitive applications with carefully-maintained small dependency sets may be more affected. Key generation overhead is not generally noticeable if the system has adequate entropy, though headless environments for integration testing may benefit from entropy-generating services like haveged.
Isolation pattern limitation on Windows
External files do not load correctly inside sandboxed iframes on Windows due to platform inconsistencies. To work around this, Tauri inlines scripts at build time, taking the content of scripts relative to the Isolation application and injecting them inline. Typical bundling and simple script inclusion work, but newer mechanisms such as ES Modules will not successfully load.
Isolation application recommendation to keep simple
The Isolation application should be kept as simple as possible to protect against Development Threats. Dependencies should be kept minimal and required build steps should be minimized to avoid needing to worry about supply chain attacks against the Isolation application on top of frontend attacks.
Isolation pattern hook implementation
The Isolation application uses a global hook function `window.__TAURI_ISOLATION_HOOK__` that receives incoming IPC payloads. This function can inspect, modify, or reject the payload before it is encrypted and passed to Tauri Core. The hook must return the payload to be processed.
Isolation pattern configuration example
To enable the Isolation pattern in tauri.conf.json, set the security.pattern.use field to 'isolation' and provide the directory path in security.pattern.options.dir. Example configuration: {"app": {"security": {"pattern": {"use": "isolation", "options": {"dir": "../dist-isolation"}}}}}
Isolation application file structure
The Isolation application consists of an HTML file that includes a JavaScript file. The HTML file should include a script tag that loads the JavaScript file (e.g., `<script src="index.js"></script>`). The JavaScript file defines the `window.__TAURI_ISOLATION_HOOK__` function that intercepts IPC messages.
Principle of Least Privilege in Tauri
You can limit the blast radius of potential exploits by handing out only the minimum amount of permissions to each process, just enough so they can get their job done. This pattern is known as the Principle of Least Privilege. The less access you give a process, the less harm it can do if it gets compromised.
Security best practices for WebView processes
Security best practices apply to Tauri applications: you must always sanitize user input, never handle secrets in the Frontend, and ideally defer as much business logic as possible to the Core process to keep your attack surface small.
Command permissions are denied by default
By default, plugin commands are not accessible by the frontend and will return a denied error rejection. Commands must have permissions defined to be executable.
Permission files location and format
Permissions are defined as JSON or TOML files inside the `permissions` directory. Each file can define a list of permissions, permission sets, and the plugin's default permission.
Permission structure in TOML
Permissions are defined with an `identifier` (unique name), `description` (human-readable explanation), and `commands` object. The `commands` object contains `allow` array (list of allowed commands) and `deny` array (list of denied commands). Example: `identifier = "allow-start-server"`, `description = "Enables the start_server command."`, `commands.allow = ["start_server"]`.
Command scope for plugin permissions
Command scopes allow plugins to define deeper restrictions for individual commands. Plugin consumers define scopes for specific commands in their capability files. Access command-specific scope in Rust using the `tauri::ipc::CommandScope` struct with generic type parameter for the scope data type. Methods include `allows()` and `denies()`.
Global scope for plugins without command restrictions
When a permission defines no commands to allow or deny, it is a scope permission that defines only a global scope for the plugin. Access global scope in Rust using `tauri::ipc::GlobalScope` struct with generic type parameter for the scope data type. Methods include `allows()` and `denies()`. Recommendation: check both global and command scopes for flexibility.
Scope entry schema generation with schemars
Scope entries require the `schemars` dependency to generate a JSON schema so plugin consumers know the scope format. Add `schemars = "0.8"` to both `dependencies` and `build-dependencies` in Cargo.toml since the scope module is shared between app code and build script. In build.rs, use `tauri_plugin::Builder::new(COMMANDS).global_scope_schema(schemars::schema_for!(scope::Entry)).build()`.
Permission sets for command groups
Permission sets are groups of individual permissions that provide higher-level abstraction for plugin management. Use them when a single API uses multiple commands or when there's logical connection between commands. Defined with `identifier` (unique name), `description` (explanation), and `permissions` array listing the included permission identifiers.
Default permission with identifier 'default'
The default permission is a special permission set with identifier `default`. It is recommended to enable required commands by default. Defined in a permission file with `description` and `permissions` array listing default-enabled permissions.
Autogenerated permissions in build.rs
Define a `COMMANDS` const array in build.rs listing command names in snake_case (matching the function name). Tauri automatically generates `allow-$commandname` and `deny-$commandname` permissions for each command. Example: `const COMMANDS: &[&str] = &["upload"];` generates `allow-upload` and `deny-upload` permissions. Call `tauri_plugin::Builder::new(COMMANDS).build()` in build.rs.
Android permissions in TauriPlugin annotation
Android permissions are defined in the @TauriPlugin annotation with a permissions array. Each permission entry includes the permission string and an alias. Example:
```kotlin
@TauriPlugin(
permissions = [
Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "postNotification")
]
)
class ExamplePlugin(private val activity: Activity): Plugin(activity) { }
```
iOS permission methods checkPermissions and requestPermissions
iOS plugins should override the checkPermissions and requestPermissions methods to manage permissions. Example:
```swift
class ExamplePlugin: Plugin {
@objc open func checkPermissions(_ invoke: Invoke) {
invoke.resolve(["postNotification": "prompt"])
}
@objc public override func requestPermissions(_ invoke: Invoke) {
// request permissions here
// then resolve the request
invoke.resolve(["postNotification": "granted"])
}
}
```
Tauri automatically implements checkPermissions and requestPermissions commands
Tauri automatically implements checkPermissions and requestPermissions commands for plugins. These commands can be called directly from JavaScript or Rust using the plugin command naming convention.
Plugin event capability requirement
Listening to plugin events from JavaScript is gated by the capability and permission system. Add the plugin's permission (commonly <plugin-name>:default or a specific allow-listen-* permission) to the permissions array of a capability under src-tauri/capabilities/. Example:
```json
{
"identifier": "default",
"windows": ["main"],
"permissions": ["<plugin-name>:default"]
}
```
Built-in Tauri dev server security limitations
The built-in Tauri development server does not support mutual authentication or encryption. It should never be used for development on untrusted networks due to these security limitations.
Grant sidecar execute/spawn permissions in capabilities
To run a sidecar from JavaScript, grant permission in src-tauri/capabilities/default.json by adding a permission entry with identifier 'shell:allow-execute' (for execute() method) or 'shell:allow-spawn' (for spawn() method). Include the sidecar entry in the 'allow' array with the 'name' matching a path from externalBin and 'sidecar: true' flag.
JavaScript sidecar permission configuration example
{
"permissions": [
"core:default",
{
"identifier": "shell:allow-execute",
"allow": [
{
"name": "binaries/app",
"sidecar": true
}
]
}
]
}
Define sidecar arguments in capabilities
Arguments for sidecar commands are defined in src-tauri/capabilities/default.json within the 'allow' entry for that sidecar. Arguments can be: static strings (exact values like 'arg1' or '-a'), dynamic values defined with regex validator objects (like {"validator": "\\S+"}), true (allow any arguments), or false (disable all arguments). Arguments must be passed in the exact order defined.
Sidecar argument configuration example in capabilities
{
"identifier": "shell:allow-execute",
"allow": [
{
"args": [
"arg1",
"-a",
"--arg2",
{
"validator": "\\S+"
}
],
"name": "binaries/my-sidecar",
"sidecar": true
}
]
}
Tauri security features
Tauri is built on Rust, which provides memory, thread, and type-safety benefits that apps can automatically leverage. Tauri undergoes security audits for major and minor releases, which cover code in the Tauri organization and upstream dependencies. A Tauri security policy and audit reports are publicly available.
capabilities/ directory default location
The capabilities/ directory is the default folder where Tauri reads capability files from. Capabilities are required to allow commands to be used in JavaScript code.
Tauri security audit
The entire Tauri project has been horizontally and vertically audited by an independent third party, with the full audit report available at https://github.com/tauri-apps/tauri/blob/dev/audits/Radically_Open_Security-v1-report.pdf
Tauri local-first application design
Tauri allows you to build local-first applications without a webserver, so users do not have to share their data with external services. Using local databases and Rust-based cryptography is supported.
Tauri 1.2.0 security fix for file dialog escaping
Tauri 1.2.0 includes a security patch for a vulnerability in file dialog and drag-and-drop functionality. Incorrect escaping of special characters in paths allowed partial bypass of the fs scope definition. The vulnerability was limited to neighboring files and subfolders of already allowed paths and required user interaction (selecting a malicious file during file picker) combined with adversary-controlled logic. The patch is also available in versions 1.0.7 and 1.1.2. See advisory GHSA-q9wv-22m9-vhqh for details.
ZipSlip vulnerability fix in bundle extraction
Tauri 1.3 fixed a ZipSlip vulnerability in the bundler where remote files were extracted using ZipFile::name() instead of ZipFile::enclosed_name(). This could allow malicious archives with paths like ../../../../foo.sh to extract files outside the intended directory. The implementation was changed to use the proper extraction method.
Tauri v2.0.0-beta.0 is undergoing security audit
Tauri v2.0.0-beta.0 is currently being audited to ensure security, similar to what was done for the v1 stable release.
External security audit for Tauri 2.0
Tauri 2.0 underwent an external security audit by RadicallyOpenSecurity funded by NLNet. All findings were fixed and retested. One finding (CVE-2024-35222) was distributed as a security patch during beta. The full audit report is available in the Tauri repository at audits/Radically_Open_Security-v2-report.pdf. Users should upgrade to the release candidate to ensure all fixes are applied.
External security audit
Tauri 2.0's major changes and architecture were independently audited by Radically Open Security during the beta and release candidate period. The audit was funded by NLNet via funding from NGI. The audit report is available at https://github.com/tauri-apps/tauri/blob/dev/audits/Radically_Open_Security-v2-report.pdf. The audit results led to rewrites of the dev server exposure for mobile development, hardening of the iFrame API exposure, fixing of scope validation and resource identifier access for the fs and http plugins, and improved inter-process communication stability.