use prick_core::keyname;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum GuardError {
#[error(
"refusing to set `{name}` in the child environment: it is interpreted before the \
program starts, so its value controls what code runs. Pass --allow-unsafe-env to \
override."
)]
LoaderControlled {
name: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct EnvGuard {
allow_unsafe: bool,
}
impl EnvGuard {
pub fn strict() -> Self {
Self { allow_unsafe: false }
}
pub fn permissive() -> Self {
Self { allow_unsafe: true }
}
pub fn check(self, name: &str) -> Result<(), GuardError> {
if !self.allow_unsafe && keyname::is_loader_controlled(name) {
return Err(GuardError::LoaderControlled { name: name.to_owned() });
}
Ok(())
}
pub fn check_all<'a>(self, names: impl IntoIterator<Item = &'a str>) -> Result<(), GuardError> {
for name in names {
self.check(name)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_policy_is_strict() {
assert_eq!(EnvGuard::default(), EnvGuard::strict());
}
#[test]
fn loader_controlled_names_are_refused_by_default() {
let guard = EnvGuard::strict();
for name in ["LD_PRELOAD", "DYLD_INSERT_LIBRARIES", "PATH", "NODE_OPTIONS", "BASH_ENV"] {
assert_eq!(
guard.check(name),
Err(GuardError::LoaderControlled { name: name.to_owned() }),
"{name} was not refused"
);
}
}
#[test]
fn ordinary_names_pass() {
let guard = EnvGuard::strict();
for name in ["DATABASE_URL", "API_KEY", "STRIPE_SECRET"] {
assert_eq!(guard.check(name), Ok(()), "{name} was wrongly refused");
}
}
#[test]
fn the_opt_in_allows_everything() {
let guard = EnvGuard::permissive();
assert_eq!(guard.check("LD_PRELOAD"), Ok(()));
assert_eq!(guard.check("DATABASE_URL"), Ok(()));
}
#[test]
fn a_single_refusal_fails_the_whole_set() {
let guard = EnvGuard::strict();
let names = ["SAFE_ONE", "LD_PRELOAD", "SAFE_TWO"];
assert_eq!(
guard.check_all(names),
Err(GuardError::LoaderControlled { name: "LD_PRELOAD".to_owned() })
);
assert_eq!(guard.check_all(["SAFE_ONE", "SAFE_TWO"]), Ok(()));
}
#[test]
fn the_refusal_message_names_the_override() {
let err = EnvGuard::strict().check("LD_PRELOAD").unwrap_err();
let message = err.to_string();
assert!(message.contains("LD_PRELOAD"));
assert!(message.contains("--allow-unsafe-env"));
}
}