一键导入
audit-rust-backend
Audit Rust backend for mutex issues, panics, process management, and concurrency
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Audit Rust backend for mutex issues, panics, process management, and concurrency
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Fill missing type, title, and description frontmatter on documents using structured AI output
Download a web page by URL and save it as clean markdown with images
Audit for accessibility — keyboard navigation, ARIA labels, contrast, focus indicators
Produce a full dependency health report (SBOM, vulnerabilities, staleness, upgrades, licenses)
Audit error handling UX — error boundaries, silent failures, loading states, empty states
Audit for unnecessary re-renders — Zustand subscriptions, missing memoization, inline callbacks
| name | audit-rust-backend |
| description | Audit Rust backend for mutex issues, panics, process management, and concurrency |
| user-invocable | true |
Audit the Rust backend (src-tauri/) for correctness and robustness. This is a research-only audit — do not modify any code.
.lock() calls (both std::sync::Mutex and tokio::sync::Mutex). Check if I/O, network calls, or other slow operations happen while the lock is held..await: Find Mutex guards held across .await points. std::sync::Mutex guards must NOT be held across awaits (!Send guard breaks the future). tokio::sync::Mutex is safe across awaits but may still cause contention.Mutex poisoning across panics: A std::sync::Mutex is poisoned permanently if any code under the guard panics — every later .lock() then returns Err until the app restarts. Flag any std Mutex whose guard wraps code that can panic (FFI calls, parsing, indexing). Prefer parking_lot::Mutex (no poisoning — the convention already used in index/ and watcher.rs) or recover via .lock().unwrap_or_else(|e| e.into_inner()). This is a higher-priority finding in this codebase than the await case (e.g. a whisper_ctx std Mutex held across a whole-file Whisper pass poisons on an FFI panic).parking_lot::Mutex or std::sync::Mutex in sync contexts, tokio::sync::Mutex in async contexts. In Notesage: parking_lot for hot sync state (index/, watcher), tokio Mutex for async lifecycle state (acp, mcp, network_proxy).Find .unwrap() and .expect() on Result and Option values. For each:
Ok/Some at that point? (e.g., just matched on it)Check spawned subprocesses for:
kill_on_drop(true) — process killed when handle dropsRunEvent::Exit hookpkill stale processes)Check Result<T, String> error messages:
vec![0u8; n] / Vec::with_capacity(n) / String reads where n comes from a subprocess, socket, or file header. Verify an explicit upper bound is enforced before the allocation — a timeout wrapping the subsequent read does NOT help, the allocation already happened. Reference good pattern: a GGUF parser capping strings at 10 MB; reference bad pattern: a JSON-RPC Content-Length body alloc with no cap.resp.text(), response.json(), read_to_end, and reader loops with no byte cap. Stream with a running-total cap instead..clone() on large String or Vec<u8> values.Vec accumulation in loops without capacity hints.The agent network sandbox only gates subprocess traffic. Tauri commands that fetch a URL run in the main process, unsandboxed. For each command that calls reqwest/client.get() with a URL sourced from user paste, agent output, or document content:
127.0.0.0/8, ::1), link-local (169.254.0.0/16 — incl. cloud metadata 169.254.169.254), private RFC1918, or CGNAT. Missing guard = SSRF probe of internal services.redirect::Policy::limited(n) lets a public host bounce to an internal target. The private-IP check must run on every redirect hop via a custom redirect policy, not just the initial URL.<title>/<description>/body text flows back to the frontend, that is an info-leak channel for the probe (e.g. a fetch_link_metadata command that follows redirects and returns parsed OpenGraph metadata).resp.text() on a hostile page is also a memory-exhaustion amplifier (see Allocations).For any code that extracts a downloaded archive (tar/zip) to disk:
Component::ParentDir (..) or Component::RootDir, OR canonicalize the destination and assert dest.starts_with(extract_root). Stripping the top-level dir (components().skip(1)) does NOT prevent .. escape (e.g. a Node.js runtime tarball extraction that strips only the top dir and joins .. unchecked).& 0o755) — never apply set_permissions with the raw archive mode (setuid / world-writable risk).For recursive parsers (binary headers, nested structures) fed by files or downloads the user can point at:
read_gguf_value that recurses on array types with per-array caps but no depth bound, reachable via a custom-models config file.)[..n] and arithmetic on header-derived sizes for overflow.Flag protocol parsing that assumes a complete message arrives in one read() and fits a fixed buffer (e.g. let n = sock.read(&mut buf[..8192]).await? then parse). TCP delivers partial reads; oversized headers truncate. Loop until the framing delimiter (\r\n\r\n) with a max-size cap before parsing. (e.g. a network proxy that reads one 8 KB buffer and parses the request line/headers, truncating on partial or >8 KB requests.)
For cleanup paths that kill <pid> using a PID read from a .pid file on disk (startup orphan recovery), verify the PID still belongs to the expected binary before signalling — validate the command name (/proc/<pid>/comm on Linux, ps -o comm= on macOS) or store a start-timestamp alongside the PID. A crash + OS PID recycle otherwise signals an unrelated user process.
Find all unsafe blocks. For each:
#[cfg(...)] if platform-specific?Mutex, RwLock, or atomic typesArc usage — is the inner type Send + Sync?static mut (should never exist)#[tauri::command] panics are IPC errors, not process aborts — except allocator/overflow/stack panics. Most .unwrap() in a command body becomes a rejected promise to the frontend; do not flood the report with provably-guarded .unwrap()s (e.g. on a value just matched Some). DO prioritize panics the command boundary cannot contain: allocator panics (vec![0u8; huge]), arithmetic overflow, and stack overflow from unbounded recursion — these abort the whole app.Child is moved into a tokio::spawn(child.wait_with_output()) task, kill_on_drop(true) is defeated — the wait task pins the child alive and cleanup must rely on kill <pid>. Prefer keeping the Child in state so cleanup can start_kill() + wait() deterministically, draining pipes via taken handles. Check that RunEvent::Exit cleanup is exhaustive across every spawner (ACP, MCP, llama-server main + completion server).Mutex without spawn_blocking — it stalls the runtime worker.unbounded_channel() fed by a source the peer/subprocess controls (no backpressure → memory growth); prefer a bounded channel.osascript do script), one escaping model cannot satisfy both. Prefer building argv without a shell hop; if a string literal is unavoidable, assert callers pass only vetted constants and use one correct escaper.Note: "kill_on_drop", "RunEvent::Exit", "stdout/stderr consumed", and "PID tracking" already appear under Process Management above — the additions here deepen completeness/defeat judgment rather than duplicating the presence checks. Do not re-list the basic presence bullets.
For each finding:
### <SEVERITY>: <Short title>
**File:** `<path>:<line>`
<Description — what can go wrong and under what conditions.>
**Fix:** <Suggested fix.>
End with a ### Confirmed Good Patterns section — this is especially important for Rust since many patterns will be correct. Seed it with references already validated in this codebase so future runs don't re-flag them:
parking_lot::Mutex in index/ and watcher (no poisoning).stop_all_sync (SIGKILL by PID + bounded join) and MCP start_kill at exit (good cleanup)..unwrap()s (e.g. on a value just matched Some) — do not flag.File: src-tauri/src/commands/acp.rs:411
.expect("Failed to create tokio runtime for ACP agent");
Creating a tokio runtime can fail under resource exhaustion. This .expect() will panic, crashing the entire app instead of returning a graceful error to the frontend.
Fix: Replace with map_err:
.map_err(|e| format!("Failed to create tokio runtime: {e}"))?;