tauri-expert — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited tauri-expert (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Cross-platform desktop/mobile apps with Rust backend and web frontend.
# Create new Tauri app
npm create tauri-app@latest
# Add to existing frontend project
npm install -D @tauri-apps/cli
npx tauri init
# Dev
npm run tauri dev
# Build
npm run tauri buildmy-app/
├── src/ # Frontend (React/Vue/Svelte/vanilla)
├── src-tauri/
│ ├── src/
│ │ ├── main.rs
│ │ └── lib.rs # Commands defined here
│ ├── capabilities/
│ │ └── default.json # Permission grants
│ ├── Cargo.toml
│ └── tauri.conf.json # App config
└── package.json// src-tauri/src/lib.rs
use tauri::State;
use std::sync::Mutex;
#[derive(Default)]
struct AppState {
counter: Mutex<i32>,
}
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {name}! You've been greeted from Rust.")
}
#[tauri::command]
async fn fetch_data(url: String) -> Result<String, String> {
reqwest::get(&url)
.await
.map_err(|e| e.to_string())?
.text()
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
fn increment_counter(state: State<AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.manage(AppState::default())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![
greet,
fetch_data,
increment_counter
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}import { invoke } from "@tauri-apps/api/core"
// Simple command
const greeting = await invoke<string>("greet", { name: "World" })
// Async command with error handling
try {
const data = await invoke<string>("fetch_data", { url: "https://api.example.com" })
} catch (error) {
console.error("Command failed:", error)
}
// State-managed command
const count = await invoke<number>("increment_counter")// lib/tauri-commands.ts — single source of truth for all commands
import { invoke } from "@tauri-apps/api/core"
export const commands = {
greet: (name: string) => invoke<string>("greet", { name }),
fetchData: (url: string) => invoke<string>("fetch_data", { url }),
incrementCounter: () => invoke<number>("increment_counter"),
} as const
// Usage — fully typed
const result = await commands.greet("Alice")use tauri::{Emitter, AppHandle};
#[tauri::command]
async fn long_running_task(app: AppHandle) -> Result<(), String> {
for i in 0..100 {
// Do work...
app.emit("progress", i).map_err(|e| e.to_string())?;
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
app.emit("task-complete", ()).map_err(|e| e.to_string())?;
Ok(())
}import { listen } from "@tauri-apps/api/event"
const unlisten = await listen<number>("progress", (event) => {
console.log("Progress:", event.payload)
setProgress(event.payload)
})
// Clean up when component unmounts
useEffect(() => {
return () => { unlisten() }
}, [])Tauri v2 uses an explicit capability system — nothing is allowed by default.
capabilities/default.json{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default capabilities for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open",
"fs:allow-read-text-file",
"fs:allow-write-text-file",
{
"identifier": "fs:scope",
"allow": [{ "path": "$APPDATA/*" }]
},
"http:default",
{
"identifier": "http:default",
"allow": [{ "url": "https://api.example.com/*" }]
}
]
}# src-tauri/permissions/my-permissions.toml
[[permission]]
identifier = "allow-greet"
description = "Allows calling the greet command"
commands.allow = ["greet"]// Add to capabilities/default.json
{
"permissions": ["my-plugin:allow-greet"]
}// ❌ Overly broad
{ "permissions": ["fs:default"] } // grants all filesystem access
// ✅ Scoped to exactly what's needed
{
"permissions": [
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "$APPDATA/config.json" }]
}
]
}// Shared, mutable state across commands
use std::sync::Mutex;
use tauri::State;
struct Database {
conn: Mutex<rusqlite::Connection>,
}
#[tauri::command]
fn get_user(id: i64, db: State<Database>) -> Result<User, String> {
let conn = db.conn.lock().map_err(|e| e.to_string())?;
conn.query_row(
"SELECT id, name FROM users WHERE id = ?1",
[id],
|row| Ok(User { id: row.get(0)?, name: row.get(1)? }),
)
.map_err(|e| e.to_string())
}
pub fn run() {
let conn = rusqlite::Connection::open("app.db").expect("failed to open db");
tauri::Builder::default()
.manage(Database { conn: Mutex::new(conn) })
.invoke_handler(tauri::generate_handler![get_user])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}use tauri::{WebviewWindowBuilder, WebviewUrl};
#[tauri::command]
async fn open_settings_window(app: tauri::AppHandle) -> Result<(), String> {
WebviewWindowBuilder::new(&app, "settings", WebviewUrl::App("settings.html".into()))
.title("Settings")
.inner_size(600.0, 400.0)
.resizable(false)
.build()
.map_err(|e| e.to_string())?;
Ok(())
}import { getCurrentWindow } from "@tauri-apps/api/window"
const win = getCurrentWindow()
await win.setTitle("New Title")
await win.minimize()
await win.close()# Initialize mobile targets
npx tauri ios init
npx tauri android init
# Dev
npx tauri ios dev
npx tauri android dev
# Build
npx tauri ios build
npx tauri android build// Mobile-specific entry point required
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
// ...
.run(tauri::generate_context!())
.expect("error while running tauri application");
}// tauri.conf.json — mobile-specific config
{
"bundle": {
"iOS": {
"minimumSystemVersion": "13.0"
},
"android": {
"minSdkVersion": 24
}
}
}// tauri.conf.json
{
"productName": "MyApp",
"version": "1.0.0",
"identifier": "com.example.myapp",
"app": {
"windows": [
{
"title": "MyApp",
"width": 1024,
"height": 768,
"resizable": true
}
],
"security": {
"csp": "default-src 'self'; img-src 'self' data: https:; connect-src 'self' https://api.example.com"
}
},
"bundle": {
"active": true,
"targets": ["dmg", "msi", "deb", "appimage"],
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.icns", "icons/icon.ico"]
}
}*:default broadly; scope to specific paths/URLsMutex/RwLockinvoke() callsconnect-src to known API domainsunlisten() on component unmount~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.