mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 15:40:18 +00:00
fix(fuzz): address PR review — LazyLock for expensive constructors, fix assertions and docs
- Use std::sync::LazyLock to construct Sanitizer, Validator, and LeakDetector once instead of on every fuzz iteration (they compile regex/Aho-Corasick) - Remove fuzz_config_env assertion that panics on null-byte-only input - Remove no-op length check with misleading comment in fuzz_config_env - Update fuzz_config_env description in README to match actual behavior Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@ Fuzz testing for security-critical input parsing paths using [cargo-fuzz](https:
|
||||
| `fuzz_safety_validator` | Input validation (length, encoding, forbidden patterns) |
|
||||
| `fuzz_leak_detector` | Secret leak detection (API keys, tokens, credentials) |
|
||||
| `fuzz_tool_params` | Tool parameter and schema JSON validation |
|
||||
| `fuzz_config_env` | SafetyLayer end-to-end (sanitize, validate, policy check) |
|
||||
| `fuzz_config_env` | Combined safety primitives (sanitize, validate, leak detect) |
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use ironclaw::safety::{LeakDetector, Sanitizer, Validator};
|
||||
|
||||
static SANITIZER: LazyLock<Sanitizer> = LazyLock::new(Sanitizer::new);
|
||||
static VALIDATOR: LazyLock<Validator> = LazyLock::new(Validator::new);
|
||||
static LEAK_DETECTOR: LazyLock<LeakDetector> = LazyLock::new(LeakDetector::new);
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(input) = std::str::from_utf8(data) {
|
||||
// Exercise Sanitizer: detect and neutralize prompt injection attempts.
|
||||
let sanitizer = Sanitizer::new();
|
||||
let sanitized = sanitizer.sanitize(input);
|
||||
// The sanitized content must never be empty when input is non-empty,
|
||||
// because sanitization wraps/escapes rather than deleting.
|
||||
if !input.is_empty() {
|
||||
assert!(
|
||||
!sanitized.content.is_empty(),
|
||||
"sanitize() produced empty content for non-empty input"
|
||||
);
|
||||
}
|
||||
let sanitized = SANITIZER.sanitize(input);
|
||||
// If no modification occurred, content must equal input.
|
||||
if !sanitized.was_modified {
|
||||
assert_eq!(sanitized.content, input);
|
||||
}
|
||||
|
||||
// Exercise Validator: input validation (length, encoding, patterns).
|
||||
let validator = Validator::new();
|
||||
let result = validator.validate(input);
|
||||
let result = VALIDATOR.validate(input);
|
||||
// ValidationResult must always be well-formed: if valid, no errors.
|
||||
if result.is_valid {
|
||||
assert!(
|
||||
@@ -33,15 +28,9 @@ fuzz_target!(|data: &[u8]| {
|
||||
}
|
||||
|
||||
// Exercise LeakDetector: secret detection (API keys, tokens, etc.).
|
||||
let detector = LeakDetector::new();
|
||||
let scan = detector.scan(input);
|
||||
let scan = LEAK_DETECTOR.scan(input);
|
||||
// scan_and_clean must not panic and must return valid UTF-8.
|
||||
let cleaned = detector.scan_and_clean(input);
|
||||
if let Ok(ref clean_str) = cleaned {
|
||||
// Cleaned output must never be longer than original + redaction markers.
|
||||
// At minimum it should be valid UTF-8 (guaranteed by String type).
|
||||
let _ = clean_str.len();
|
||||
}
|
||||
let cleaned = LEAK_DETECTOR.scan_and_clean(input);
|
||||
// If scan found no matches, scan_and_clean should return the input unchanged.
|
||||
if scan.matches.is_empty() {
|
||||
if let Ok(ref clean_str) = cleaned {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use ironclaw::safety::LeakDetector;
|
||||
|
||||
static DETECTOR: LazyLock<LeakDetector> = LazyLock::new(LeakDetector::new);
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(s) = std::str::from_utf8(data) {
|
||||
let detector = LeakDetector::new();
|
||||
|
||||
// Exercise scan path
|
||||
let result = detector.scan(s);
|
||||
let result = DETECTOR.scan(s);
|
||||
// Invariant: if should_block, there must be matches
|
||||
if result.should_block {
|
||||
assert!(!result.matches.is_empty());
|
||||
@@ -18,6 +20,6 @@ fuzz_target!(|data: &[u8]| {
|
||||
}
|
||||
|
||||
// Exercise scan_and_clean path
|
||||
let _ = detector.scan_and_clean(s);
|
||||
let _ = DETECTOR.scan_and_clean(s);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use ironclaw::safety::Sanitizer;
|
||||
|
||||
static SANITIZER: LazyLock<Sanitizer> = LazyLock::new(Sanitizer::new);
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(s) = std::str::from_utf8(data) {
|
||||
let sanitizer = Sanitizer::new();
|
||||
|
||||
// Exercise the main sanitization path
|
||||
let result = sanitizer.sanitize(s);
|
||||
let result = SANITIZER.sanitize(s);
|
||||
// Verify invariant: warnings should have valid ranges
|
||||
for w in &result.warnings {
|
||||
assert!(w.location.end <= s.len());
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use ironclaw::safety::Validator;
|
||||
|
||||
static VALIDATOR: LazyLock<Validator> = LazyLock::new(Validator::new);
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(s) = std::str::from_utf8(data) {
|
||||
let validator = Validator::new();
|
||||
|
||||
// Exercise input validation
|
||||
let result = validator.validate(s);
|
||||
let result = VALIDATOR.validate(s);
|
||||
// Invariant: empty input is always invalid
|
||||
if s.is_empty() {
|
||||
assert!(!result.is_valid);
|
||||
@@ -15,7 +17,7 @@ fuzz_target!(|data: &[u8]| {
|
||||
|
||||
// Exercise tool parameter validation with arbitrary JSON
|
||||
if let Ok(value) = serde_json::from_str::<serde_json::Value>(s) {
|
||||
let _ = validator.validate_tool_params(&value);
|
||||
let _ = VALIDATOR.validate_tool_params(&value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use ironclaw::safety::Validator;
|
||||
use ironclaw::tools::validate_tool_schema;
|
||||
|
||||
static VALIDATOR: LazyLock<Validator> = LazyLock::new(Validator::new);
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(s) = std::str::from_utf8(data) {
|
||||
// Try parsing as JSON and validating as tool parameters
|
||||
if let Ok(value) = serde_json::from_str::<serde_json::Value>(s) {
|
||||
// Exercise Validator::validate_tool_params with arbitrary JSON
|
||||
let validator = Validator::new();
|
||||
let result = validator.validate_tool_params(&value);
|
||||
let result = VALIDATOR.validate_tool_params(&value);
|
||||
// Invariant: result should always be well-formed
|
||||
if !result.is_valid {
|
||||
assert!(!result.errors.is_empty());
|
||||
|
||||
Reference in New Issue
Block a user