#[cfg(test)]
mod integration;
pub mod enforcer;
pub mod engine;
pub mod types;
pub use enforcer::{GuardrailEnforcer, GuardrailStats};
pub use engine::GuardrailEngine;
pub use types::{
Condition, EvaluationResult, GuardrailContext, GuardrailDef, GuardrailOutcome,
GuardrailSeverity, GuardrailSummary, GuardrailTelemetryEvent, GuardrailType, LogicalOperator,
ViolationAction,
};
use crate::errors::SelfwareError;
use crate::swl::parser::ast::SwlDocument;
pub async fn quick_check(
condition: &str,
context: &GuardrailContext,
) -> Result<EvaluationResult, SelfwareError> {
let engine = GuardrailEngine::new();
Ok(engine.evaluate_condition(&Condition::Inline(condition.to_string()), context))
}
pub struct ConditionBuilder {
operator: LogicalOperator,
conditions: Vec<Condition>,
}
impl ConditionBuilder {
pub fn and() -> Self {
Self {
operator: LogicalOperator::And,
conditions: Vec::new(),
}
}
pub fn or() -> Self {
Self {
operator: LogicalOperator::Or,
conditions: Vec::new(),
}
}
pub fn inline(mut self, expr: impl Into<String>) -> Self {
self.conditions.push(Condition::Inline(expr.into()));
self
}
pub fn code(mut self, language: impl Into<String>, content: impl Into<String>) -> Self {
self.conditions.push(Condition::Code {
language: language.into(),
content: content.into(),
});
self
}
pub fn composite(mut self, condition: Condition) -> Self {
self.conditions.push(condition);
self
}
pub fn build(self) -> Condition {
match self.conditions.len() {
0 => Condition::Inline("true".to_string()),
1 => self
.conditions
.into_iter()
.next()
.unwrap_or_else(|| Condition::Inline("true".to_string())),
_ => Condition::Composite {
operator: self.operator,
conditions: self.conditions,
},
}
}
}
pub mod patterns {
use super::*;
pub fn no_secrets_in_output() -> Condition {
ConditionBuilder::and()
.inline("!agent_output.to_lowercase().contains('password:')")
.inline("!agent_output.to_lowercase().contains('api_key:')")
.inline("!agent_output.to_lowercase().contains('secret:')")
.inline("!agent_output.to_lowercase().contains('token:')")
.build()
}
pub fn max_output_length(max_chars: usize) -> Condition {
Condition::Inline(format!("agent_output.len() <= {}", max_chars))
}
pub fn safe_shell_command() -> Condition {
ConditionBuilder::and()
.inline("!tool_input.contains('rm -rf')")
.inline("!tool_input.contains('mkfs')")
.inline("!tool_input.contains('dd if=')")
.inline("!tool_input.contains('> /dev/')")
.build()
}
pub fn allowed_paths(paths: &[impl AsRef<str>]) -> Condition {
let path_checks: Vec<String> = paths
.iter()
.map(|p| format!("tool_input.starts_with('{}')", p.as_ref()))
.collect();
Condition::Inline(format!("({})", path_checks.join(" || ")))
}
}
#[cfg(test)]
#[path = "../../../tests/unit/swl/guardrails/mod_test.rs"]
mod tests;