lean_ctx/core/config/
risk.rs1pub struct ConfigRisk {
16 pub note: &'static str,
18}
19
20#[must_use]
25pub fn classify(key: &str) -> Option<ConfigRisk> {
26 let note = match key {
27 "path_jail" => {
28 "Path jail confines agent file access to your projects. Disabling it lets tools read and write any path on this machine."
29 }
30 "path_jail_scope" => {
31 "Jail scope decides what tools may reach: \"projects\" (default) reads every project below your home directory and writes only the active one, \"home\" reads and writes all of it (protected dot dirs and ~/Library excepted), \"project\" only the active project."
32 }
33 "shell_security" => {
34 "Shell gating blocks dangerous commands via an allowlist. Lowering it (warn/off) lets the agent run any command."
35 }
36 "sandbox_level" => {
37 "Sandbox level governs how strictly tool execution is contained. Lowering it reduces isolation."
38 }
39 "secret_detection.enabled" => {
40 "Secret detection masks API keys and .env values before they reach the LLM. Disabling it can leak credentials to the provider."
41 }
42 "secret_detection.redact" => {
43 "Secret redaction masks detected secrets. Disabling it sends them verbatim to the provider."
44 }
45 "boundary_policy" => {
46 "Boundary policy controls what context is allowed to leave this machine. Relaxing it widens data egress."
47 }
48 "proxy_require_token" => {
49 "Proxy token policy controls whether provider API keys can authenticate local proxy requests without the lean-ctx Bearer token."
50 }
51 "proxy_loopback_open" => {
52 "Disables ALL proxy authentication on loopback binds. Any local process can access the proxy without a token."
53 }
54 "a2a_accept_untrusted_signers" => {
55 "Accepts A2A handoffs and context packages signed by any key, not only keys pinned in trusted-signers.toml. Anyone who can reach the endpoint can then plant content under a self-chosen identity."
56 }
57 "mcp_import_allow_unverified" => {
58 "Lets the agent import unsigned or untrusted-signer handoff bundles and context packages into session, knowledge and package stores without asking you."
59 }
60 "proxy.anthropic_upstream"
61 | "proxy.openai_upstream"
62 | "proxy.chatgpt_upstream"
63 | "proxy.gemini_upstream" => {
64 "Redirects provider traffic to a custom upstream — every request and API key for this provider will flow through it."
65 }
66 _ => return None,
67 };
68 Some(ConfigRisk { note })
69}
70
71#[must_use]
73pub fn is_consequential(key: &str) -> bool {
74 classify(key).is_some()
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn security_keys_are_consequential() {
83 for key in [
84 "path_jail",
85 "path_jail_scope",
86 "shell_security",
87 "sandbox_level",
88 "secret_detection.enabled",
89 "secret_detection.redact",
90 "boundary_policy",
91 "proxy_require_token",
92 "proxy_loopback_open",
93 "proxy.openai_upstream",
94 "proxy.chatgpt_upstream",
95 "proxy.anthropic_upstream",
96 "proxy.gemini_upstream",
97 "a2a_accept_untrusted_signers",
98 "mcp_import_allow_unverified",
99 ] {
100 assert!(is_consequential(key), "{key} should be consequential");
101 assert!(
102 !classify(key).unwrap().note.is_empty(),
103 "`classify(key).unwrap().note` is empty"
104 );
105 }
106 }
107
108 #[test]
109 fn routine_keys_are_not_consequential() {
110 for key in [
111 "theme",
112 "max_ram_percent",
113 "compression_level",
114 "proxy.port",
115 "proxy.effort",
116 "bm25_max_cache_mb",
117 ] {
118 assert!(!is_consequential(key), "{key} should be routine");
119 }
120 }
121}