rust-strict — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited rust-strict (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.
Security and strictness rules complementing the existing rust-skills (179 rules). These rules are derived from 5 production Rust projects.
Every Rust project must configure workspace lints. Baseline:
# Cargo.toml (workspace root)
[workspace.lints.rust]
unsafe_code = "deny" # No unsafe in production code
unused_qualifications = "deny"
[workspace.lints.clippy]
unwrap_used = "deny" # Force proper error handling
expect_used = "deny" # Same: use ? or ok_or_else()Per-crate opt-in:
# crates/my-crate/Cargo.toml
[lints]
workspace = true// FFI crate only: deny everywhere else
#![cfg_attr(feature = "local-llm", allow(unsafe_code))]
// Static initialization (regex, lazy): this is the ONLY acceptable expect()
static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"...").expect("regex must compile") // Bug if this fails
});thiserror, app crates use anyhow// Library crate: structured errors
#[derive(Debug, thiserror::Error)]
pub enum GatewayError {
#[error("authentication required")]
AuthRequired,
#[error("rate limited: retry after {retry_after_ms}ms")]
RateLimited { retry_after_ms: u64 },
#[error("payload too large: {size} exceeds {max} bytes")]
PayloadTooLarge { size: usize, max: usize },
}
impl GatewayError {
pub fn status_code(&self) -> StatusCode { /* match self */ }
pub fn is_retryable(&self) -> bool { matches!(self, Self::RateLimited { .. }) }
}
// App crate: flexible propagation
fn main() -> anyhow::Result<()> {
let config = load_config().context("failed to load configuration")?;
Ok(())
}Result<T, String>#[tauri::command]
pub async fn my_command(
state: tauri::State<'_, AppState>,
) -> Result<ResponseData, String> {
let data = do_work().map_err(|e| format!("operation failed: {e}"))?;
Ok(data)
}Tauri serializes errors as strings: this is the framework constraint, not a hack.
Box<dyn Error>: use domain-specific enums// BAD
fn process() -> Result<(), Box<dyn std::error::Error>> { ... }
// GOOD
fn process() -> Result<(), GatewayError> { ... }// 1. SIMD intrinsics with runtime feature detection
#[target_feature(enable = "avx2,fma")]
unsafe fn cosine_similarity_avx2(a: &[f32], b: &[f32]) -> f64 {
// SAFETY: AVX2 feature verified by caller via is_x86_feature_detected!
let va = unsafe { _mm256_loadu_ps(a_ptr.add(offset)) };
// offset < chunks*8 <= n: bounds checked by loop structure
}
// 2. Test-only env manipulation (with guard lock)
#[cfg(test)]
fn test_env_var() {
let _guard = env_lock(); // Prevents test race conditions
// SAFETY: env_lock serializes all env var access in tests
unsafe { std::env::set_var("KEY", "value") };
}transmute (almost always wrong: use From/Into)Send/Sync manually (unless wrapping C FFI)unsafe impl without audited invariants.await// BAD: deadlock risk
let mut data = state.data.write().await;
data.insert(key, value);
let json = serde_json::to_string(&*data)?;
tokio::fs::write(path, json).await; // STILL LOCKED
// GOOD: clone, drop, then write
let json = {
let mut data = state.data.write().await;
data.insert(key, value);
serde_json::to_string(&*data)?
}; // Lock dropped
tokio::fs::write(path, json).await;// BAD
let mut cookies = state.cookies.write().await;
cookies.insert(name, result);
std::fs::write(&path, serde_json::to_string(&*cookies)?); // SYNC I/O WHILE LOCKED
// GOOD
let json = {
let mut cookies = state.cookies.write().await;
cookies.insert(name, result);
serde_json::to_string(&*cookies)?
};
tokio::fs::write(&path, json).await;pub struct AppState {
// Atomics: lock-free for simple state
pub is_running: AtomicBool,
pub connection_count: AtomicUsize,
// RwLock: for collections and complex data
pub sessions: RwLock<HashMap<String, Session>>,
// Watch channel: for shutdown signaling
pub shutdown: watch::Sender<bool>,
}// BAD: unbounded HashMap cache
let cache: HashMap<String, CachedValue> = HashMap::new();
// GOOD: bounded with capacity + TTL
let cache = BoundedMap::new(1000, Duration::from_secs(300));// macOS: system `security` CLI (subprocess, not FFI)
#[cfg(target_os = "macos")]
fn keychain_get(account: &str) -> Result<Option<String>, String> {
let output = Command::new("security")
.args(["find-generic-password", "-s", "AppName", "-a", account, "-w"])
.output()
.map_err(|e| format!("keychain read failed: {e}"))?;
// ...
}
// Fallback: Tauri plugin-store (encrypted)
#[cfg(not(target_os = "macos"))]
fn save_fallback(app: &AppHandle, name: &str, value: &str) -> Result<(), String> { ... }obfstr!() for hardcoded strings in binary// BAD: visible in binary strings
let api_url = "https://api.internal.example.com/v2";
// GOOD: obfuscated at compile time
let api_url = obfstr::obfstr!("https://api.internal.example.com/v2");pub fn save_secret(name: &str, value: &str) -> Result<(), String> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err("Secret value cannot be empty".to_string());
}
// Proceed with trimmed value
}// BAD: arbitrary string input
#[tauri::command]
fn spawn_agent(agent: String) -> Result<(), String> {
Command::new(&agent).spawn(); // COMMAND INJECTION
}
// GOOD: enum-constrained
#[derive(Debug, Clone, Deserialize)]
pub enum AgentType { Claude, Codex, Opencode }
#[tauri::command]
fn spawn_agent(agent: AgentType) -> Result<(), String> {
let binary = match agent {
AgentType::Claude => "claude",
AgentType::Codex => "codex",
AgentType::Opencode => "opencode",
};
// Safe: only known values
}tempfile crate for temp files// BAD: predictable path, symlink attacks
std::fs::write("/tmp/prompt.txt", prompt)?;
// GOOD: unpredictable, auto-cleanup
let mut tmp = tempfile::NamedTempFile::new()
.map_err(|e| format!("temp file failed: {e}"))?;
writeln!(tmp, "{}", prompt)?;
let path = tmp.path().to_string_lossy().to_string();# BAD: pulls in everything
reqwest = "0.12"
# GOOD: only what you need
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] }rustls-tls over native-tlsPure Rust TLS stack: no OpenSSL dependency, auditable, consistent behavior.
# Workspace Cargo.toml
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
# Crate Cargo.toml
[dependencies]
tokio = { workspace = true }
serde = { workspace = true }// BAD: any UUID can be passed where any ID is expected
pub fn ban_user(uid: Uuid, by: Uuid) { ... }
// GOOD: type system rejects swapped arguments
pub struct UserId(Uuid);
pub struct AdminId(Uuid);
pub fn ban_user(uid: UserId, by: AdminId) { ... }Same pattern for units: Bytes(u64), Millis(u64), RetryCount(u32).
#[must_use] on results that should not be silently dropped#[must_use = "builder is incomplete until .build() is called"]
pub struct RequestBuilder { ... }
#[must_use]
pub fn try_acquire(&self) -> Option<Lease> { ... }Result<T, E> already has #[must_use]. Add it to builders, locks, and validation outcomes.
// BAD: wraps silently in release mode
let next = current + delta;
// GOOD: pick the semantic you mean
let next = current.checked_add(delta).ok_or(Error::Overflow)?; // fail
let next = current.saturating_add(delta); // clamp
let next = current.wrapping_add(delta); // wrap (rare, document why)Default to checked_* for anything user-influenced (sizes, counts, indices).
Edition 2024 ships with Rust 1.85 (Feb 2025). Current stable is 1.95.0. New crates start at edition 2024 with current stable. Migration changes that affect strict-mode rules:
unsafe extern "C" blocks// BAD: edition 2024 hard error
extern "C" { fn external_fn(); }
// GOOD
unsafe extern "C" {
fn external_fn();
}Forces the FFI declaration site to acknowledge unsafety.
unsafe_op_in_unsafe_fn is warn-by-defaultInside unsafe fn, every individual unsafe operation now needs its own unsafe { ... } block. This makes the audit surface explicit, do not bypass with #[allow].
use<...> on RPIT// Edition 2024: opaque return types capture all generic params by default
fn parse(s: &str) -> impl Iterator<Item = &str> + use<'_> { ... }The use<> syntax is the escape hatch for the new capture rules. Reach for it when the inferred capture set is wrong.
gen is a reserved keywordIf you have variables, functions, or modules named gen, rename them before migrating.
tracing for structured logs, not log or env_loggeruse tracing::{info, instrument, warn};
#[instrument(skip(state), fields(user_id = %req.user_id))]
async fn handler(state: AppState, req: Request) -> Result<Response, Error> {
info!("processing request");
...
}tracing is the de-facto standard. Pair with tracing-subscriber for output and tracing-opentelemetry for distributed tracing.
[profile.release]
strip = true
lto = true # Full LTO
codegen-units = 1
opt-level = 3
panic = "abort" # No unwinding[profile.release]
strip = true
lto = "thin" # Faster compile than full LTO
codegen-units = 1[profile.dev.package."*"]
opt-level = 3 # Optimize deps in dev (faster runtime)unsafe blocks without SAFETY comments and justification.unwrap() or .expect() in production (deny via lint)tempfile crate).await or disk I/Otauri.conf.json (for Tauri apps)cargo audit passes with no advisoriescargo clippy -- -D warnings clean~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.