pub mod audit;
#[cfg(feature = "sandbox-bubblewrap")]
pub mod bubblewrap;
pub mod detect;
pub mod docker;
pub mod domain_matcher;
pub mod estop;
#[cfg(target_os = "linux")]
pub mod firejail;
pub mod iam_policy;
#[cfg(feature = "sandbox-landlock")]
pub mod landlock;
pub mod leak_detector;
pub mod nevis;
pub mod otp;
pub mod pairing;
pub mod playbook;
pub mod policy;
pub mod prompt_guard;
#[cfg(target_os = "macos")]
pub mod seatbelt;
pub mod secrets;
pub mod ssrf;
pub mod traits;
pub mod vulnerability;
#[cfg(feature = "webauthn")]
pub mod webauthn;
pub mod workspace_boundary;
#[allow(unused_imports)]
pub use audit::{AuditEvent, AuditEventType, AuditLogger};
#[allow(unused_imports)]
pub use detect::create_sandbox;
pub use domain_matcher::DomainMatcher;
#[allow(unused_imports)]
pub use estop::{EstopLevel, EstopManager, EstopState, ResumeSelector};
#[allow(unused_imports)]
pub use otp::OtpValidator;
#[allow(unused_imports)]
pub use pairing::PairingGuard;
pub use policy::{AutonomyLevel, SecurityPolicy};
#[allow(unused_imports)]
pub use secrets::SecretStore;
#[allow(unused_imports)]
pub use traits::{NoopSandbox, Sandbox};
#[allow(unused_imports)]
pub use iam_policy::{IamPolicy, PolicyDecision};
#[allow(unused_imports)]
pub use nevis::{NevisAuthProvider, NevisIdentity};
#[allow(unused_imports)]
pub use leak_detector::{LeakDetector, LeakResult};
#[allow(unused_imports)]
pub use prompt_guard::{GuardAction, GuardResult, PromptGuard};
#[allow(unused_imports)]
pub use workspace_boundary::{BoundaryVerdict, WorkspaceBoundary};
pub fn redact_outbound(content: &str) -> (String, Option<Vec<String>>) {
match LeakDetector::new().scan(content) {
LeakResult::Clean => (content.to_string(), None),
LeakResult::Detected { patterns, redacted } => (redacted, Some(patterns)),
}
}
pub fn redact(value: &str) -> String {
let char_count = value.chars().count();
if char_count <= 4 {
"***".to_string()
} else {
let prefix: String = value.chars().take(4).collect();
format!("{prefix}***")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reexported_policy_and_pairing_types_are_usable() {
let policy = SecurityPolicy::default();
assert_eq!(policy.autonomy, AutonomyLevel::Supervised);
let guard = PairingGuard::new(false, &[]);
assert!(!guard.require_pairing());
}
#[test]
fn reexported_secret_store_encrypt_decrypt_roundtrip() {
let temp = tempfile::tempdir().unwrap();
let store = SecretStore::new(temp.path(), false);
let encrypted = store.encrypt("top-secret").unwrap();
let decrypted = store.decrypt(&encrypted).unwrap();
assert_eq!(decrypted, "top-secret");
}
#[test]
fn redact_outbound_returns_clean_unchanged() {
let (out, leaked) = redact_outbound("just some normal text");
assert_eq!(out, "just some normal text");
assert!(leaked.is_none());
}
#[test]
fn redact_outbound_scrubs_api_key_and_pem_block() {
let content = "key sk_test_1234567890abcdefghijklmnop and\n\
-----BEGIN PRIVATE KEY-----\n\
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ\n\
-----END PRIVATE KEY-----";
let (out, leaked) = redact_outbound(content);
let patterns = leaked.expect("should detect leaks");
assert!(patterns.iter().any(|p| p.contains("Stripe")));
assert!(patterns.iter().any(|p| p.contains("Private key")));
assert!(!out.contains("sk_test_1234567890abcdefghijklmnop"));
assert!(!out.contains("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ"));
assert!(out.contains("[REDACTED_PRIVATE_KEY]"));
}
#[test]
fn redact_hides_most_of_value() {
assert_eq!(redact("abcdefgh"), "abcd***");
assert_eq!(redact("ab"), "***");
assert_eq!(redact(""), "***");
assert_eq!(redact("12345"), "1234***");
}
#[test]
fn redact_handles_multibyte_utf8_without_panic() {
let result = redact("密码是很长的秘密");
assert!(result.ends_with("***"));
assert!(result.is_char_boundary(result.len()));
}
}