Skip to main content

systemprompt_api/services/gateway/safety/
mod.rs

1//! Content-safety scanning of gateway requests and responses.
2//!
3//! The [`SafetyScanner`] trait inspects a canonical request or final response
4//! and returns [`Finding`]s graded by [`Severity`]. Scanners are selected by
5//! policy: [`HeuristicScanner`] applies pattern-based checks, [`NullScanner`]
6//! is the no-op used when scanning is disabled.
7
8pub mod heuristic;
9pub mod null;
10
11use async_trait::async_trait;
12
13use super::protocol::canonical::CanonicalRequest;
14use super::protocol::canonical_response::CanonicalResponse;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Severity {
18    Low,
19    Medium,
20    High,
21}
22
23impl Severity {
24    pub const fn as_str(self) -> &'static str {
25        match self {
26            Self::Low => "low",
27            Self::Medium => "medium",
28            Self::High => "high",
29        }
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct Finding {
35    pub phase: &'static str,
36    pub severity: Severity,
37    pub category: String,
38    pub excerpt: Option<String>,
39    pub scanner: &'static str,
40}
41
42// Why: #[async_trait] is required — scanners are selected by policy and held as
43// trait objects, so the trait must stay dyn-compatible.
44#[async_trait]
45pub trait SafetyScanner: Send + Sync {
46    fn name(&self) -> &'static str;
47    async fn scan_request(&self, req: &CanonicalRequest) -> Vec<Finding>;
48    async fn scan_response_final(&self, response: &CanonicalResponse) -> Vec<Finding>;
49}
50
51pub use heuristic::HeuristicScanner;
52pub use null::NullScanner;