/rust-unsafe-audit
L1 supplement - audits Rust-specific hazards: unsafe blocks, uninitialized memory, Send/Sync violations, panic safety in hot paths, drop order, FFI.
$ npx -y skills add PlamenTSV/plamen --skill rust-unsafe-audit --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/rust-unsafe-audit
Context preview
The summary Claude sees to decide when to auto-load this skill.
L1 supplement - audits Rust-specific hazards: unsafe blocks, uninitialized memory, Send/Sync violations, panic safety in hot paths, drop order, FFI.
SKILL.md
rust-unsafe-audit.SKILL.mdname: "rust-unsafe-audit"
description: "L1 supplement - audits Rust-specific hazards: unsafe blocks, uninitialized memory, Send/Sync violations, panic safety in hot paths, drop order, FFI."
Injectable Skill: Rust Unsafe Audit
> **L1 trigger**: `L1_PATTERN=true` AND target language = Rust > **Inject Into**: Every L1 depth agent working on Rust code, in addition to the main skill > **Finding prefix**: `[RS-N]` > **Status**: v0.1 draft, Round 4 exemplars pending
When This Skill Activates
Supplement to the main L1 skills when the target is written in Rust. Rust's safe subset prevents most memory safety bugs by default, but node clients use `unsafe` for performance (crypto, serialization), `panic!` for unrecoverable states, and FFI (blst, rocksdb, librocksdb-sys). Each is a bug class.
1. Unsafe Block Audit
Every `unsafe` block is a memory-safety assertion by the author: "I guarantee this is safe." The audit must verify that guarantee.
**Detection**:
- `cargo-geiger` counts unsafe per crate (cross-platform)
- Ast-grep `unsafe { $$ }` and `unsafe fn $NAME` and `unsafe impl`
- For each, read the surrounding safety comment (Rust convention: `// SAFETY: ...` comment should justify the unsafe)
**Checklist per unsafe block**:
- Pointer deref: is the pointer non-null? Aligned? Pointing to valid memory of the right type and lifetime?
- Type transmutation: is the layout guaranteed? (`#[repr(C)]`, `#[repr(transparent)]`)
- FFI call: are the C function's preconditions documented and met?
- Raw integer-to-pointer: is this actually sound, or just "works on current compiler"?
Tag: `[RS-UNSAFE:{loc}:{category}]`
2. Uninitialized Memory
Rust allows uninitialized memory via `MaybeUninit` or `mem::uninitialized` (deprecated). Reading uninitialized memory is Undefined Behavior.
**Detection**:
- Grep `MaybeUninit`, `mem::uninitialized`
- For each, verify the memory is initialized before `assume_init()` is called
- `MaybeUninit::zeroed()` for non-zeroable types is UB
Tag: `[RS-UNINIT:{loc}]`
3. Send / Sync Violations
`Send` allows a type to be transferred between threads; `Sync` allows shared references across threads. Manually implementing them (`unsafe impl Send for T`) is an assertion.
**Detection**:
- `unsafe impl Send for X` / `unsafe impl Sync for X`
- For each, ask: is the type actually thread-safe, or is this a compile-time workaround?
- `Rc<T>` in an async context: `Rc` is not `Send`, only `Arc` is
Tag: `[RS-SEND-SYNC:{type}:{justification}]`
4. Panic Safety
Panics in Rust unwind the stack by default. In a consensus-critical hot path, a panic kills the node. Exception: `panic = "abort"` in Cargo.toml makes panics immediately terminate the process.
**Detection**:
- `.unwrap()` on user-input-derived values: if the input is attacker-controlled, panic is reachable
- `.expect("...")` same
- `arr[i]` indexing on attacker-controlled `i`
- Integer division where denominator is attacker-controlled
- `assert!` / `assert_eq!` in hot paths with assertion on untrusted data
Tag: `[RS-PANIC:{loc}:{input-source}]`
**Rule of thumb**: any function that parses peer/RPC input must not `.unwrap()` or panic-index. Use `?` and `Result` throughout.
5. Drop Order and RAII
Rust's Drop trait runs deterministically when a value goes out of scope. Bugs:
- Drop order in structs is field-declaration order — reorder fields and RAII semantics change
- Circular references via `Rc` / `Arc` prevent drop
- Panic during drop is double-panic → abort
**Detection**:
- Every `impl Drop for X`: is the drop logic panic-safe?
- `Arc<Mutex<T>>` cycles: are there weak references breaking cycles?
Tag: `[RS-DROP:{issue}]`
6. FFI Correctness
Most L1 Rust clients call C libraries: `blst` (BLS), `rocksdb-sys` (storage), `libsecp256k1-sys` (signatures).
**Detection**:
- Every `extern "C"` call site
- Check the C function signature vs the Rust binding
- Buffer passed to C: length correctly communicated?
- Return code from C: checked for errors?
- Memory allocated by C: freed using the C allocator (not Rust's)?
Tag: `[RS-FFI:{function}:{issue}]`
6a. C FFI type size platform portability (LP64 vs LLP64)
When binding to C / C++ / CUDA / HIP via FFI (`extern "C"`, `bindgen`, hand-written headers, `*.cu` / `*.hip` files), audit every C integer type use. C type sizes differ between platforms:
| C type | Linux/macOS (LP64) | Windows MSVC (LLP64) | Safe? | |---|---|---|---| | `unsigned long` / `long` | 64 bits | **32 bits** | NO — silent truncation on Windows | | `unsigned long long` / `long long` | 64 bits | 64 bits | YES | | `int` / `unsigned int` | 32 bits | 32 bits | YES | | `size_t` / `uintptr_t` | pointer-width | pointer-width | YES (verify) | | `uint64_t` / `int64_t` (`<stdint.h>`) | 64 bits | 64 bits | YES — preferred |
**Check**:
- Grep every `.h` / `.c` / `.cpp` / `.cu` / `.hip` file in scope for `unsigned long` and `long` declarations
- For each, replace with a fixed-width type (`uint32_t`, `uint64_t`) AND verify the corresponding Rust binding uses `c_uint` / `c_ulong` consistently with the actual C type after replacement
- `c_ulong` in Rust IS the platform-native unsigned long, so it has the same problem on Windows — fixed-width on both sides is the only safe answer
- For CUDA kernels: device-side `unsigned long` follows the host platform's ABI on most compilers, so Windows MSVC builds will silently truncate
**Fail mode**: 64-bit values (chunk offsets, partition hashes, block heights) silently truncated to 32 bits on Windows builds, producing different consensus output from Linux builds → silent network split between operators on different platforms.
Tag: `[RS-FFI:c-type-portability:{file}:{line}]`
6b. Database transaction commit-before-check
Many Rust DB wrappers (mdbx-rs, sled, rocksdb) provide an `update`-style helper that runs a closure inside a transaction and commits afterwards. A common bug pattern: the wrapper commits the transaction unconditionally before checking whether
Read more
name: "rust-unsafe-audit" description: "L1 supplement - audits Rust-specific hazards: unsafe blocks, uninitialized memory, Send/Sync violations, panic safety in hot paths, drop order, FFI."
Injectable Skill: Rust Unsafe Audit
> **L1 trigger**: `L1_PATTERN=true` AND target language = Rust > **Inject Into**: Every L1 depth agent working on Rust code, in addition to the main skill > **Finding prefix**: `[RS-N]` > **Status**: v0.1 draft, Round 4 exemplars pending
When This Skill Activates
Supplement to the main L1 skills when the target is written in Rust. Rust's safe subset prevents most memory safety bugs by default, but node clients use `unsafe` for performance (crypto, serialization), `panic!` for unrecoverable states, and FFI (blst, rocksdb, librocksdb-sys). Each is a bug class.
1. Unsafe Block Audit
Every `unsafe` block is a memory-safety assertion by the author: "I guarantee this is safe." The audit must verify that guarantee.
**Detection**:
- `cargo-geiger` counts unsafe per crate (cross-platform)
- Ast-grep `unsafe { $$ }` and `unsafe fn $NAME` and `unsafe impl`
- For each, read the surrounding safety comment (Rust convention: `// SAFETY: ...` comment should justify the unsafe)
**Checklist per unsafe block**:
- Pointer deref: is the pointer non-null? Aligned? Pointing to valid memory of the right type and lifetime?
- Type transmutation: is the layout guaranteed? (`#[repr(C)]`, `#[repr(transparent)]`)
- FFI call: are the C function's preconditions documented and met?
- Raw integer-to-pointer: is this actually sound, or just "works on current compiler"?
Tag: `[RS-UNSAFE:{loc}:{category}]`
2. Uninitialized Memory
Rust allows uninitialized memory via `MaybeUninit` or `mem::uninitialized` (deprecated). Reading uninitialized memory is Undefined Behavior.
**Detection**:
- Grep `MaybeUninit`, `mem::uninitialized`
- For each, verify the memory is initialized before `assume_init()` is called
- `MaybeUninit::zeroed()` for non-zeroable types is UB
Tag: `[RS-UNINIT:{loc}]`
3. Send / Sync Violations
`Send` allows a type to be transferred between threads; `Sync` allows shared references across threads. Manually implementing them (`unsafe impl Send for T`) is an assertion.
**Detection**:
- `unsafe impl Send for X` / `unsafe impl Sync for X`
- For each, ask: is the type actually thread-safe, or is this a compile-time workaround?
- `Rc<T>` in an async context: `Rc` is not `Send`, only `Arc` is
Tag: `[RS-SEND-SYNC:{type}:{justification}]`
4. Panic Safety
Panics in Rust unwind the stack by default. In a consensus-critical hot path, a panic kills the node. Exception: `panic = "abort"` in Cargo.toml makes panics immediately terminate the process.
**Detection**:
- `.unwrap()` on user-input-derived values: if the input is attacker-controlled, panic is reachable
- `.expect("...")` same
- `arr[i]` indexing on attacker-controlled `i`
- Integer division where denominator is attacker-controlled
- `assert!` / `assert_eq!` in hot paths with assertion on untrusted data
Tag: `[RS-PANIC:{loc}:{input-source}]`
**Rule of thumb**: any function that parses peer/RPC input must not `.unwrap()` or panic-index. Use `?` and `Result` throughout.
5. Drop Order and RAII
Rust's Drop trait runs deterministically when a value goes out of scope. Bugs:
- Drop order in structs is field-declaration order — reorder fields and RAII semantics change
- Circular references via `Rc` / `Arc` prevent drop
- Panic during drop is double-panic → abort
**Detection**:
- Every `impl Drop for X`: is the drop logic panic-safe?
- `Arc<Mutex<T>>` cycles: are there weak references breaking cycles?
Tag: `[RS-DROP:{issue}]`
6. FFI Correctness
Most L1 Rust clients call C libraries: `blst` (BLS), `rocksdb-sys` (storage), `libsecp256k1-sys` (signatures).
**Detection**:
- Every `extern "C"` call site
- Check the C function signature vs the Rust binding
- Buffer passed to C: length correctly communicated?
- Return code from C: checked for errors?
- Memory allocated by C: freed using the C allocator (not Rust's)?
Tag: `[RS-FFI:{function}:{issue}]`
6a. C FFI type size platform portability (LP64 vs LLP64)
When binding to C / C++ / CUDA / HIP via FFI (`extern "C"`, `bindgen`, hand-written headers, `*.cu` / `*.hip` files), audit every C integer type use. C type sizes differ between platforms:
| C type | Linux/macOS (LP64) | Windows MSVC (LLP64) | Safe? | |---|---|---|---| | `unsigned long` / `long` | 64 bits | **32 bits** | NO — silent truncation on Windows | | `unsigned long long` / `long long` | 64 bits | 64 bits | YES | | `int` / `unsigned int` | 32 bits | 32 bits | YES | | `size_t` / `uintptr_t` | pointer-width | pointer-width | YES (verify) | | `uint64_t` / `int64_t` (`<stdint.h>`) | 64 bits | 64 bits | YES — preferred |
**Check**:
- Grep every `.h` / `.c` / `.cpp` / `.cu` / `.hip` file in scope for `unsigned long` and `long` declarations
- For each, replace with a fixed-width type (`uint32_t`, `uint64_t`) AND verify the corresponding Rust binding uses `c_uint` / `c_ulong` consistently with the actual C type after replacement
- `c_ulong` in Rust IS the platform-native unsigned long, so it has the same problem on Windows — fixed-width on both sides is the only safe answer
- For CUDA kernels: device-side `unsigned long` follows the host platform's ABI on most compilers, so Windows MSVC builds will silently truncate
**Fail mode**: 64-bit values (chunk offsets, partition hashes, block heights) silently truncated to 32 bits on Windows builds, producing different consensus output from Linux builds → silent network split between operators on different platforms.
Tag: `[RS-FFI:c-type-portability:{file}:{line}]`
6b. Database transaction commit-before-check
Many Rust DB wrappers (mdbx-rs, sled, rocksdb) provide an `update`-style helper that runs a closure inside a transaction and commits afterwards. A common bug pattern: the wrapper commits the transaction unconditionally before checking whether
Autonomous Web3 security auditor for Claude Code and OpenAI Codex CLI. Orchestrates 18-100 AI agents across 40+ phases to produce audit reports with verified PoC exploits — for smart contracts and L1 node-client infrastructure.
Repo: PlamenTSV/plamen
Other skills on plamen.
- /ability-analysis
Trigger Pattern Always (Aptos Move) - foundational security check - Inject Into Breadth agents, depth agents
Open skill - /bit-shift-safety
Trigger Pattern Always (Aptos Move) - Move VM aborts on shift = bit width - Inject Into Breadth agents, depth-edge-case
Open skill - /centralization-risk
Trigger Protocol has privileged roles (admin, operator, governance, resource account owner) - Covers Single points of failure, privilege escalation, external governance dependen...
Open skill - /cross-chain-timing
Trigger Pattern wormhole|layerzero|ccip|bridge|cross_chain|vaa|guardian|emitter|relay|remote_chain|payload|nonce.sequence - Inject Into Breadth agents, depth-external
Open skill - /dependency-audit
Trigger EXTERNAL_LIB flag detected (protocol uses third-party Move dependencies) - Used by Breadth agents, depth-external
Open skill - /economic-design-audit
Trigger Pattern MONETARY_PARAMETER flag (required) - Inject Into Breadth agents (merged via M4 hierarchy)
Open skill

