mlua_batteries/policy/env_policy.rs
1//! Environment variable access policy.
2
3use std::collections::HashSet;
4
5use super::{PolicyError, Unrestricted};
6
7/// Policy that decides whether a given environment variable may be accessed.
8///
9/// Every function in the `env` module calls [`EnvPolicy::check_get`] or
10/// [`EnvPolicy::check_set`] before accessing a variable.
11///
12/// # Built-in implementations
13///
14/// | Type | Behaviour |
15/// |------|-----------|
16/// | [`Unrestricted`] | No checks (default) |
17/// | [`EnvAllowList`] | Allow only listed variable names |
18///
19/// # Custom implementations
20///
21/// ```rust,no_run
22/// use mlua_batteries::policy::{EnvPolicy, PolicyError};
23///
24/// struct DenySecrets;
25///
26/// impl EnvPolicy for DenySecrets {
27/// fn check_get(&self, key: &str) -> Result<(), PolicyError> {
28/// if key.contains("SECRET") || key.contains("TOKEN") {
29/// Err(PolicyError::new(format!("read denied: env var '{key}' looks sensitive")))
30/// } else {
31/// Ok(())
32/// }
33/// }
34/// fn check_set(&self, key: &str) -> Result<(), PolicyError> {
35/// self.check_get(key) // same rules for set
36/// }
37/// }
38/// ```
39pub trait EnvPolicy: Send + Sync + 'static {
40 /// Human-readable name for this policy, used in `Debug` output.
41 ///
42 /// The default implementation returns [`std::any::type_name`] of the
43 /// concrete type, which works correctly even through trait objects
44 /// because the vtable dispatches to the concrete implementation.
45 fn policy_name(&self) -> &'static str {
46 std::any::type_name::<Self>()
47 }
48
49 /// Validate read access to env var `key`.
50 ///
51 /// Return `Ok(())` to allow, `Err(reason)` to deny.
52 fn check_get(&self, key: &str) -> Result<(), PolicyError>;
53
54 /// Validate write access (overlay set) to env var `key`.
55 ///
56 /// Return `Ok(())` to allow, `Err(reason)` to deny.
57 fn check_set(&self, key: &str) -> Result<(), PolicyError>;
58}
59
60impl EnvPolicy for Unrestricted {
61 fn check_get(&self, _key: &str) -> Result<(), PolicyError> {
62 Ok(())
63 }
64 fn check_set(&self, _key: &str) -> Result<(), PolicyError> {
65 Ok(())
66 }
67}
68
69/// Allow access only to listed environment variable names.
70///
71/// ```rust,no_run
72/// use mlua_batteries::policy::EnvAllowList;
73///
74/// let policy = EnvAllowList::new(["HOME", "PATH", "LANG"]);
75/// ```
76#[derive(Debug)]
77pub struct EnvAllowList {
78 allowed_keys: HashSet<String>,
79 allow_set: bool,
80}
81
82impl EnvAllowList {
83 /// Create an allow-list for the given variable names.
84 ///
85 /// By default, `set` is allowed for variables in the list.
86 pub fn new<I, S>(keys: I) -> Self
87 where
88 I: IntoIterator<Item = S>,
89 S: Into<String>,
90 {
91 Self {
92 allowed_keys: keys.into_iter().map(Into::into).collect::<HashSet<_>>(),
93 allow_set: true,
94 }
95 }
96
97 /// Deny all `env.set()` calls regardless of key.
98 pub fn read_only(mut self) -> Self {
99 self.allow_set = false;
100 self
101 }
102}
103
104impl EnvPolicy for EnvAllowList {
105 fn check_get(&self, key: &str) -> Result<(), PolicyError> {
106 if self.allowed_keys.contains(key) {
107 Ok(())
108 } else {
109 Err(PolicyError::new(format!(
110 "read denied: env var '{key}' is not in the allow list"
111 )))
112 }
113 }
114
115 fn check_set(&self, key: &str) -> Result<(), PolicyError> {
116 if !self.allow_set {
117 return Err(PolicyError::new(format!(
118 "set denied: env is read-only (key '{key}')"
119 )));
120 }
121 if self.allowed_keys.contains(key) {
122 Ok(())
123 } else {
124 Err(PolicyError::new(format!(
125 "set denied: env var '{key}' is not in the allow list"
126 )))
127 }
128 }
129}