lean_ctx/core/intelligence_runtime/
personal_protection.rs1use super::{InstallError, Result};
4use std::collections::BTreeMap;
5
6#[derive(serde::Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct Compiled {
9 schema_version: u32,
10 pub rule_count: usize,
11 pub redaction: BTreeMap<String, String>,
12 pub blocked_patterns: BTreeMap<String, String>,
13}
14
15const CAPABILITY: super::exchange::Capability = super::exchange::Capability {
16 id: "pro.runtime.personal_protection",
17 version: "1.0.0",
18 declared: super::health::supports_protection,
19 deadline_ms: 5000,
20 tag: "protection",
21};
22
23pub fn compile(raw: &str) -> Result<Compiled> {
24 if raw.len() > 128 * 1024 {
25 return Err(InstallError::Policy);
26 }
27 let input: serde_json::Value = serde_json::from_str(raw).map_err(|_| InstallError::Policy)?;
28 let rules = input["rules"].as_array().ok_or(InstallError::Policy)?;
29 if input["schema_version"] != 1 || rules.is_empty() || rules.len() > 32 {
30 return Err(InstallError::Policy);
31 }
32 let output = super::exchange::invoke(&CAPABILITY, raw)?;
33 let compiled: Compiled = serde_json::from_str(&output).map_err(|_| InstallError::Exchange)?;
34 if compiled.schema_version != 1
35 || compiled.rule_count != rules.len()
36 || compiled.redaction.len() + compiled.blocked_patterns.len() != rules.len()
37 {
38 return Err(InstallError::Exchange);
39 }
40 let mut ids = std::collections::BTreeSet::new();
41 for rule in rules {
42 let id = rule["id"].as_str().ok_or(InstallError::Exchange)?;
43 if id.is_empty()
44 || id.len() > 48
45 || !id.as_bytes()[0].is_ascii_lowercase()
46 || !id
47 .bytes()
48 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b"_-".contains(&b))
49 || !ids.insert(id)
50 {
51 return Err(InstallError::Exchange);
52 }
53 let map = match rule["action"].as_str() {
54 Some("mask") => &compiled.redaction,
55 Some("block") => &compiled.blocked_patterns,
56 _ => return Err(InstallError::Exchange),
57 };
58 if !map.contains_key(id) {
59 return Err(InstallError::Exchange);
60 }
61 }
62 for pattern in compiled
63 .redaction
64 .values()
65 .chain(compiled.blocked_patterns.values())
66 {
67 if pattern.len() > 4096 {
68 return Err(InstallError::Exchange);
69 }
70 let regex = regex::RegexBuilder::new(pattern)
71 .size_limit(1024 * 1024)
72 .dfa_size_limit(1024 * 1024)
73 .build()
74 .map_err(|_| InstallError::Exchange)?;
75 if regex.is_match("") {
76 return Err(InstallError::Exchange);
77 }
78 }
79 Ok(compiled)
80}