From 8d1d92937bc761f84f0adb56a3725572328eb6d7 Mon Sep 17 00:00:00 2001 From: "ilblackdragon@gmail.com" Date: Tue, 10 Mar 2026 00:36:03 -0700 Subject: [PATCH] fix: rewrite fuzz_config_env to exercise IronClaw safety code directly Replace SafetyLayer wrapper usage with direct Sanitizer, Validator, and LeakDetector instantiation and invocation. Adds meaningful consistency assertions (non-empty output, valid-means-no-errors, scan/clean agreement). Removes the config construction that was only exercising struct instantiation. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 --- fuzz/fuzz_targets/fuzz_config_env.rs | 66 +++++++++++++++++++--------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_config_env.rs b/fuzz/fuzz_targets/fuzz_config_env.rs index 617bda05..265a85e9 100644 --- a/fuzz/fuzz_targets/fuzz_config_env.rs +++ b/fuzz/fuzz_targets/fuzz_config_env.rs @@ -1,29 +1,55 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use ironclaw::config::SafetyConfig; +use ironclaw::safety::{LeakDetector, Sanitizer, Validator}; fuzz_target!(|data: &[u8]| { - if let Ok(s) = std::str::from_utf8(data) { - // SafetyConfig fields are parsed from env vars. We cannot safely set - // env vars in a multi-threaded fuzzer, but we can exercise the types - // that config parsing produces by constructing SafetyConfig directly - // and feeding the fuzzed string through the safety layer it creates. + 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" + ); + } + // If no modification occurred, content must equal input. + if !sanitized.was_modified { + assert_eq!(sanitized.content, input); + } - // Parse the fuzzed input as a potential max_output_length value. - let max_len: usize = s.parse().unwrap_or(100_000); + // Exercise Validator: input validation (length, encoding, patterns). + let validator = Validator::new(); + let result = validator.validate(input); + // ValidationResult must always be well-formed: if valid, no errors. + if result.is_valid { + assert!( + result.errors.is_empty(), + "valid result should have no errors" + ); + } - let config = SafetyConfig { - max_output_length: max_len, - injection_check_enabled: true, - }; - - // Build a SafetyLayer from the config and exercise it. - let layer = ironclaw::safety::SafetyLayer::new(&config); - - // Use the fuzzed string as tool output content. - let _ = layer.sanitize_tool_output("fuzz_tool", s); - let _ = layer.validate_input(s); - let _ = layer.check_policy(s); + // Exercise LeakDetector: secret detection (API keys, tokens, etc.). + let detector = LeakDetector::new(); + let scan = 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(); + } + // 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 { + assert_eq!( + clean_str, input, + "scan_and_clean changed content despite no matches" + ); + } + } } });