use crate::error::{Error, Result};
pub const ALLOW_DANGEROUS_ENV: &str = "CODEX_WRAPPER_ALLOW_DANGEROUS";
#[derive(Debug, Clone, Copy)]
pub struct DangerousClient {
_private: (),
}
impl DangerousClient {
pub fn new() -> Result<Self> {
allowed(&|key| std::env::var(key).ok())?;
Ok(Self { _private: () })
}
#[cfg(test)]
pub(crate) fn unchecked() -> Self {
Self { _private: () }
}
}
pub(crate) fn allowed(env: &impl Fn(&str) -> Option<String>) -> Result<()> {
match env(ALLOW_DANGEROUS_ENV) {
Some(value) if !value.trim().is_empty() => Ok(()),
_ => Err(Error::DangerousNotAllowed {
variable: ALLOW_DANGEROUS_ENV,
}),
}
}
mod sealed {
pub trait Sealed {}
}
pub trait Dangerous: sealed::Sealed + Sized {
fn bypass_approvals_and_sandbox(self, allow: &DangerousClient) -> Result<Self>;
fn bypass_hook_trust(self, allow: &DangerousClient) -> Result<Self>;
}
macro_rules! impl_dangerous {
($($ty:ty),+ $(,)?) => {
$(
impl sealed::Sealed for $ty {}
impl Dangerous for $ty {
fn bypass_approvals_and_sandbox(
self,
_allow: &DangerousClient,
) -> Result<Self> {
allowed(&|key| std::env::var(key).ok())?;
Ok(self.set_bypass_approvals_and_sandbox())
}
fn bypass_hook_trust(self, _allow: &DangerousClient) -> Result<Self> {
allowed(&|key| std::env::var(key).ok())?;
Ok(self.set_bypass_hook_trust())
}
}
)+
};
}
impl_dangerous!(
crate::command::exec::ExecCommand,
crate::command::exec::ExecResumeCommand,
crate::command::review::ReviewCommand,
crate::command::fork::ForkCommand,
crate::command::resume::ResumeCommand,
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_gate_is_closed_by_default() {
let err = DangerousClient::new().unwrap_err();
assert!(matches!(err, Error::DangerousNotAllowed { .. }), "{err:?}");
assert!(err.to_string().contains(ALLOW_DANGEROUS_ENV));
}
#[test]
fn the_gate_opens_for_a_non_empty_value() {
assert!(allowed(&|_| Some("1".into())).is_ok());
assert!(allowed(&|_| Some("anything".into())).is_ok());
}
#[test]
fn a_blank_value_does_not_open_the_gate() {
assert!(allowed(&|_| Some(String::new())).is_err());
assert!(allowed(&|_| Some(" ".into())).is_err());
assert!(allowed(&|_| None).is_err());
}
#[test]
fn holding_a_client_is_not_enough() {
use crate::command::exec::ExecCommand;
let stale = DangerousClient::unchecked();
let err = ExecCommand::new("rewrite everything")
.bypass_approvals_and_sandbox(&stale)
.unwrap_err();
assert!(matches!(err, Error::DangerousNotAllowed { .. }), "{err:?}");
let err = ExecCommand::new("rewrite everything")
.bypass_hook_trust(&stale)
.unwrap_err();
assert!(matches!(err, Error::DangerousNotAllowed { .. }), "{err:?}");
}
#[test]
fn the_flags_still_reach_argv_once_set() {
use crate::command::exec::ExecCommand;
use crate::command::{CodexCommand, review::ReviewCommand};
let args = ExecCommand::new("x")
.set_bypass_approvals_and_sandbox()
.set_bypass_hook_trust()
.args();
assert!(
args.iter()
.any(|a| a == "--dangerously-bypass-approvals-and-sandbox"),
"{args:?}"
);
assert!(
args.iter().any(|a| a == "--dangerously-bypass-hook-trust"),
"{args:?}"
);
let args = ReviewCommand::new()
.uncommitted()
.set_bypass_approvals_and_sandbox()
.args();
assert!(
args.iter()
.any(|a| a == "--dangerously-bypass-approvals-and-sandbox"),
"{args:?}"
);
}
}