//! Admin-set ceiling for per-script sandbox overrides. //! //! The orchestrator-core's default `Limits` is what scripts get when they //! don't override. The ceiling here is the per-field maximum that a //! script's override is allowed to request. Validation runs at write //! time so the executor can trust whatever's stored. use std::env; use picloud_shared::ScriptSandbox; use thiserror::Error; /// Maximum allowed value per sandbox knob. Loaded from env vars at /// startup (with conservative built-in defaults). A `None` field means /// "unbounded" — only useful if the operator explicitly clears the /// ceiling for a given knob (it must still fit `u64`). #[derive(Debug, Clone, Copy)] pub struct SandboxCeiling { pub max_operations: u64, pub max_string_size: u64, pub max_array_size: u64, pub max_map_size: u64, pub max_call_levels: u64, pub max_expr_depth: u64, } impl SandboxCeiling { /// Conservative built-in ceiling. Matches the executor's defaults — /// scripts can request anything between zero and this, but no /// higher. Operators can widen via env vars (see `from_env`). #[must_use] pub const fn conservative() -> Self { Self { max_operations: 10_000_000, max_string_size: 1024 * 1024, // 1 MiB max_array_size: 100_000, max_map_size: 100_000, max_call_levels: 128, max_expr_depth: 128, } } /// Read overrides from env vars, falling back to `conservative()` /// for any unset knob. Invalid values are ignored with a warning /// (we'd rather start with the conservative default than refuse to /// boot on a typo). #[must_use] pub fn from_env() -> Self { let mut c = Self::conservative(); macro_rules! load { ($field:ident, $key:expr) => { if let Ok(v) = env::var($key) { match v.parse::() { Ok(n) => c.$field = n, Err(e) => tracing::warn!(env = $key, error = %e, "ignoring invalid sandbox ceiling value"), } } }; } load!(max_operations, "PICLOUD_SANDBOX_MAX_OPERATIONS"); load!(max_string_size, "PICLOUD_SANDBOX_MAX_STRING_SIZE"); load!(max_array_size, "PICLOUD_SANDBOX_MAX_ARRAY_SIZE"); load!(max_map_size, "PICLOUD_SANDBOX_MAX_MAP_SIZE"); load!(max_call_levels, "PICLOUD_SANDBOX_MAX_CALL_LEVELS"); load!(max_expr_depth, "PICLOUD_SANDBOX_MAX_EXPR_DEPTH"); c } /// Returns `Err` if any override exceeds the ceiling on the same /// field. Empty overrides (`ScriptSandbox::empty()`) always pass. pub fn check(&self, s: &ScriptSandbox) -> Result<(), CeilingError> { check_field("max_operations", s.max_operations, self.max_operations)?; check_field("max_string_size", s.max_string_size, self.max_string_size)?; check_field("max_array_size", s.max_array_size, self.max_array_size)?; check_field("max_map_size", s.max_map_size, self.max_map_size)?; check_field("max_call_levels", s.max_call_levels, self.max_call_levels)?; check_field("max_expr_depth", s.max_expr_depth, self.max_expr_depth)?; Ok(()) } } fn check_field(name: &'static str, value: Option, ceiling: u64) -> Result<(), CeilingError> { if let Some(v) = value { if v > ceiling { return Err(CeilingError::Exceeded { field: name, requested: v, ceiling, }); } } Ok(()) } #[derive(Debug, Error, Clone)] pub enum CeilingError { #[error("sandbox override `{field}` = {requested} exceeds admin ceiling of {ceiling}")] Exceeded { field: &'static str, requested: u64, ceiling: u64, }, }