Agent skill
security-and-vulnerability-management
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/security-and-vulnerability-management
SKILL.md
priority: critical
Security & Vulnerability Management
CVE disclosure · cargo-audit · cargo-deny · Fuzzing · Unsafe code review · Security testing
Vulnerability Management
-
cargo-audit: Run on every commit and in CI; fail build on known vulnerabilities
bashcargo audit cargo audit --deny warnings # Fail on advisories -
cargo-deny: Detect vulnerable, unmaintained, and unlicensed dependencies
bashcargo deny check advisories cargo deny check bans cargo deny check sources -
CVE Disclosure Policy:
- Report to maintainers privately before public disclosure
- Allow 90 days for patch before public CVE announcement
- Document CVE in
SECURITY.mdwith patch version and workarounds - Never commit exploits; reference by CVE ID and mitigation
- Include timeline: discovery → disclosure → patch release
Cargo.lock Management
# Cargo.toml
[dependencies]
serde = "=1.0.150" # Pin critical deps to known safe versions
serde_json = "=1.0.89"
# Run lock file checks in CI
# .github/workflows/security.yml
- name: Check lock file
run: cargo tree --locked # Ensure lock matches Cargo.toml
Fuzzing with cargo-fuzz
- Setup:
cargo install cargo-fuzz && cargo fuzz init - Fuzz targets:
fuzz/fuzz_targets/directory; one target per public API surface - Continuous fuzzing: Run in CI with timeout limits (e.g., 60 seconds per commit)
- Crash reproduction: Save failing inputs; add regression tests
Fuzzing Example
// fuzz/fuzz_targets/parse_fuzzer.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
use my_crate::parse;
fuzz_target!(|data: &[u8]| {
// Parse should never crash or panic on arbitrary input
let _ = parse(data);
});
# Run fuzzing
cargo +nightly fuzz run parse_fuzzer -- -max_len=1024 -timeout=5
# Minimize failing input
cargo +nightly fuzz cmin parse_fuzzer
Unsafe Code Review
-
SAFETY comments: EVERY
unsafeblock MUST have a// SAFETY:comment explaining:- What invariant is being relied upon
- Why calling code maintains that invariant
- What could go wrong if invariant is violated
-
Isolation: Unsafe code in dedicated modules; public API must be safe
-
Review checklist:
- All pointers are valid (not null, not dangling)
- Pointer dereferencing is properly aligned
- No use-after-free (object still alive)
- No double-free (only freed once)
- No data races (proper synchronization)
- Type safety maintained across FFI boundaries
Unsafe Code Example
/// SAFETY: This function assumes the pointer points to valid, initialized memory
/// for at least `len` bytes. Caller MUST ensure pointer is:
/// - Non-null and properly aligned
/// - Pointing to valid, initialized T values
/// - Valid for reads of at least `len * sizeof(T)` bytes
/// - No aliasing mutable references exist to this data
unsafe fn copy_from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
std::slice::from_raw_parts(ptr, len).to_vec()
}
// Calling code MUST validate preconditions
let data = unsafe {
// SAFETY: C function guarantees ptr is valid, len bytes
copy_from_raw(c_buffer, buffer_len)
};
Security Testing
-
No panics on untrusted input: All public functions should return
Result, never panic on bad input -
Test adversarial inputs:
- Empty data
- Maximum size data
- Null/invalid pointers (for FFI)
- Concurrency stress tests (data races)
-
Input validation: Validate all untrusted input at boundaries (FFI, network, files)
-
Property-based testing: Use
proptestfor fuzz-like testing in normal tests
Security Test Example
#[cfg(test)]
mod security_tests {
use proptest::proptest;
#[test]
fn parse_never_panics_on_arbitrary_input() {
// Test with random bytes
for _ in 0..1000 {
let random_bytes = rand::random::<Vec<u8>>();
let result = my_crate::parse(&random_bytes);
// Should return Err, never panic
assert!(result.is_ok() || result.is_err());
}
}
#[test]
fn handles_max_size_input() {
let huge_input = vec![0u8; 1024 * 1024 * 100]; // 100MB
let result = my_crate::parse(&huge_input);
assert!(result.is_ok() || result.is_err()); // Never panic
}
proptest! {
#[test]
fn parse_property_never_panics(input in ".*") {
let _ = my_crate::parse(input.as_bytes());
}
}
}
Dependency Management
- Minimize dependencies: Each dependency is a potential vulnerability surface
- Evaluate before adding: Check security history, maintenance status, license
- Keep updated: Regular
cargo updatebut within version constraints - Audit on CI: Every PR should pass
cargo auditandcargo deny
Deny Configuration
# deny.toml
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "warn"
notice = "warn"
ignore = []
[bans]
multiple-versions = "warn"
wildcards = "warn"
allow = []
deny = [
{ name = "openssl", version = "<0.10" },
{ name = "parking_lot", version = "*" } # Use std instead
]
[sources]
unknown-registry = "warn"
unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
Anti-Patterns
- No SAFETY comments: Unsafe code without explanation is unreviewable and unmaintainable
- Unsafe in public API: Safe wrapper should be enforced at public boundary
- Ignoring cargo-audit warnings: Known vulns should block builds immediately
- Panicking on untrusted input:
unwrap()on parsed/network data will crash in production - No fuzzing of parsers: Parsers are attack surface; fuzzing essential
- Mixing unsafe and concurrency: Unsafe + threading = subtle data races; minimize overlap
- Outdated dependencies: Stale deps accumulate vulns; regular updates required
- No bounds checking on FFI pointers: Dereferencing unchecked FFI pointers is UB
Security Checklist for Release
-
cargo auditpasses (no known vulns) -
cargo deny checkpasses (licenses, sources) - All unsafe blocks have SAFETY comments
- Fuzzing targets pass (no crashes)
-
cargo test --releasepasses (including security tests) - No panics on arbitrary input
- Dependencies are reviewed for security
- SECURITY.md updated with any CVE fixes
Didn't find tool you were looking for?