Skip to main content

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}