mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
* refactor: extract safety module into ironclaw_safety crate Move prompt injection defense, input validation, secret leak detection, and safety policy enforcement into a standalone crate under crates/. The safety module was a leaf dependency with no async, no database, and no other ironclaw traits — only pure computation with pattern matching. SafetyConfig (2 fields) moves into the crate; env-var resolution stays in ironclaw's config module as a free function. src/safety/mod.rs becomes a thin re-export so all existing `crate::safety::*` imports keep working. Co-Authored-By: Claude Opus 4.6 <[email protected]> * docs: update CLAUDE.md for ironclaw_safety crate extraction Add guidance to migrate imports from crate::safety to ironclaw_safety when touching files. Update project structure to reflect crates/ dir. Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: move safety fuzz targets into ironclaw_safety crate Split fuzz infrastructure: - crates/ironclaw_safety/fuzz/ — 5 safety-only targets (sanitizer, validator, leak_detector, credential_detect, config_env) depending only on ironclaw_safety for faster builds - fuzz/ — keeps fuzz_tool_params which needs ironclaw::tools Add seed corpus files (51 total) covering each pattern family: sanitizer injection patterns, validator edge cases, leak detector secret formats, credential detect HTTP param shapes. Add new fuzz_credential_detect target exercising params_contain_manual_credentials with arbitrary JSON. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review — single-pass XML escaping and versioned path dep Rewrite escape_xml_attr from chained .replace() to single-pass char iteration (O(n) instead of O(4n) with intermediate allocations). Add version = "0.1.0" to ironclaw_safety path dep to satisfy cargo-deny wildcards = "deny". Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
55 lines
2.1 KiB
Rust
55 lines
2.1 KiB
Rust
#![no_main]
|
|
use ironclaw_safety::{LeakDetector, Sanitizer, Validator};
|
|
use libfuzzer_sys::fuzz_target;
|
|
|
|
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"
|
|
);
|
|
}
|
|
// 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);
|
|
// 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"
|
|
);
|
|
}
|
|
|
|
// 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"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|