Skip to main content

lean_ctx/core/intelligence_runtime/
personal_protection.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Authenticated compilation transport; policy execution remains in the public engine.
3use 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}