Agent skill
rust-safety
Rust safety patterns and secure coding. Use when writing code that handles untrusted input, uses unsafe blocks, deals with memory safety, or requires security review.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/rust-safety
SKILL.md
Rust Safety Guidelines
Based on Microsoft Pragmatic Rust Guidelines and Rust security best practices.
Unsafe Code Guidelines
When to Use Unsafe
Only use unsafe when:
- FFI (Foreign Function Interface) calls
- Performance-critical code where safe alternatives are too slow
- Implementing low-level abstractions that can't be expressed safely
Unsafe Block Requirements
// ALWAYS document safety invariants
/// # Safety
///
/// - `ptr` must be valid and properly aligned
/// - `ptr` must point to initialized memory
/// - The memory must not be accessed through any other pointer during this call
unsafe fn process_raw(ptr: *mut u8, len: usize) {
// SAFETY: Caller guarantees ptr validity and exclusive access
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, len) };
// ...
}
Minimize Unsafe Scope
// BAD - too much in unsafe block
unsafe {
let ptr = get_pointer();
let len = calculate_length(); // Safe operation in unsafe block
let slice = std::slice::from_raw_parts(ptr, len);
process(slice); // Safe operation in unsafe block
}
// GOOD - minimal unsafe scope
let ptr = get_pointer();
let len = calculate_length();
// SAFETY: ptr is valid for len bytes per get_pointer contract
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
process(slice);
Input Validation
File Existence Check (Rust 1.81+)
use std::fs;
// GOOD - explicit existence check
if fs::exists("config.toml")? {
let config = fs::read_to_string("config.toml")?;
}
// For optional files
let config = if fs::exists(&path)? {
Some(fs::read_to_string(&path)?)
} else {
None
};
Validate at Boundaries
/// Parses user-provided workflow configuration.
///
/// # Errors
///
/// Returns error if input exceeds size limits or contains invalid data.
pub fn parse_config(input: &str) -> Result<Config, ParseError> {
// Validate size first
if input.len() > MAX_CONFIG_SIZE {
return Err(ParseError::TooLarge {
size: input.len(),
max: MAX_CONFIG_SIZE
});
}
// Parse with timeout protection
let config: Config = serde_json::from_str(input)
.map_err(ParseError::InvalidJson)?;
// Validate parsed values
config.validate()?;
Ok(config)
}
Numeric Overflow Protection
// BAD - can panic or wrap
let result = a + b;
// GOOD - explicit handling
let result = a.checked_add(b).ok_or(Error::Overflow)?;
// Or use saturating for counters
let count = count.saturating_add(1);
// Or wrapping when intentional
let hash = hash.wrapping_mul(PRIME);
String Handling
// BAD - potential DoS with large allocations
let repeated = input.repeat(count);
// GOOD - validate first
if count > MAX_REPEAT || input.len().saturating_mul(count) > MAX_SIZE {
return Err(Error::TooLarge);
}
let repeated = input.repeat(count);
Memory Safety
Avoid Use-After-Free
// BAD - reference may outlive data
struct Handler<'a> {
data: &'a str,
}
// GOOD - owned data
struct Handler {
data: String,
}
// OR - explicit lifetime with clear ownership
struct Handler<'a> {
data: &'a str,
_marker: PhantomData<&'a ()>,
}
Prevent Data Races
use std::sync::Arc;
use parking_lot::RwLock; // Prefer over std::sync::Mutex
// Thread-safe shared state
struct SharedState {
data: Arc<RwLock<Data>>,
}
impl SharedState {
fn update(&self, new_data: Data) {
let mut guard = self.data.write();
*guard = new_data;
// Lock released here
}
fn read(&self) -> Data {
self.data.read().clone()
}
}
Error Handling Safety
Don't Expose Internal Details
// BAD - leaks internal paths and structure
#[error("Failed to read {path}: {source}")]
ReadError { path: PathBuf, source: std::io::Error }
// GOOD - sanitized error (user sees generic message, details for logging)
#[error("Failed to read configuration file")]
ConfigReadError {
path: PathBuf, // Keep for internal logging
#[source]
source: std::io::Error,
}
impl ConfigReadError {
/// Returns internal details for logging (not user-facing).
pub fn internal_details(&self) -> String {
format!("path: {:?}, error: {}", self.path, self.source)
}
}
Fail Securely
// BAD - returns partial data on error
fn load_secrets() -> Vec<Secret> {
let mut secrets = Vec::new();
for path in paths {
if let Ok(s) = load_secret(path) {
secrets.push(s);
}
// Silently ignores errors
}
secrets
}
// GOOD - fail completely or succeed completely
fn load_secrets() -> Result<Vec<Secret>, Error> {
paths.iter()
.map(load_secret)
.collect::<Result<Vec<_>, _>>()
}
Cryptography
Use High-Level Libraries
// GOOD - use established libraries
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use rand::rngs::OsRng;
fn hash_password(password: &str) -> Result<String, Error> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
Ok(argon2.hash_password(password.as_bytes(), &salt)?.to_string())
}
fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
let parsed = PasswordHash::new(hash)?;
Ok(Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok())
}
Secure Random Generation
use rand::{rngs::OsRng, RngCore};
fn generate_token() -> [u8; 32] {
let mut token = [0u8; 32];
OsRng.fill_bytes(&mut token);
token
}
Denial of Service Prevention
Resource Limits
/// Process with bounded resource usage.
pub async fn process_with_limits(
input: &[u8],
limits: &Limits,
) -> Result<Output, Error> {
// Size limit
if input.len() > limits.max_input_size {
return Err(Error::InputTooLarge);
}
// Time limit
tokio::time::timeout(limits.max_duration, async {
process_inner(input).await
})
.await
.map_err(|_| Error::Timeout)?
}
Prevent Regex DoS
use regex::Regex;
// BAD - user-provided regex
let re = Regex::new(user_input)?;
// GOOD - pre-compiled patterns or validated
use std::sync::LazyLock;
use regex::Regex;
const ALLOWED_PATTERN: &str = r"^[a-zA-Z0-9_-]+$";
static NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(ALLOWED_PATTERN).unwrap()
});
fn validate_name(name: &str) -> bool {
NAME_REGEX.is_match(name)
}
Verification Commands
# Security audit
cargo audit
# Check for unsafe code
cargo geiger
# Clippy security lints
cargo clippy -- -W clippy::unwrap_used -W clippy::expect_used
# Miri for undefined behavior (nightly)
cargo +nightly miri test
Common Safety Issues Reference
Memory Safety Issues
- Stack/Heap overflow - exceeding memory limits
- Integer overflow/underflow - use
checked_*,saturating_*,wrapping_* - Null pointer dereference - Rust prevents via Option, but watch for FFI
- Out-of-bounds access - use safe slice methods, avoid unsafe indexing
- Uninitialized memory - use
MaybeUninitcarefully in unsafe - Type confusion - ensure proper type casting, avoid transmute abuse
Rust-Specific Issues to Watch
- RefCell panic - borrow checking at runtime can panic
- Mutex poisoning - handle
PoisonErrorfrom panicked threads - Drop order dependencies - be explicit about destruction order
- Async runtime blocking - never use
std::thread::sleepin async - Pin projection issues - use
pin-projectcrate for safe projections - Send/Sync violations - ensure types are thread-safe if shared
Concurrency Issues
- Race condition - use proper synchronization (Mutex, RwLock, atomics)
- Deadlock - consistent lock ordering, avoid nested locks
- Livelock - ensure progress in retry loops
- Starvation - use fair locks (parking_lot), avoid long critical sections
- False sharing - pad cache lines with
#[repr(align(64))] - ABA problem - use epoch-based reclamation for lock-free code
Performance Anti-patterns
- Excessive cloning - use references, Cow, or Arc where possible
- String concatenation in loops - preallocate with
String::with_capacity - Unnecessary boxing - prefer stack allocation for small types
- N+1 queries - batch database operations
- Busy waiting - use proper async or condvar waiting
Error Handling Anti-patterns
- Silent failures - never ignore errors with
let _ = ... - Error swallowing - log or propagate all errors
- Panic in libraries - return Result, don't panic
- Unwrap/expect abuse - only use in tests or with documented invariants
- Context loss - chain errors with
.context()or#[from]
Resource Management
- Resource leaks - use RAII, implement Drop properly
- Connection pool exhaustion - bound pools, add timeouts
- Unbounded growth - use bounded channels and collections
- File descriptor exhaustion - close handles promptly
Nebula-Specific Safety
- Never log credentials or secrets
- Validate all external input at API boundaries
- Use timeouts for all external calls
- Sanitize error messages before returning to users
- Use
secrecycrate for sensitive data in memory
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?