重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
The Agent Skills Directory
npx skills add https://smithery.ai/skills/martinholovsky/tauri本技能针对高风险要求采用拆分结构:
风险等级 : 高
理由 : Tauri 应用程序将 Web 内容与原生系统访问桥接起来。不当的 IPC 配置、CSP 绕过和能力管理不善可能导致任意代码执行、文件系统访问和权限提升。
您是 Tauri 桌面应用程序开发专家,深刻理解 Web 代码和原生代码之间的安全边界。您能以最小权限配置应用程序,同时保持功能。
| 情境 | 方法 |
|---|---|
| 需要文件系统访问 | 限定到特定目录,绝不允许根目录 |
| 需要执行 shell 命令 | 默认禁用,如需则使用允许列表 |
| 需要网络访问 | 在 CSP 中指定允许的域名 |
| 自定义 IPC 命令 | 验证所有输入,检查权限 |
| 敏感操作 | 要求来源验证 |
| 类别 | 版本 | 备注 |
|---|---|---|
| Tauri CLI | 2.0+ | 新项目使用 2.x |
| Tauri Core | 2.0+ | 相比 1.x 有重大安全改进 |
| Rust | 1.77.2+ | 修复了 CVE-2024-24576 |
| Node.js | 20 LTS | 用于构建工具 |
src-tauri/
├── Cargo.toml
├── tauri.conf.json # 主配置文件
├── capabilities/ # 权限定义
│ ├── default.json
│ └── admin.json
└── src/
└── main.rs
Rust 后端测试:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_read_validates_path() {
let request = FileRequest { path: "../secret".to_string() };
assert!(request.validate().is_err(), "Should reject path traversal");
}
#[tokio::test]
async fn test_async_command_returns_result() {
let result = process_data("valid input".to_string()).await;
assert!(result.is_ok());
}
}
前端 Vitest 测试:
import { describe, it, expect, vi } from 'vitest'
import { invoke } from '@tauri-apps/api/core'
vi.mock('@tauri-apps/api/core')
describe('Tauri IPC', () => {
it('invokes read_file command correctly', async () => {
vi.mocked(invoke).mockResolvedValue('file content')
const result = await invoke('read_file', { path: 'config.json' })
expect(result).toBe('file content')
})
})
仅编写使测试通过所必需的代码:
#[command]
pub async fn process_data(input: String) -> Result<String, String> {
// 通过测试所需的最小实现
Ok(format!("Processed: {}", input))
}
测试通过后,在不改变行为的前提下改进代码结构:
# Rust 测试和代码检查
cd src-tauri && cargo test
cd src-tauri && cargo clippy -- -D warnings
cd src-tauri && cargo audit
# 前端测试
npm test
npm run typecheck
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default permissions for standard users",
"windows": ["main"],
"permissions": [
"core:event:default",
"core:window:default",
{
"identifier": "fs:read-files",
"allow": ["$APPDATA/*", "$RESOURCE/*"]
},
{
"identifier": "fs:write-files",
"allow": ["$APPDATA/*"]
}
]
}
// tauri.conf.json
{
"app": {
"security": {
"csp": {
"default-src": "'self'",
"script-src": "'self'",
"style-src": "'self' 'unsafe-inline'",
"connect-src": "'self' https://api.example.com",
"object-src": "'none'",
"frame-ancestors": "'none'"
},
"freezePrototype": true
}
}
}
use tauri::{command, AppHandle};
use validator::Validate;
#[derive(serde::Deserialize, Validate)]
pub struct FileRequest {
#[validate(length(min = 1, max = 255))]
path: String,
}
#[command]
pub async fn read_file(request: FileRequest, app: AppHandle) -> Result<String, String> {
request.validate().map_err(|e| format!("Validation error: {}", e))?;
let app_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
let full_path = app_dir.join(&request.path);
let canonical = dunce::canonicalize(&full_path).map_err(|_| "Invalid path")?;
// 安全:确保路径在应用程序目录内
if !canonical.starts_with(&app_dir) {
return Err("Access denied: path traversal detected".into());
}
std::fs::read_to_string(canonical).map_err(|e| format!("Failed: {}", e))
}
use tauri::Window;
#[command]
pub async fn sensitive_operation(window: Window) -> Result<(), String> {
let url = window.url();
match url.origin() {
url::Origin::Tuple(scheme, host, _) => {
if scheme != "tauri" && scheme != "https" {
return Err("Invalid origin".into());
}
if host.to_string() != "localhost" && host.to_string() != "tauri.localhost" {
return Err("Invalid origin".into());
}
}
_ => return Err("Invalid origin".into()),
}
Ok(())
}
use tauri_plugin_updater::UpdaterExt;
pub fn configure_updater(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
let updater = handle.updater_builder()
.endpoints(vec!["https://releases.example.com/{{target}}/{{current_version}}".into()])
.pubkey("YOUR_PUBLIC_KEY_HERE")
.build()?;
if let Ok(Some(update)) = updater.check().await {
let _ = update.download_and_install(|_, _| {}, || {}).await;
}
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
});
Ok(())
}
关于高级模式和插件开发,请参阅
references/advanced-patterns.md
// 错误做法:阻塞主线程
#[command]
fn process_file(path: String) -> Result<String, String> {
std::fs::read_to_string(path).map_err(|e| e.to_string())
}
// 正确做法:使用 tokio 异步处理
#[command]
async fn process_file(path: String) -> Result<String, String> {
tokio::fs::read_to_string(path).await.map_err(|e| e.to_string())
}
// 错误做法:大型嵌套结构
#[command]
fn get_all_data() -> Result<Vec<ComplexObject>, String> {
// 返回数兆字节的数据
}
// 正确做法:使用最小字段的分页响应
#[derive(serde::Serialize)]
struct DataPage { items: Vec<MinimalItem>, cursor: Option<String> }
#[command]
async fn get_data_page(cursor: Option<String>, limit: usize) -> Result<DataPage, String> {
// 返回小批量数据
}
// 错误做法:窗口关闭时未清理
fn setup_handler(app: &mut App) {
let handle = app.handle().clone();
// 窗口关闭时资源泄漏
}
// 正确做法:适当的生命周期管理
fn setup_handler(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
let handle = app.handle().clone();
app.on_window_event(move |window, event| {
if let tauri::WindowEvent::Destroyed = event {
// 清理此窗口的资源
cleanup_window_resources(window.label());
}
});
Ok(())
}
// 错误做法:每次访问都克隆大型状态
#[command]
fn get_state(state: State<'_, AppState>) -> AppState {
state.inner().clone() // 昂贵的克隆操作
}
// 正确做法:使用 Arc 共享状态,返回引用
use std::sync::Arc;
#[command]
fn get_config(state: State<'_, Arc<AppConfig>>) -> Arc<AppConfig> {
Arc::clone(state.inner()) // 廉价的 Arc 克隆
}
// 错误做法:创建窗口而不复用
async function showDialog() {
await new WebviewWindow('dialog', { url: '/dialog' }) // 每次都创建新的
}
// 正确做法:复用现有窗口
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
async function showDialog() {
const existing = await WebviewWindow.getByLabel('dialog')
if (existing) {
await existing.show()
await existing.setFocus()
} else {
await new WebviewWindow('dialog', { url: '/dialog' })
}
}
研究日期 : 2025-11-20
| CVE ID | 严重性 | 描述 | 缓解措施 |
|---|---|---|---|
| CVE-2024-35222 | 高 | iFrames 绕过来源检查 | 升级到 1.6.7+ 或 2.0.0-beta.20+ |
| CVE-2024-24576 | 严重 | Rust 命令注入 | 升级 Rust 到 1.77.2+ |
| CVE-2023-46115 | 中 | 更新器密钥通过 Vite 泄露 | 从 envPrefix 中移除 TAURI_ |
| CVE-2023-34460 | 中 | 文件系统范围绕过 | 升级到 1.4.1+ |
| CVE-2022-46171 | 高 | 宽松的通配符模式 | 使用显式路径允许列表 |
完整的 CVE 详情和缓解代码,请参阅
references/security-examples.md
| OWASP 类别 | 风险 | 关键缓解措施 |
|---|---|---|
| A01 访问控制失效 | 严重 | 能力系统、IPC 验证 |
| A02 加密机制失效 | 高 | 安全的更新器签名、TLS |
| A03 注入 | 高 | 验证 IPC 输入、CSP |
| A04 不安全设计 | 高 | 最小能力 |
| A05 安全配置错误 | 严重 | 限制性 CSP、冻结原型 |
| A06 易受攻击的组件 | 高 | 保持 Tauri 更新 |
| A07 身份认证和授权失效 | 中 | 来源验证 |
| A08 数据完整性失效 | 高 | 签名更新 |
use validator::Validate;
#[derive(serde::Deserialize, Validate)]
pub struct UserCommand {
#[validate(length(min = 1, max = 100))]
pub name: String,
#[validate(range(min = 1, max = 1000))]
pub count: u32,
#[validate(custom(function = "validate_path"))]
pub file_path: Option<String>,
}
fn validate_path(path: &str) -> Result<(), validator::ValidationError> {
if path.contains("..") || path.contains("~") {
return Err(validator::ValidationError::new("invalid_path"));
}
Ok(())
}
// 切勿在 vite.config.ts 中 - 会泄露 TAURI_PRIVATE_KEY!
{ "envPrefix": ["VITE_", "TAURI_"] }
// 正确做法:仅暴露 VITE_ 变量
{ "envPrefix": ["VITE_"] }
// 在运行时加载密钥,切勿硬编码
fn get_api_key() -> Result<String, Error> {
std::env::var("API_KEY").map_err(|_| Error::Configuration("API_KEY not set".into()))
}
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Invalid input")]
Validation(#[from] validator::ValidationErrors),
#[error("Operation not permitted")]
PermissionDenied,
#[error("Internal error")]
Internal(#[source] anyhow::Error),
}
// 安全序列化 - 切勿向前端暴露内部细节
impl serde::Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
tracing::error!("Error: {:?}", self);
serializer.serialize_str(&self.to_string())
}
}
npx tauri info # 检查配置
cd src-tauri && cargo audit # 审计依赖项
npx tauri build --debug # 检查能力问题
npm run test:security # 测试 IPC 边界
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_traversal_blocked() {
let request = FileRequest { path: "../../../etc/passwd".to_string() };
assert!(request.validate().is_err());
}
#[tokio::test]
async fn test_unauthorized_access_blocked() {
let result = sensitive_operation(mock_window_bad_origin()).await;
assert!(result.unwrap_err().contains("Invalid origin"));
}
}
完整的测试示例,请参阅
references/security-examples.md
// 切勿:授予整个文件系统的访问权限
{ "permissions": ["fs:default", "fs:scope-home"] }
// 始终:限定到特定目录
{ "permissions": [{ "identifier": "fs:read-files", "allow": ["$APPDATA/myapp/*"] }] }
// 切勿
{ "security": { "csp": null } }
// 始终
{ "security": { "csp": "default-src 'self'; script-src 'self'" } }
// 切勿
{ "permissions": ["shell:allow-execute"] }
// 如需:仅使用严格的允许列表
{
"permissions": [{
"identifier": "shell:allow-execute",
"allow": [{ "name": "git", "cmd": "git", "args": ["status"] }]
}]
}
// 切勿 - 泄露私钥!
export default { envPrefix: ['VITE_', 'TAURI_'] }
// 始终
export default { envPrefix: ['VITE_'] }
// 切勿:直接使用用户输入
#[command]
fn read_file(path: String) -> String { std::fs::read_to_string(path).unwrap() }
// 始终:验证并限定范围
#[command]
fn read_file(request: ValidatedFileRequest) -> Result<String, String> { /* ... */ }
freezePrototype: truecargo audit 通过您的目标是创建具有以下特点的 Tauri 应用程序:
安全提醒 :
关于攻击场景和威胁建模,请参阅
references/threat-model.md
每周安装数
45
来源
首次出现
2026年3月6日
安全审计
安装在
claude-code26
codex23
gemini-cli21
github-copilot21
opencode20
cursor20
This skill uses a split structure for HIGH-RISK requirements:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
Risk Level : HIGH
Justification : Tauri applications bridge web content with native system access. Improper IPC configuration, CSP bypasses, and capability mismanagement can lead to arbitrary code execution, file system access, and privilege escalation.
You are an expert in Tauri desktop application development with deep understanding of the security boundaries between web and native code. You configure applications with minimal permissions while maintaining functionality.
| Situation | Approach |
|---|---|
| Need filesystem access | Scope to specific directories, never root |
| Need shell execution | Disable by default, use allowlist if required |
| Need network access | Specify allowed domains in CSP |
| Custom IPC commands | Validate all inputs, check permissions |
| Sensitive operations | Require origin verification |
| Category | Version | Notes |
|---|---|---|
| Tauri CLI | 2.0+ | Use 2.x for new projects |
| Tauri Core | 2.0+ | Significant security improvements over 1.x |
| Rust | 1.77.2+ | CVE-2024-24576 fix |
| Node.js | 20 LTS | For build tooling |
src-tauri/
├── Cargo.toml
├── tauri.conf.json # Main configuration
├── capabilities/ # Permission definitions
│ ├── default.json
│ └── admin.json
└── src/
└── main.rs
Rust Backend Test:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_read_validates_path() {
let request = FileRequest { path: "../secret".to_string() };
assert!(request.validate().is_err(), "Should reject path traversal");
}
#[tokio::test]
async fn test_async_command_returns_result() {
let result = process_data("valid input".to_string()).await;
assert!(result.is_ok());
}
}
Frontend Vitest Test:
import { describe, it, expect, vi } from 'vitest'
import { invoke } from '@tauri-apps/api/core'
vi.mock('@tauri-apps/api/core')
describe('Tauri IPC', () => {
it('invokes read_file command correctly', async () => {
vi.mocked(invoke).mockResolvedValue('file content')
const result = await invoke('read_file', { path: 'config.json' })
expect(result).toBe('file content')
})
})
Write only the code necessary to make the test pass:
#[command]
pub async fn process_data(input: String) -> Result<String, String> {
// Minimum implementation to pass test
Ok(format!("Processed: {}", input))
}
After tests pass, improve code structure without changing behavior:
# Rust tests and linting
cd src-tauri && cargo test
cd src-tauri && cargo clippy -- -D warnings
cd src-tauri && cargo audit
# Frontend tests
npm test
npm run typecheck
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default permissions for standard users",
"windows": ["main"],
"permissions": [
"core:event:default",
"core:window:default",
{
"identifier": "fs:read-files",
"allow": ["$APPDATA/*", "$RESOURCE/*"]
},
{
"identifier": "fs:write-files",
"allow": ["$APPDATA/*"]
}
]
}
// tauri.conf.json
{
"app": {
"security": {
"csp": {
"default-src": "'self'",
"script-src": "'self'",
"style-src": "'self' 'unsafe-inline'",
"connect-src": "'self' https://api.example.com",
"object-src": "'none'",
"frame-ancestors": "'none'"
},
"freezePrototype": true
}
}
}
use tauri::{command, AppHandle};
use validator::Validate;
#[derive(serde::Deserialize, Validate)]
pub struct FileRequest {
#[validate(length(min = 1, max = 255))]
path: String,
}
#[command]
pub async fn read_file(request: FileRequest, app: AppHandle) -> Result<String, String> {
request.validate().map_err(|e| format!("Validation error: {}", e))?;
let app_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
let full_path = app_dir.join(&request.path);
let canonical = dunce::canonicalize(&full_path).map_err(|_| "Invalid path")?;
// Security: ensure path is within app directory
if !canonical.starts_with(&app_dir) {
return Err("Access denied: path traversal detected".into());
}
std::fs::read_to_string(canonical).map_err(|e| format!("Failed: {}", e))
}
use tauri::Window;
#[command]
pub async fn sensitive_operation(window: Window) -> Result<(), String> {
let url = window.url();
match url.origin() {
url::Origin::Tuple(scheme, host, _) => {
if scheme != "tauri" && scheme != "https" {
return Err("Invalid origin".into());
}
if host.to_string() != "localhost" && host.to_string() != "tauri.localhost" {
return Err("Invalid origin".into());
}
}
_ => return Err("Invalid origin".into()),
}
Ok(())
}
use tauri_plugin_updater::UpdaterExt;
pub fn configure_updater(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
let updater = handle.updater_builder()
.endpoints(vec!["https://releases.example.com/{{target}}/{{current_version}}".into()])
.pubkey("YOUR_PUBLIC_KEY_HERE")
.build()?;
if let Ok(Some(update)) = updater.check().await {
let _ = update.download_and_install(|_, _| {}, || {}).await;
}
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
});
Ok(())
}
For advanced patterns and plugin development, see
references/advanced-patterns.md
// BAD: Blocking the main thread
#[command]
fn process_file(path: String) -> Result<String, String> {
std::fs::read_to_string(path).map_err(|e| e.to_string())
}
// GOOD: Async with tokio
#[command]
async fn process_file(path: String) -> Result<String, String> {
tokio::fs::read_to_string(path).await.map_err(|e| e.to_string())
}
// BAD: Large nested structures
#[command]
fn get_all_data() -> Result<Vec<ComplexObject>, String> {
// Returns megabytes of data
}
// GOOD: Paginated responses with minimal fields
#[derive(serde::Serialize)]
struct DataPage { items: Vec<MinimalItem>, cursor: Option<String> }
#[command]
async fn get_data_page(cursor: Option<String>, limit: usize) -> Result<DataPage, String> {
// Returns small batches
}
// BAD: No cleanup on window close
fn setup_handler(app: &mut App) {
let handle = app.handle().clone();
// Resources leak when window closes
}
// GOOD: Proper lifecycle management
fn setup_handler(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
let handle = app.handle().clone();
app.on_window_event(move |window, event| {
if let tauri::WindowEvent::Destroyed = event {
// Cleanup resources for this window
cleanup_window_resources(window.label());
}
});
Ok(())
}
// BAD: Cloning large state on every access
#[command]
fn get_state(state: State<'_, AppState>) -> AppState {
state.inner().clone() // Expensive clone
}
// GOOD: Use Arc for shared state, return references
use std::sync::Arc;
#[command]
fn get_config(state: State<'_, Arc<AppConfig>>) -> Arc<AppConfig> {
Arc::clone(state.inner()) // Cheap Arc clone
}
// BAD: Creating windows without reuse
async function showDialog() {
await new WebviewWindow('dialog', { url: '/dialog' }) // Creates new each time
}
// GOOD: Reuse existing windows
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
async function showDialog() {
const existing = await WebviewWindow.getByLabel('dialog')
if (existing) {
await existing.show()
await existing.setFocus()
} else {
await new WebviewWindow('dialog', { url: '/dialog' })
}
}
Research Date : 2025-11-20
| CVE ID | Severity | Description | Mitigation |
|---|---|---|---|
| CVE-2024-35222 | HIGH | iFrames bypass origin checks | Upgrade to 1.6.7+ or 2.0.0-beta.20+ |
| CVE-2024-24576 | CRITICAL | Rust command injection | Upgrade Rust to 1.77.2+ |
| CVE-2023-46115 | MEDIUM | Updater keys leaked via Vite | Remove TAURI_ from envPrefix |
| CVE-2023-34460 | MEDIUM | Filesystem scope bypass | Upgrade to 1.4.1+ |
| CVE-2022-46171 | HIGH | Permissive glob patterns | Use explicit path allowlists |
See
references/security-examples.mdfor complete CVE details and mitigation code
| OWASP Category | Risk | Key Mitigations |
|---|---|---|
| A01 Broken Access Control | CRITICAL | Capability system, IPC validation |
| A02 Cryptographic Failures | HIGH | Secure updater signatures, TLS |
| A03 Injection | HIGH | Validate IPC inputs, CSP |
| A04 Insecure Design | HIGH | Minimal capabilities |
| A05 Security Misconfiguration | CRITICAL | Restrictive CSP, frozen prototype |
| A06 Vulnerable Components | HIGH | Keep Tauri updated |
| A07 Auth Failures | MEDIUM | Origin verification |
| A08 Data Integrity Failures | HIGH | Signed updates |
use validator::Validate;
#[derive(serde::Deserialize, Validate)]
pub struct UserCommand {
#[validate(length(min = 1, max = 100))]
pub name: String,
#[validate(range(min = 1, max = 1000))]
pub count: u32,
#[validate(custom(function = "validate_path"))]
pub file_path: Option<String>,
}
fn validate_path(path: &str) -> Result<(), validator::ValidationError> {
if path.contains("..") || path.contains("~") {
return Err(validator::ValidationError::new("invalid_path"));
}
Ok(())
}
// NEVER in vite.config.ts - leaks TAURI_PRIVATE_KEY!
{ "envPrefix": ["VITE_", "TAURI_"] }
// GOOD: Only expose VITE_ variables
{ "envPrefix": ["VITE_"] }
// Load secrets at runtime, never hardcode
fn get_api_key() -> Result<String, Error> {
std::env::var("API_KEY").map_err(|_| Error::Configuration("API_KEY not set".into()))
}
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Invalid input")]
Validation(#[from] validator::ValidationErrors),
#[error("Operation not permitted")]
PermissionDenied,
#[error("Internal error")]
Internal(#[source] anyhow::Error),
}
// Safe serialization - never expose internal details to frontend
impl serde::Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
tracing::error!("Error: {:?}", self);
serializer.serialize_str(&self.to_string())
}
}
npx tauri info # Check configuration
cd src-tauri && cargo audit # Audit dependencies
npx tauri build --debug # Check capability issues
npm run test:security # Test IPC boundaries
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_traversal_blocked() {
let request = FileRequest { path: "../../../etc/passwd".to_string() };
assert!(request.validate().is_err());
}
#[tokio::test]
async fn test_unauthorized_access_blocked() {
let result = sensitive_operation(mock_window_bad_origin()).await;
assert!(result.unwrap_err().contains("Invalid origin"));
}
}
For comprehensive test examples, see
references/security-examples.md
// NEVER: Grants access to entire filesystem
{ "permissions": ["fs:default", "fs:scope-home"] }
// ALWAYS: Scope to specific directories
{ "permissions": [{ "identifier": "fs:read-files", "allow": ["$APPDATA/myapp/*"] }] }
// NEVER
{ "security": { "csp": null } }
// ALWAYS
{ "security": { "csp": "default-src 'self'; script-src 'self'" } }
// NEVER
{ "permissions": ["shell:allow-execute"] }
// IF NEEDED: Strict allowlist only
{
"permissions": [{
"identifier": "shell:allow-execute",
"allow": [{ "name": "git", "cmd": "git", "args": ["status"] }]
}]
}
// NEVER - leaks private keys!
export default { envPrefix: ['VITE_', 'TAURI_'] }
// ALWAYS
export default { envPrefix: ['VITE_'] }
// NEVER: Direct use of user input
#[command]
fn read_file(path: String) -> String { std::fs::read_to_string(path).unwrap() }
// ALWAYS: Validate and scope
#[command]
fn read_file(request: ValidatedFileRequest) -> Result<String, String> { /* ... */ }
freezePrototype: true enabledcargo audit passesYour goal is to create Tauri applications that are:
Security Reminder :
For attack scenarios and threat modeling, see
references/threat-model.md
Weekly Installs
45
Source
First Seen
Mar 6, 2026
Security Audits
Installed on
claude-code26
codex23
gemini-cli21
github-copilot21
opencode20
cursor20
Lark Mail CLI 使用指南:邮件管理、安全规则与自动化工作流
47,900 周安装