use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct DenoSandboxConfig {
pub timeout_ms: u64,
pub host_kill_timeout_ms: u64,
pub max_ops: u64,
pub max_source_bytes: usize,
pub max_output_bytes: usize,
pub local_fetch_root: String,
pub allow_list: DenoSandboxAllowList,
pub capabilities: Vec<String>,
pub danger_zone: DenoSandboxDangerZone,
}
impl Default for DenoSandboxConfig {
fn default() -> Self {
Self {
timeout_ms: 1_000,
host_kill_timeout_ms: 2_500,
max_ops: 1_000_000,
max_source_bytes: 128 * 1024,
max_output_bytes: 256 * 1024,
local_fetch_root: String::new(),
allow_list: DenoSandboxAllowList::default(),
capabilities: vec!["time.now".into(), "math.imul".into(), "math.u32".into()],
danger_zone: DenoSandboxDangerZone::default(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
pub struct DenoSandboxAllowList {
pub external_fetch_hosts: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
pub struct DenoSandboxDangerZone {
pub allow_dynamic_code: bool,
pub allow_import: bool,
pub allow_timers: bool,
pub allow_net: bool,
pub allow_net_hosts: Vec<String>,
pub allow_read_paths: Vec<String>,
pub allow_write_paths: Vec<String>,
pub allow_env_keys: Vec<String>,
pub allow_run: bool,
pub disable_loop_guards: bool,
pub allow_unbounded_limits: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
pub struct DenoSandboxConfigPatch {
pub timeout_ms: Option<u64>,
pub host_kill_timeout_ms: Option<u64>,
pub max_ops: Option<u64>,
pub max_source_bytes: Option<usize>,
pub max_output_bytes: Option<usize>,
pub local_fetch_root: Option<String>,
pub allow_list: Option<DenoSandboxAllowListPatch>,
pub capabilities: Option<Vec<String>>,
pub danger_zone: Option<DenoSandboxDangerZonePatch>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
pub struct DenoSandboxAllowListPatch {
pub external_fetch_hosts: Option<Vec<String>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
pub struct DenoSandboxDangerZonePatch {
pub allow_dynamic_code: Option<bool>,
pub allow_import: Option<bool>,
pub allow_timers: Option<bool>,
pub allow_net: Option<bool>,
pub allow_net_hosts: Option<Vec<String>>,
pub allow_read_paths: Option<Vec<String>>,
pub allow_write_paths: Option<Vec<String>>,
pub allow_env_keys: Option<Vec<String>>,
pub allow_run: Option<bool>,
pub disable_loop_guards: Option<bool>,
pub allow_unbounded_limits: Option<bool>,
}
pub(crate) fn apply_patch(cfg: &mut DenoSandboxConfig, patch: &DenoSandboxConfigPatch) {
if let Some(v) = patch.timeout_ms {
cfg.timeout_ms = v;
}
if let Some(v) = patch.host_kill_timeout_ms {
cfg.host_kill_timeout_ms = v;
}
if let Some(v) = patch.max_ops {
cfg.max_ops = v;
}
if let Some(v) = patch.max_source_bytes {
cfg.max_source_bytes = v;
}
if let Some(v) = patch.max_output_bytes {
cfg.max_output_bytes = v;
}
if let Some(v) = &patch.local_fetch_root {
cfg.local_fetch_root = v.clone();
}
if let Some(list) = &patch.allow_list
&& let Some(v) = &list.external_fetch_hosts
{
cfg.allow_list.external_fetch_hosts = v.clone();
}
if let Some(v) = &patch.capabilities {
cfg.capabilities = v.clone();
}
if let Some(dz) = &patch.danger_zone {
if let Some(v) = dz.allow_dynamic_code {
cfg.danger_zone.allow_dynamic_code = v;
}
if let Some(v) = dz.allow_import {
cfg.danger_zone.allow_import = v;
}
if let Some(v) = dz.allow_timers {
cfg.danger_zone.allow_timers = v;
}
if let Some(v) = dz.allow_net {
cfg.danger_zone.allow_net = v;
}
if let Some(v) = &dz.allow_net_hosts {
cfg.danger_zone.allow_net_hosts = v.clone();
}
if let Some(v) = &dz.allow_read_paths {
cfg.danger_zone.allow_read_paths = v.clone();
}
if let Some(v) = &dz.allow_write_paths {
cfg.danger_zone.allow_write_paths = v.clone();
}
if let Some(v) = &dz.allow_env_keys {
cfg.danger_zone.allow_env_keys = v.clone();
}
if let Some(v) = dz.allow_run {
cfg.danger_zone.allow_run = v;
}
if let Some(v) = dz.disable_loop_guards {
cfg.danger_zone.disable_loop_guards = v;
}
if let Some(v) = dz.allow_unbounded_limits {
cfg.danger_zone.allow_unbounded_limits = v;
}
}
}
pub(crate) fn normalize_limits(cfg: &mut DenoSandboxConfig) {
if !cfg.danger_zone.allow_unbounded_limits {
cfg.timeout_ms = cfg.timeout_ms.clamp(5, 60_000);
cfg.host_kill_timeout_ms = cfg.host_kill_timeout_ms.clamp(10, 120_000);
cfg.max_ops = cfg.max_ops.clamp(1_000, 50_000_000);
cfg.max_source_bytes = cfg.max_source_bytes.clamp(1_024, 1_024 * 1_024);
cfg.max_output_bytes = cfg.max_output_bytes.clamp(256, 1_024 * 1_024);
}
if cfg.host_kill_timeout_ms < cfg.timeout_ms + 5 {
cfg.host_kill_timeout_ms = cfg.timeout_ms + 5;
}
cfg.local_fetch_root = cfg.local_fetch_root.trim().to_string();
let mut hosts: Vec<String> = cfg
.allow_list
.external_fetch_hosts
.iter()
.map(|h| h.trim().to_ascii_lowercase())
.filter(|h| !h.is_empty())
.collect();
hosts.sort();
hosts.dedup();
cfg.allow_list.external_fetch_hosts = hosts;
}