Skip to main content

enact_core/policy/
pii_input.rs

1//! PII Input Processor
2//!
3//! Detects PII in user input before execution.
4//! Can block, warn, or allow based on configuration.
5
6use super::input_processor::{InputProcessor, InputProcessorResult};
7use super::PolicyContext;
8use async_trait::async_trait;
9
10/// PII detection mode
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum PiiInputMode {
13    /// Allow all input (no PII checking)
14    Allow,
15    /// Warn if PII detected but allow
16    #[default]
17    Warn,
18    /// Block if Direct PII detected (email, SSN, etc.)
19    BlockDirect,
20    /// Block if any PII detected
21    BlockAll,
22}
23
24/// PII Input Processor
25///
26/// Detects PII in user input before sending to LLM.
27/// Behavior is configurable via `PiiInputMode`.
28/// (Full PII detection requires the optional `enact-guardrails` crate and guardrails feature.)
29pub struct PiiInputProcessor {
30    mode: PiiInputMode,
31}
32
33impl PiiInputProcessor {
34    /// Create a new PII input processor (no-op without enact-guardrails; always allows input)
35    pub fn new() -> Self {
36        Self {
37            mode: PiiInputMode::Allow,
38        }
39    }
40
41    /// Set the detection mode
42    pub fn with_mode(mut self, mode: PiiInputMode) -> Self {
43        self.mode = mode;
44        self
45    }
46}
47
48impl Default for PiiInputProcessor {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54#[async_trait]
55impl InputProcessor for PiiInputProcessor {
56    fn name(&self) -> &str {
57        "pii-input"
58    }
59
60    fn priority(&self) -> u32 {
61        50 // Run early in the pipeline
62    }
63
64    async fn process(
65        &self,
66        _input: &str,
67        _ctx: &PolicyContext,
68    ) -> anyhow::Result<InputProcessorResult> {
69        // Without enact-guardrails, always pass
70        Ok(InputProcessorResult::Pass)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::policy::PolicyAction;
78    use std::collections::HashMap;
79
80    fn test_context() -> PolicyContext {
81        PolicyContext {
82            tenant_id: None,
83            user_id: None,
84            action: PolicyAction::StartExecution { graph_id: None },
85            metadata: HashMap::new(),
86        }
87    }
88
89    #[tokio::test]
90    async fn test_pii_input_processor_name() {
91        let processor = PiiInputProcessor::new();
92        assert_eq!(processor.name(), "pii-input");
93    }
94
95    #[tokio::test]
96    async fn test_pii_input_processor_priority() {
97        let processor = PiiInputProcessor::new();
98        assert_eq!(processor.priority(), 50);
99    }
100
101    #[tokio::test]
102    async fn test_pii_input_no_pii() {
103        let processor = PiiInputProcessor::new();
104        let ctx = test_context();
105
106        // No PII should always pass
107        let result = processor
108            .process("Hello, how can I help?", &ctx)
109            .await
110            .unwrap();
111        assert!(result.should_proceed());
112    }
113}