apollo-agent 0.3.0

Local-first Rust AI agent runtime — Telegram-first, trait-driven, SurrealDB + RocksDB state layer.
Documentation
use regex::Regex;
use serde_json::Value;
use std::sync::OnceLock;

fn patterns() -> &'static [Regex] {
    static PATTERNS: OnceLock<Vec<Regex>> = OnceLock::new();
    PATTERNS.get_or_init(|| {
        [
            r"(?i)\b(?:sk|sk-ant|ghp_|gho_|github_pat_|xoxb-|xapp-)[A-Za-z0-9_\-]{16,}\b",
            r"(?i)(?:authorization|x-api-key|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\s*[:=]\s*(?:bearer\s+)?[^\s,;]+",
            r"(?i)https?://[^\s/@:]+:[^\s/@]+@[^\s]+",
            r"(?im)\b[A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIALS?)[A-Z0-9_]*\s*=\s*[^\s]+",
            r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
        ]
        .into_iter()
        .map(|pattern| Regex::new(pattern).expect("valid redaction regex"))
        .collect()
    })
}

pub fn redact_text(input: &str) -> String {
    let mut output = input.to_string();
    for pattern in patterns() {
        output = pattern.replace_all(&output, "[REDACTED]").into_owned();
    }
    for name in [
        "ANTHROPIC_API_KEY",
        "OPENAI_API_KEY",
        "GEMINI_API_KEY",
        "DISCORD_TOKEN",
        "TELEGRAM_BOT_TOKEN",
        "SLACK_BOT_TOKEN",
        "SLACK_APP_TOKEN",
        "GITHUB_TOKEN",
    ] {
        if let Ok(secret) = std::env::var(name) {
            if secret.len() >= 8 {
                output = output.replace(&secret, "[REDACTED]");
            }
        }
    }
    output
}

pub fn redact_tool_payload(tool: &str, input: &str) -> String {
    if tool != "praefectus" {
        return redact_text(input);
    }
    let Ok(mut value) = serde_json::from_str::<Value>(input) else {
        return "[REDACTED]".to_string();
    };
    redact_praefectus_value(&mut value);
    serde_json::to_string(&value).unwrap_or_else(|_| "[REDACTED]".to_string())
}

fn redact_praefectus_value(value: &mut Value) {
    match value {
        Value::Array(values) => values.iter_mut().for_each(redact_praefectus_value),
        Value::Object(values) => {
            for key in [
                "after",
                "authority",
                "before",
                "evidence",
                "fallback_chain",
                "name",
                "signature",
                "target",
                "text",
                "value",
                "warnings",
            ] {
                if values.contains_key(key) {
                    values.insert(key.to_string(), Value::String("[REDACTED]".to_string()));
                }
            }
            values.values_mut().for_each(redact_praefectus_value);
        }
        _ => {}
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn redacts_common_tokens() {
        let output = redact_text(
            "token sk-ant-abcdefghijklmnopqrstuvwxyz and ghp_abcdefghijklmnopqrstuvwxyz1234567890",
        );
        assert!(!output.contains("abcdefghijklmnopqrstuvwxyz"));
        assert!(output.contains("[REDACTED]"));
    }

    #[test]
    fn redacts_environment_assignments() {
        let output = redact_text("AWS_SECRET_ACCESS_KEY=top-secret\nCUSTOM_TOKEN=value");
        assert!(!output.contains("top-secret"));
        assert!(!output.contains("CUSTOM_TOKEN=value"));
    }

    #[test]
    fn redacts_headers_and_private_keys() {
        let output = redact_text("Authorization: Bearer secret-token\n-----BEGIN PRIVATE KEY-----\nabc\n-----END PRIVATE KEY-----");
        assert!(!output.contains("secret-token"));
        assert!(!output.contains("abc"));
    }

    #[test]
    fn redacts_praefectus_targets_and_values_from_persistent_payloads() {
        let output = redact_tool_payload(
            "praefectus",
            r#"{"desktop_action":{"kind":"set_value","value":"private"},"target":{"element_id":"selector"}}"#,
        );
        assert!(!output.contains("private"));
        assert!(!output.contains("selector"));
        assert!(output.contains("[REDACTED]"));
    }
}