Tauri Security
Tauri 2.0 security model including capability-based permissions, allowlist configuration, Content Security Policy, IPC safety, sandboxing, code signing, auto-update security, and supply chain considerations.
You are an expert in securing Tauri 2.0 applications, covering the capability-based permission system, IPC hardening, code signing, and supply chain security.
## Key Points
- **Granting `allow-*` permissions on any plugin**: Wildcard permissions negate the capability system. Grant only specific permissions your app actually uses.
- **Trusting frontend-provided file paths without validation**: The frontend is untrusted. Always validate and sanitize paths on the Rust side.
- **Disabling CSP for convenience**: A permissive CSP opens the door to XSS attacks that can invoke your Tauri commands.
- **Shipping debug builds**: Debug builds include devtools and may have relaxed security settings.
- **Storing secrets in the frontend bundle**: JavaScript source is visible to anyone who opens the app package. Use the Rust backend for secret management.
- name: Build Tauri App
1. Define minimal capabilities per window
2. Scope filesystem access to specific directories
3. Set a restrictive Content Security Policy
4. Validate all inputs in Rust commands
5. Sanitize file paths against traversal attacks
6. Enable code signing for all platforms
## Quick Example
```bash
# Generate a keypair for update signing
cargo tauri signer generate -w ~/.tauri/myapp.key
# The public key goes in tauri.conf.json
# The private key signs your update bundles during CI/CD
```
```toml
# Cargo.toml -- pin dependencies
[dependencies]
tauri = { version = "=2.1.0", features = [] }
serde = { version = "=1.0.210", features = ["derive"] }
```skilldb get tauri-skills/Tauri SecurityFull skill: 386 linesTauri Security — Secure Desktop Application Development
You are an expert in securing Tauri 2.0 applications, covering the capability-based permission system, IPC hardening, code signing, and supply chain security.
Core Philosophy
Tauri's security model is built on the principle of least privilege. The webview frontend is untrusted by default -- it cannot access the filesystem, network, or system APIs without explicit permission grants from the Rust backend. This is fundamentally different from Electron, where the renderer process can be granted broad Node.js access.
Every capability your app uses must be declared in capability files. These declarations are compiled into the binary and enforced at runtime. An attacker who achieves code execution in the webview (via XSS or a compromised dependency) still cannot access anything beyond the declared permissions. This layered defense means a single vulnerability does not compromise the entire system.
Security is not a feature you add at the end. Design your permission scopes during architecture, restrict IPC commands to the minimum needed, and treat every frontend input as potentially malicious on the Rust side.
Anti-Patterns
- Granting
allow-*permissions on any plugin: Wildcard permissions negate the capability system. Grant only specific permissions your app actually uses. - Trusting frontend-provided file paths without validation: The frontend is untrusted. Always validate and sanitize paths on the Rust side.
- Disabling CSP for convenience: A permissive CSP opens the door to XSS attacks that can invoke your Tauri commands.
- Shipping debug builds: Debug builds include devtools and may have relaxed security settings.
- Storing secrets in the frontend bundle: JavaScript source is visible to anyone who opens the app package. Use the Rust backend for secret management.
Capability-Based Permissions
Capability Files
Capabilities live in src-tauri/capabilities/ and define what each window can do:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-window",
"description": "Capabilities for the main application window",
"windows": ["main"],
"permissions": [
"core:default",
"fs:allow-read",
"dialog:allow-open",
"dialog:allow-save",
"notification:default"
]
}
Per-Window Capabilities
Different windows can have different permission sets:
// capabilities/settings.json
{
"identifier": "settings-window",
"windows": ["settings"],
"permissions": [
"core:default",
"fs:allow-read",
"fs:allow-write"
]
}
// capabilities/viewer.json
{
"identifier": "viewer-window",
"windows": ["viewer"],
"permissions": [
"core:default",
"fs:allow-read"
]
}
Scoped File Access
{
"permissions": [
{
"identifier": "fs:allow-read",
"allow": [
{ "path": "$APPDATA/**" },
{ "path": "$DOCUMENT/*.txt" },
{ "path": "$HOME/.config/myapp/**" }
],
"deny": [
{ "path": "$HOME/.ssh/**" },
{ "path": "$HOME/.gnupg/**" }
]
},
{
"identifier": "fs:allow-write",
"allow": [
{ "path": "$APPDATA/**" }
]
}
]
}
Available Path Variables
| Variable | Description |
|---|---|
$APPDATA | App-specific data directory |
$APPCONFIG | App configuration directory |
$APPLOCALDATA | App local data directory |
$DESKTOP | User's desktop |
$DOCUMENT | User's documents |
$DOWNLOAD | User's downloads |
$HOME | User's home directory |
$TEMP | Temporary directory |
$RESOURCE | App bundled resources |
Content Security Policy
Configuring CSP
// tauri.conf.json
{
"app": {
"security": {
"csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' asset: https://cdn.example.com; connect-src ipc: http://ipc.localhost"
}
}
}
Recommended CSP Directives
default-src 'self' # Only load resources from the app bundle
script-src 'self' # No inline scripts, no eval()
style-src 'self' 'unsafe-inline' # Allow inline styles (common in frameworks)
img-src 'self' asset: blob: # Allow local images and blob URLs
font-src 'self' data: # Allow local fonts and data URIs
connect-src ipc: http://ipc.localhost # Allow Tauri IPC
Development vs Production CSP
fn run() {
let csp = if cfg!(debug_assertions) {
// Relaxed for development (allow devtools, HMR)
"default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src ipc: http://ipc.localhost ws://localhost:*"
} else {
// Strict for production
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
};
// CSP is typically set in tauri.conf.json, but can be overridden
}
IPC Safety
Validating Command Inputs
use std::path::{Path, PathBuf};
#[tauri::command]
async fn read_user_file(
path: String,
app: AppHandle,
) -> Result<String, AppError> {
// Resolve and canonicalize the path
let resolved = PathBuf::from(&path);
let canonical = resolved.canonicalize()
.map_err(|_| AppError::InvalidPath(path.clone()))?;
// Verify the path is within allowed directories
let app_data = app.path().app_data_dir()
.map_err(|e| AppError::Internal(e.to_string()))?;
if !canonical.starts_with(&app_data) {
return Err(AppError::PermissionDenied(
"Path is outside the allowed directory".into()
));
}
tokio::fs::read_to_string(&canonical)
.await
.map_err(|e| AppError::Io(e.to_string()))
}
Preventing Path Traversal
fn safe_join(base: &Path, user_input: &str) -> Result<PathBuf, AppError> {
let joined = base.join(user_input);
let canonical = joined.canonicalize()
.map_err(|_| AppError::InvalidPath(user_input.to_string()))?;
// Ensure the resolved path is still under the base
if !canonical.starts_with(base) {
return Err(AppError::PermissionDenied(
"Path traversal detected".into()
));
}
Ok(canonical)
}
Rate Limiting IPC Commands
use std::sync::Mutex;
use std::time::{Duration, Instant};
struct RateLimiter {
last_call: Mutex<Instant>,
min_interval: Duration,
}
impl RateLimiter {
fn new(min_interval: Duration) -> Self {
Self {
last_call: Mutex::new(Instant::now() - min_interval),
min_interval,
}
}
fn check(&self) -> Result<(), AppError> {
let mut last = self.last_call.lock().unwrap();
let now = Instant::now();
if now.duration_since(*last) < self.min_interval {
return Err(AppError::RateLimited);
}
*last = now;
Ok(())
}
}
Code Signing
macOS Code Signing
# Set environment variables for Tauri build
export APPLE_CERTIFICATE="base64-encoded-p12"
export APPLE_CERTIFICATE_PASSWORD="password"
export APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)"
# For notarization
export APPLE_API_KEY="AuthKey_XXXXXX.p8"
export APPLE_API_KEY_ID="XXXXXX"
export APPLE_API_ISSUER="your-issuer-id"
cargo tauri build
Windows Code Signing
# Using a PFX certificate
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="password"
# In tauri.conf.json
{
"bundle": {
"windows": {
"certificateThumbprint": "YOUR_CERT_THUMBPRINT",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
}
}
}
Auto-Update Security
// tauri.conf.json
{
"plugins": {
"updater": {
"pubkey": "YOUR_PUBLIC_KEY_HERE",
"endpoints": [
"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
]
}
}
}
Generating Update Keys
# Generate a keypair for update signing
cargo tauri signer generate -w ~/.tauri/myapp.key
# The public key goes in tauri.conf.json
# The private key signs your update bundles during CI/CD
Update Endpoint Response
{
"version": "1.2.0",
"notes": "Bug fixes and performance improvements",
"pub_date": "2025-01-15T12:00:00Z",
"platforms": {
"windows-x86_64": {
"signature": "SIGNATURE_HERE",
"url": "https://releases.myapp.com/v1.2.0/myapp_1.2.0_x64-setup.nsis.zip"
},
"darwin-aarch64": {
"signature": "SIGNATURE_HERE",
"url": "https://releases.myapp.com/v1.2.0/myapp.app.tar.gz"
}
}
}
Supply Chain Considerations
Frontend Dependencies
# Audit npm dependencies
npm audit
# Use lockfile integrity checks
npm ci # Uses package-lock.json exactly
# Consider using a tool like Socket.dev for supply chain monitoring
Rust Dependencies
# Audit Cargo dependencies
cargo install cargo-audit
cargo audit
# Check for unsafe code
cargo install cargo-geiger
cargo geiger
# Pin dependency versions in Cargo.toml
# Use exact versions for critical dependencies
Build Reproducibility
# Cargo.toml -- pin dependencies
[dependencies]
tauri = { version = "=2.1.0", features = [] }
serde = { version = "=1.0.210", features = ["derive"] }
CI/CD Security
# GitHub Actions -- build in a controlled environment
- name: Build Tauri App
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
run: |
npm ci
cargo tauri build
Security Checklist
- Define minimal capabilities per window
- Scope filesystem access to specific directories
- Set a restrictive Content Security Policy
- Validate all inputs in Rust commands
- Sanitize file paths against traversal attacks
- Enable code signing for all platforms
- Sign updates with a dedicated keypair
- Audit both npm and Cargo dependencies regularly
- Never store secrets in the frontend bundle
- Ship release builds only (never debug builds)
Install this skill directly: skilldb add tauri-skills
Related Skills
Tauri Commands
Rust commands with the invoke pattern, argument passing, return types, async commands, error handling, state management, and type safety between Rust and TypeScript in Tauri 2.0.
Tauri Distribution
Distributing Tauri applications including installers for MSI, DMG, AppImage, and deb, auto-update with the built-in updater, code signing for Windows and macOS, CI/CD builds, and cross-compilation.
Tauri Frontend
Frontend integration with Tauri 2.0 including React, Vue, Svelte, and Solid frameworks, Vite configuration, asset handling, window management, multiple windows, and webview communication.
Tauri Fundamentals
Tauri 2.0 architecture, Rust backend with webview frontend, project setup with Cargo and npm, development workflow, and build targets for Windows, macOS, Linux, iOS, and Android.
Tauri Mobile
Tauri 2.0 mobile development for iOS and Android, including platform-specific code, mobile plugins, testing on simulators and devices, and app store distribution.
Tauri Patterns
Common Tauri 2.0 patterns: system tray apps, menu bar apps, file handling, SQLite database integration, IPC communication patterns, background tasks, and single-instance enforcement.