audit-rust-backend
Audit Rust backend for mutex issues, panics, process management, and concurrency
Works with
---
name: audit-rust-backend
description: Audit Rust backend for mutex issues, panics, process management, and concurrency
license: MIT
---
# Audit: Rust Backend
Audit the Rust backend (`src-tauri/`) for correctness and robustness. This is a research-only audit — do not modify any code.
## What to Search For
### Mutex Contention & Deadlocks
- **Long-held locks:** Find `.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.
- **Nested locks:** Find functions that acquire two or more locks. Check if another code path acquires them in the opposite order (deadlock potential).
- **Locks across `.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.
- **std `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).
- **Long-held locks pinning shared resources:** Beyond I/O, flag a guard held across a multi-second/minutes-long compute section (e.g. whole-file inference) — it serializes work the product markets as concurrent and pins the cached resource for the duration.
- **Mutex type selection:** Verify `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`).
### Panic Vectors
Find `.unwrap()` and `.expect()` on `Result` and `Option` values. For each:
- Is the value guaranteed to be `Ok`/`Some` at that point? (e.g., just matched on it)
- Could it reasonably fail at runtime? (e.g., file I/O, network, system calls, parsing)
- Is it in a test context (acceptable) or production code (flag it)?
### Process Management
Check spawned subprocesses for:
- `kill_on_drop(true)` — process killed when handle drops
- Cleanup in `RunEvent::Exit` hook
- Orphan recovery on startup (e.g., `pkill` stale processes)
- PID tracking accuracy — registered on spawn, unregistered on exit
- Zombie prevention — stdout/stderr consumed or dropped
### Error Context
Check `Result<T, String>` error messages:
- Do they include what operation failed?
- Do they include the underlying OS/library error?
- Would a developer be able to diagnose the issue from the error message alone?
### Allocations Sized By Untrusted Input
- **Allocation from a wire-supplied length:** Find `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.
- **Unbounded body / output buffering:** Find `resp.text()`, `response.json()`, `read_to_end`, and reader loops with no byte cap. Stream with a running-total cap instead.
- Find unnecessary `.clone()` on large `String` or `Vec<u8>` values.
- Find unbounded `Vec` accumulation in loops without capacity hints.
- Check for double reads (reading the same resource twice instead of binding the result).
### SSRF & Host-Supplied Fetches
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:
- **No private-IP guard:** Verify the resolved host is rejected if it is loopback (`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.
- **Redirects re-resolve:** A `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.
- **Response returned to caller:** If `<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).
- **No body cap:** `resp.text()` on a hostile page is also a memory-exhaustion amplifier (see Allocations).
### Archive Extraction & Download Integrity
For any code that extracts a downloaded archive (tar/zip) to disk:
- **Path traversal (Tar-Slip):** Reject entries whose path contains `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).
- **Attacker-controlled mode bits:** Mask permissions from the archive (`& 0o755`) — never apply `set_permissions` with the raw archive mode (setuid / world-writable risk).
- **No integrity check:** Flag a download with no pinned SHA-256 / signature verification before extraction, even over HTTPS — defense-in-depth against an origin compromise or TLS MITM.
### Parser Recursion & Slice/Index Panics
For recursive parsers (binary headers, nested structures) fed by files or downloads the user can point at:
- **Unbounded recursion depth:** Per-element/per-length caps are not enough — a nested-container input (array-of-array-of-…) drives recursion to stack overflow, which aborts the process (panic-on-overflow is NOT catchable at the command boundary). Thread a depth counter and reject beyond a small limit (~8). (e.g. a GGUF `read_gguf_value` that recurses on array types with per-array caps but no depth bound, reachable via a custom-models config file.)
- Check slice indexing / `[..n]` and arithmetic on header-derived sizes for overflow.
### Streamed-Read Parsing Assumptions
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.)
### PID-Reuse Safety in Cleanup
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.
### Unsafe Code
Find all `unsafe` blocks. For each:
- Is it necessary? Could safe Rust achieve the same thing?
- Is the invariant documented?
- Is it properly gated behind `#[cfg(...)]` if platform-specific?
### Concurrency
- Find shared mutable state not wrapped in `Mutex`, `RwLock`, or atomic types
- Check `Arc` usage — is the inner type `Send + Sync`?
- Find `static mut` (should never exist)
### Severity calibration for Tauri-v2 backends
- **`#[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.
- **kill_on_drop completeness.** Verify the cleanup contract is real, not implicit. If the spawned `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).
- **Orphaned reader tasks on timeout.** When a child is killed on a timeout, verify the spawned stdout/stderr reader tasks are awaited or aborted — not left to leak until the killed child's pipes close.
- **Blocking calls on async threads.** Flag synchronous/long-blocking compute (FFI inference, blocking I/O) executed directly in an async fn / under a tokio `Mutex` without `spawn_blocking` — it stalls the runtime worker.
- **Unbounded channels.** Flag `unbounded_channel()` fed by a source the peer/subprocess controls (no backpressure → memory growth); prefer a bounded channel.
- **Shell vs. AppleScript escaping conflation.** When building a command string passed through two layers (shell + `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.
## Output Format
For each finding:
```markdown
### <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:
- A GGUF parser's 10 MB string cap (good allocation bound).
- `parking_lot::Mutex` in `index/` and `watcher` (no poisoning).
- ACP `stop_all_sync` (SIGKILL by PID + bounded join) and MCP `start_kill` at exit (good cleanup).
- Provably-safe guarded `.unwrap()`s (e.g. on a value just matched `Some`) — do not flag.
## Example Finding
### MEDIUM: Panic on tokio runtime creation
**File:** `src-tauri/src/commands/acp.rs:411`
```rust
.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`:
```rust
.map_err(|e| format!("Failed to create tokio runtime: {e}"))?;
```More Backend Frameworks skills
git-guardrails-claude-code
mattpocock/skills
Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.
azure-compute
microsoft/azure-skills
Azure VM/VMSS router. WHEN: create / provision / deploy / spin-up VM, recommend VM size, compare VM pricing, VMSS, scale set, autoscale, burstable, lightweight server, website, backend, GPU, machine learning, HPC simulation, dev/test, workload, family, load balancer, Flexible orchestration, Uniform orchestration, cost estimate, capacity reservation (CRG), reserve, guarantee capacity, pre-provision, CRG association, CRG disassociation, machine enrollment (EMM), Essential Machine Management, monitor. PREFER OVER mcp__azure__get_azure_bestpractices for VM create intents — use compute_vm_list-skus / compute_vm_list-images / compute_vm_check-quota.
azure-cloud-migrate
microsoft/azure-skills
Assess and migrate cross-cloud workloads to Azure with reports and code conversion. Supports Lambda→Functions, Beanstalk/Heroku/App Engine→App Service, Fargate/Kubernetes/Cloud Run/Spring Boot→Container Apps. WHEN: migrate Lambda to Functions, AWS to Azure, migrate Beanstalk, migrate Heroku, migrate App Engine, Cloud Run migration, Fargate to ACA, ECS/Kubernetes/GKE/EKS to Container Apps, Spring Boot to Container Apps, cross-cloud migration.

