src/skills/desktop-security-tauri/SKILL.md
Tauri 2.x deny-by-default security model, capabilities, permissions, scopes, ACL
npx skillsauth add agents-inc/skills desktop-security-tauriInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Tauri 2 uses a deny-by-default security model. Nothing is accessible unless explicitly granted in a capability file (
src-tauri/capabilities/*.json). Capabilities bind permissions to specific windows. Permissions follow theplugin:commandidentifier pattern. Scopes restrict operations to specific paths or URLs with allow/deny lists (deny always wins). Every plugin and custom command needs a permission grant -- missing permissions cause runtime errors, not compile errors.Current version: Tauri 2.x (stable). Tauri 1.x used a boolean allowlist which is completely removed in v2.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST create at least one capability file in src-tauri/capabilities/ -- without it, ALL plugin and core API calls fail at runtime)
(You MUST include core:default in every capability -- without it, basic app lifecycle commands fail)
(You MUST scope permissions to specific windows using the windows array -- a window not listed in any capability has zero IPC access)
(You MUST use deny scopes to restrict sensitive paths -- deny ALWAYS takes precedence over allow)
(You MUST use plugin:permission-name format for plugin permissions and plain permission-name for app commands)
</critical_requirements>
Auto-detection: Tauri capabilities, src-tauri/capabilities, capability file, permissions, ACL, allow-scope, deny-scope, core:default, fs:allow, shell:allow, http:allow, permission set, remote domain, CapabilityRemote, desktop-schema.json, mobile-schema.json, scope allow deny, Tauri security, tauri permission denied, capability identifier
When to use:
When NOT to use:
Key patterns covered:
Detailed resources:
Tauri 2 implements a deny-by-default access control model. Every potentially dangerous operation (filesystem, network, shell, clipboard) is blocked until explicitly granted in a capability file. This is a fundamental shift from v1's boolean allowlist -- instead of toggling features on/off globally, you define granular permissions scoped to specific windows, platforms, and paths.
The security hierarchy:
plugin:command identifier pattern.Key design decisions:
desktop-schema.json, mobile-schema.json) for IDE autocompletionWhen to invest in fine-grained capabilities:
When simple capabilities suffice:
Every Tauri 2 app needs at least one capability file in src-tauri/capabilities/. The file grants permissions to specific windows.
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "Permissions for the main application window",
"windows": ["main"],
"permissions": ["core:default", "event:default", "window:default"]
}
Key fields: identifier (unique name), windows (which windows get these permissions), permissions (what operations are allowed). The $schema field enables IDE autocompletion for available permissions.
Key rule: All capabilities in src-tauri/capabilities/ are auto-enabled unless you explicitly list capabilities in tauri.conf.json's app.security.capabilities array -- in which case only the listed ones are used.
See examples/core.md for all capability fields and patterns.
Restrict plugin operations to specific paths using inline scope objects. Deny always takes precedence.
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "$APPDATA/**" }]
}
{
"identifier": "fs:deny-read-text-file",
"deny": [{ "path": "$APPDATA/secrets/**" }]
}
Key rule: Deny always supersedes allow. If a path matches both an allow and a deny scope, it is blocked. Use $APPDATA, $HOME, $RESOURCE and other Tauri path variables -- not hardcoded OS paths.
See examples/core.md for filesystem, HTTP, and shell scope patterns.
Different windows get different permission sets. An editor window gets write access; a viewer window gets read-only access.
{
"identifier": "editor-capability",
"windows": ["editor"],
"permissions": ["core:default", "fs:allow-write-text-file"]
}
{
"identifier": "viewer-capability",
"windows": ["viewer"],
"permissions": ["core:default", "fs:allow-read-text-file"]
}
Key rule: A window not listed in any capability's windows array has zero IPC access. Windows listed in multiple capabilities get the merged permissions of all matching capabilities.
See examples/core.md for multi-window and wildcard patterns.
Use the platforms field to restrict capabilities to specific operating systems.
{
"identifier": "desktop-features",
"windows": ["main"],
"platforms": ["linux", "macOS", "windows"],
"permissions": ["core:default", "shell:allow-open", "global-shortcut:default"]
}
Key rule: Platform values are "linux", "macOS", "windows", "iOS", "android". Splitting by platform prevents permission errors for platform-specific plugins.
See examples/core.md for mobile-specific capability examples.
Define permissions for your own Tauri commands using TOML files in src-tauri/permissions/.
# src-tauri/permissions/default.toml
[default]
description = "Default app permissions"
permissions = ["allow-greet", "allow-get-settings"]
# src-tauri/permissions/admin.toml
[[permission]]
identifier = "allow-admin-ops"
description = "Allow admin operations"
commands.allow = ["reset_database", "export_all_data"]
Key point: Tauri auto-generates allow-* and deny-* permissions for every command registered in generate_handler![]. Custom permission files let you group them and add scopes.
See examples/custom-permissions.md for permission sets and custom scope definitions.
Grant remote web content access to Tauri APIs using the remote field with URL patterns.
{
"identifier": "remote-api-access",
"windows": ["main"],
"remote": {
"urls": ["https://*.mydomain.dev"]
},
"permissions": ["core:default", "notification:default"]
}
Key rule: Remote URLs gain access to the specified Tauri APIs -- understand the security implications. On Linux and Android, Tauri cannot distinguish between iframe requests and window requests, so remote access should be used cautiously on those platforms.
See examples/core.md for remote access patterns and security considerations.
</patterns><decision_framework>
What does the app do?
|-- Reads/writes files?
| +-- tauri-plugin-fs permissions with path scopes
|-- Makes HTTP requests from Rust backend?
| +-- tauri-plugin-http permissions with URL scopes
|-- Opens file/folder dialogs?
| +-- tauri-plugin-dialog permissions
|-- Runs external processes?
| +-- tauri-plugin-shell permissions (desktop only)
|-- Shows system notifications?
| +-- tauri-plugin-notification permissions
|-- Uses persistent key-value storage?
| +-- tauri-plugin-store permissions
+-- Basic app + window lifecycle only?
+-- core:default is sufficient
How many windows does the app have?
|-- Single window?
| +-- One capability file with all permissions is fine
|-- Multiple windows with SAME needs?
| +-- One capability file listing all windows in the array
+-- Multiple windows with DIFFERENT needs?
+-- Separate capability files per window (principle of least privilege)
Does the app need platform-specific features?
|-- Same features on all platforms?
| +-- Omit the platforms field
+-- Different features per platform?
+-- Separate capability files with platforms field
Is the scope for a built-in plugin?
|-- YES -> Inline in the capability file permissions array
| { "identifier": "fs:allow-read-text-file", "allow": [...] }
+-- NO -> Is it for your custom commands?
|-- YES -> Define in src-tauri/permissions/*.toml
+-- NO -> It might not need a scope -- simple allow/deny suffices
</decision_framework>
<red_flags>
High Priority Issues:
core:default in a capability -- basic app lifecycle commands fail silentlysrc-tauri/capabilities/ -- ALL IPC calls fail at runtimetauri.allowlist in config -- completely removed in v2, does nothingfs:allow-read-text-file without path scope -- allows reading ANY file on the systemwindows array in capability -- permissions apply to no windows (useless capability)Medium Priority Issues:
"windows": ["*"] in production -- every window gets these permissions, including dynamic oneshttp:allow-fetch without URL scope) -- allows requests to any URLCommon Mistakes:
/home/user/) instead of Tauri path variables ($HOME/) in scopesapp.security.capabilities in tauri.conf.json to list some capabilities, then wondering why unlisted capability files are ignored (explicit list overrides auto-discovery)[a-z] plus hyphens)Gotchas & Edge Cases:
cargo clean) or ensure build.rs has cargo:rerun-if-changed for the capabilities directory$schema field must point to the correct generated schema (../gen/schemas/desktop-schema.json or mobile-schema.json). Run cargo tauri dev once to generate schemasplugin:permission-name, app-defined permissions use just permission-name (no prefix). Using the wrong format causes "permission not found" errorssrc-tauri/permissions/) must be TOML only</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST create at least one capability file in src-tauri/capabilities/ -- without it, ALL plugin and core API calls fail at runtime)
(You MUST include core:default in every capability -- without it, basic app lifecycle commands fail)
(You MUST scope permissions to specific windows using the windows array -- a window not listed in any capability has zero IPC access)
(You MUST use deny scopes to restrict sensitive paths -- deny ALWAYS takes precedence over allow)
(You MUST use plugin:permission-name format for plugin permissions and plain permission-name for app commands)
Failure to follow these rules will cause silent runtime permission denials that do not surface at compile time.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events