Skip to main content

secure_exec_sidecar/
limits.rs

1//! Typed, operator-tunable VM-scoped runtime limits.
2//!
3//! `VmLimits` is the single home for runtime bounds that operators may tune through the typed
4//! create-VM JSON config. Every field is a concrete value (not `Option`): the `Default` impls own
5//! the numbers and they are byte-identical to the historical hardcoded constants, so behavior is
6//! unchanged unless an operator overrides a config field.
7
8use secure_exec_kernel::resource_accounting::ResourceLimits;
9use secure_exec_vm_config::{ResourceLimitsConfig, VmLimitsConfig};
10
11use crate::state::SidecarError;
12use crate::wire::DEFAULT_MAX_FRAME_BYTES;
13
14/// Default cap on `vm.fetch()` buffered response bodies. Historically aliased to the wire frame
15/// cap; decoupled here but still validated to stay within the negotiated frame budget.
16pub const DEFAULT_MAX_FETCH_RESPONSE_BYTES: usize = DEFAULT_MAX_FRAME_BYTES;
17
18pub const DEFAULT_TOOL_TIMEOUT_MS: u64 = 30_000;
19pub const MAX_TOOL_TIMEOUT_MS: u64 = 300_000;
20pub const MAX_REGISTERED_TOOLKITS: usize = 64;
21pub const MAX_REGISTERED_TOOLS_PER_VM: usize = 256;
22pub const MAX_TOOLS_PER_TOOLKIT: usize = 64;
23pub const MAX_TOOL_SCHEMA_BYTES: usize = 16 * 1024;
24pub const MAX_TOOL_EXAMPLES_PER_TOOL: usize = 16;
25pub const MAX_TOOL_EXAMPLE_INPUT_BYTES: usize = 4 * 1024;
26
27pub const MAX_PERSISTED_MANIFEST_BYTES: usize = 64 * 1024 * 1024;
28pub const MAX_PERSISTED_MANIFEST_FILE_BYTES: u64 = 1024 * 1024 * 1024;
29
30pub const DEFAULT_ACP_MAX_READ_LINE_BYTES: usize = 16 * 1024 * 1024;
31pub const DEFAULT_ACP_STDOUT_BUFFER_BYTE_LIMIT: usize = 1024 * 1024;
32
33pub const DEFAULT_JS_CAPTURED_OUTPUT_LIMIT_BYTES: usize = 16 * 1024 * 1024;
34pub const DEFAULT_JS_STDIN_BUFFER_LIMIT_BYTES: usize = 16 * 1024 * 1024;
35pub const DEFAULT_JS_EVENT_PAYLOAD_LIMIT_BYTES: usize = 1024 * 1024;
36pub const DEFAULT_V8_IPC_MAX_FRAME_BYTES: u32 = 64 * 1024 * 1024;
37
38pub const DEFAULT_PYTHON_OUTPUT_BUFFER_MAX_BYTES: usize = 1024 * 1024;
39pub const DEFAULT_PYTHON_EXECUTION_TIMEOUT_MS: u64 = 5 * 60 * 1000;
40pub const DEFAULT_PYTHON_VFS_RPC_TIMEOUT_MS: u64 = 30 * 1000;
41
42pub const DEFAULT_WASM_MAX_MODULE_FILE_BYTES: u64 = 256 * 1024 * 1024;
43pub const DEFAULT_WASM_CAPTURED_OUTPUT_LIMIT_BYTES: usize = 16 * 1024 * 1024;
44pub const DEFAULT_WASM_SYNC_READ_LIMIT_BYTES: usize = 16 * 1024 * 1024;
45
46/// All operator-tunable VM-scoped limits. Fields are concrete values; the `Default` impls own the
47/// numbers and equal today's hardcoded constants, so unset operator config leaves behavior
48/// unchanged.
49#[derive(Debug, Clone, PartialEq, Eq, Default)]
50pub struct VmLimits {
51    /// Kernel resource limits (existing type, existing `resource.*` keys).
52    pub resources: ResourceLimits,
53    pub http: HttpLimits,
54    pub tools: ToolLimits,
55    pub plugins: PluginLimits,
56    pub acp: AcpLimits,
57    pub js_runtime: JsRuntimeLimits,
58    pub python: PythonLimits,
59    pub wasm: WasmLimits,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct HttpLimits {
64    /// Cap on `vm.fetch()` buffered response bodies. Must be `<=` the sidecar wire frame cap.
65    pub max_fetch_response_bytes: usize,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct ToolLimits {
70    pub default_tool_timeout_ms: u64,
71    pub max_tool_timeout_ms: u64,
72    pub max_registered_toolkits: usize,
73    pub max_registered_tools_per_vm: usize,
74    pub max_tools_per_toolkit: usize,
75    pub max_tool_schema_bytes: usize,
76    pub max_tool_examples_per_tool: usize,
77    pub max_tool_example_input_bytes: usize,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct PluginLimits {
82    pub max_persisted_manifest_bytes: usize,
83    pub max_persisted_manifest_file_bytes: u64,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct AcpLimits {
88    /// Maximum length of a single ACP adapter stdout line. Threaded into `AcpClientOptions`.
89    pub max_read_line_bytes: usize,
90    /// Pre-session ACP adapter stdout buffer cap.
91    pub stdout_buffer_byte_limit: usize,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct JsRuntimeLimits {
96    /// `None` keeps the V8 engine default heap. Maps to the existing `AGENT_OS_V8_HEAP_LIMIT_MB`
97    /// per-execution env knob.
98    pub v8_heap_limit_mb: Option<u32>,
99    pub captured_output_limit_bytes: usize,
100    pub stdin_buffer_limit_bytes: usize,
101    pub event_payload_limit_bytes: usize,
102    /// V8 IPC codec frame cap. Must feed both codec sides (`crates/execution/src/v8_ipc.rs` and
103    /// `crates/v8-runtime/src/ipc_binary.rs`).
104    pub v8_ipc_max_frame_bytes: u32,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct PythonLimits {
109    pub output_buffer_max_bytes: usize,
110    pub execution_timeout_ms: u64,
111    pub vfs_rpc_timeout_ms: u64,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct WasmLimits {
116    pub max_module_file_bytes: u64,
117    pub captured_output_limit_bytes: usize,
118    /// WASM sync read cap. Also templated into the JS runner shim, so it must flow from one field.
119    pub sync_read_limit_bytes: usize,
120}
121
122impl Default for HttpLimits {
123    fn default() -> Self {
124        Self {
125            max_fetch_response_bytes: DEFAULT_MAX_FETCH_RESPONSE_BYTES,
126        }
127    }
128}
129
130impl Default for ToolLimits {
131    fn default() -> Self {
132        Self {
133            default_tool_timeout_ms: DEFAULT_TOOL_TIMEOUT_MS,
134            max_tool_timeout_ms: MAX_TOOL_TIMEOUT_MS,
135            max_registered_toolkits: MAX_REGISTERED_TOOLKITS,
136            max_registered_tools_per_vm: MAX_REGISTERED_TOOLS_PER_VM,
137            max_tools_per_toolkit: MAX_TOOLS_PER_TOOLKIT,
138            max_tool_schema_bytes: MAX_TOOL_SCHEMA_BYTES,
139            max_tool_examples_per_tool: MAX_TOOL_EXAMPLES_PER_TOOL,
140            max_tool_example_input_bytes: MAX_TOOL_EXAMPLE_INPUT_BYTES,
141        }
142    }
143}
144
145impl Default for PluginLimits {
146    fn default() -> Self {
147        Self {
148            max_persisted_manifest_bytes: MAX_PERSISTED_MANIFEST_BYTES,
149            max_persisted_manifest_file_bytes: MAX_PERSISTED_MANIFEST_FILE_BYTES,
150        }
151    }
152}
153
154impl Default for AcpLimits {
155    fn default() -> Self {
156        Self {
157            max_read_line_bytes: DEFAULT_ACP_MAX_READ_LINE_BYTES,
158            stdout_buffer_byte_limit: DEFAULT_ACP_STDOUT_BUFFER_BYTE_LIMIT,
159        }
160    }
161}
162
163impl Default for JsRuntimeLimits {
164    fn default() -> Self {
165        Self {
166            v8_heap_limit_mb: None,
167            captured_output_limit_bytes: DEFAULT_JS_CAPTURED_OUTPUT_LIMIT_BYTES,
168            stdin_buffer_limit_bytes: DEFAULT_JS_STDIN_BUFFER_LIMIT_BYTES,
169            event_payload_limit_bytes: DEFAULT_JS_EVENT_PAYLOAD_LIMIT_BYTES,
170            v8_ipc_max_frame_bytes: DEFAULT_V8_IPC_MAX_FRAME_BYTES,
171        }
172    }
173}
174
175impl Default for PythonLimits {
176    fn default() -> Self {
177        Self {
178            output_buffer_max_bytes: DEFAULT_PYTHON_OUTPUT_BUFFER_MAX_BYTES,
179            execution_timeout_ms: DEFAULT_PYTHON_EXECUTION_TIMEOUT_MS,
180            vfs_rpc_timeout_ms: DEFAULT_PYTHON_VFS_RPC_TIMEOUT_MS,
181        }
182    }
183}
184
185impl Default for WasmLimits {
186    fn default() -> Self {
187        Self {
188            max_module_file_bytes: DEFAULT_WASM_MAX_MODULE_FILE_BYTES,
189            captured_output_limit_bytes: DEFAULT_WASM_CAPTURED_OUTPUT_LIMIT_BYTES,
190            sync_read_limit_bytes: DEFAULT_WASM_SYNC_READ_LIMIT_BYTES,
191        }
192    }
193}
194
195pub fn vm_limits_from_config(
196    config: Option<&VmLimitsConfig>,
197    sidecar_max_frame_bytes: usize,
198) -> Result<VmLimits, SidecarError> {
199    let mut limits = VmLimits::default();
200    let Some(config) = config else {
201        validate_vm_limits(&limits, sidecar_max_frame_bytes)?;
202        return Ok(limits);
203    };
204
205    if let Some(resources) = config.resources.as_ref() {
206        apply_resource_limits_config(&mut limits.resources, resources)?;
207    }
208    if let Some(http) = config.http.as_ref() {
209        set_usize(
210            &mut limits.http.max_fetch_response_bytes,
211            http.max_fetch_response_bytes,
212            "limits.http.maxFetchResponseBytes",
213        )?;
214    }
215    if let Some(tools) = config.tools.as_ref() {
216        set_u64(
217            &mut limits.tools.default_tool_timeout_ms,
218            tools.default_tool_timeout_ms,
219            "limits.tools.defaultToolTimeoutMs",
220        )?;
221        set_u64(
222            &mut limits.tools.max_tool_timeout_ms,
223            tools.max_tool_timeout_ms,
224            "limits.tools.maxToolTimeoutMs",
225        )?;
226        set_usize(
227            &mut limits.tools.max_registered_toolkits,
228            tools.max_registered_toolkits,
229            "limits.tools.maxRegisteredToolkits",
230        )?;
231        set_usize(
232            &mut limits.tools.max_registered_tools_per_vm,
233            tools.max_registered_tools_per_vm,
234            "limits.tools.maxRegisteredToolsPerVm",
235        )?;
236        set_usize(
237            &mut limits.tools.max_tools_per_toolkit,
238            tools.max_tools_per_toolkit,
239            "limits.tools.maxToolsPerToolkit",
240        )?;
241        set_usize(
242            &mut limits.tools.max_tool_schema_bytes,
243            tools.max_tool_schema_bytes,
244            "limits.tools.maxToolSchemaBytes",
245        )?;
246        set_usize(
247            &mut limits.tools.max_tool_examples_per_tool,
248            tools.max_tool_examples_per_tool,
249            "limits.tools.maxToolExamplesPerTool",
250        )?;
251        set_usize(
252            &mut limits.tools.max_tool_example_input_bytes,
253            tools.max_tool_example_input_bytes,
254            "limits.tools.maxToolExampleInputBytes",
255        )?;
256    }
257    if let Some(plugins) = config.plugins.as_ref() {
258        set_usize(
259            &mut limits.plugins.max_persisted_manifest_bytes,
260            plugins.max_persisted_manifest_bytes,
261            "limits.plugins.maxPersistedManifestBytes",
262        )?;
263        set_u64(
264            &mut limits.plugins.max_persisted_manifest_file_bytes,
265            plugins.max_persisted_manifest_file_bytes,
266            "limits.plugins.maxPersistedManifestFileBytes",
267        )?;
268    }
269    if let Some(acp) = config.acp.as_ref() {
270        set_usize(
271            &mut limits.acp.max_read_line_bytes,
272            acp.max_read_line_bytes,
273            "limits.acp.maxReadLineBytes",
274        )?;
275        set_usize(
276            &mut limits.acp.stdout_buffer_byte_limit,
277            acp.stdout_buffer_byte_limit,
278            "limits.acp.stdoutBufferByteLimit",
279        )?;
280    }
281    if let Some(js_runtime) = config.js_runtime.as_ref() {
282        if let Some(value) = js_runtime.v8_heap_limit_mb {
283            limits.js_runtime.v8_heap_limit_mb = Some(
284                u32::try_from(value)
285                    .map_err(|_| integer_too_large("limits.jsRuntime.v8HeapLimitMb", value))?,
286            );
287        }
288        set_usize(
289            &mut limits.js_runtime.captured_output_limit_bytes,
290            js_runtime.captured_output_limit_bytes,
291            "limits.jsRuntime.capturedOutputLimitBytes",
292        )?;
293        set_usize(
294            &mut limits.js_runtime.stdin_buffer_limit_bytes,
295            js_runtime.stdin_buffer_limit_bytes,
296            "limits.jsRuntime.stdinBufferLimitBytes",
297        )?;
298        set_usize(
299            &mut limits.js_runtime.event_payload_limit_bytes,
300            js_runtime.event_payload_limit_bytes,
301            "limits.jsRuntime.eventPayloadLimitBytes",
302        )?;
303        if let Some(value) = js_runtime.v8_ipc_max_frame_bytes {
304            limits.js_runtime.v8_ipc_max_frame_bytes = u32::try_from(value)
305                .map_err(|_| integer_too_large("limits.jsRuntime.v8IpcMaxFrameBytes", value))?;
306        }
307    }
308    if let Some(python) = config.python.as_ref() {
309        set_usize(
310            &mut limits.python.output_buffer_max_bytes,
311            python.output_buffer_max_bytes,
312            "limits.python.outputBufferMaxBytes",
313        )?;
314        set_u64(
315            &mut limits.python.execution_timeout_ms,
316            python.execution_timeout_ms,
317            "limits.python.executionTimeoutMs",
318        )?;
319        set_u64(
320            &mut limits.python.vfs_rpc_timeout_ms,
321            python.vfs_rpc_timeout_ms,
322            "limits.python.vfsRpcTimeoutMs",
323        )?;
324    }
325    if let Some(wasm) = config.wasm.as_ref() {
326        set_u64(
327            &mut limits.wasm.max_module_file_bytes,
328            wasm.max_module_file_bytes,
329            "limits.wasm.maxModuleFileBytes",
330        )?;
331        set_usize(
332            &mut limits.wasm.captured_output_limit_bytes,
333            wasm.captured_output_limit_bytes,
334            "limits.wasm.capturedOutputLimitBytes",
335        )?;
336        set_usize(
337            &mut limits.wasm.sync_read_limit_bytes,
338            wasm.sync_read_limit_bytes,
339            "limits.wasm.syncReadLimitBytes",
340        )?;
341    }
342
343    validate_vm_limits(&limits, sidecar_max_frame_bytes)?;
344    Ok(limits)
345}
346
347fn apply_resource_limits_config(
348    limits: &mut ResourceLimits,
349    config: &ResourceLimitsConfig,
350) -> Result<(), SidecarError> {
351    set_optional_usize(
352        &mut limits.virtual_cpu_count,
353        config.cpu_count,
354        "limits.resources.cpuCount",
355    )?;
356    set_optional_usize(
357        &mut limits.max_processes,
358        config.max_processes,
359        "limits.resources.maxProcesses",
360    )?;
361    set_optional_usize(
362        &mut limits.max_open_fds,
363        config.max_open_fds,
364        "limits.resources.maxOpenFds",
365    )?;
366    set_optional_usize(
367        &mut limits.max_pipes,
368        config.max_pipes,
369        "limits.resources.maxPipes",
370    )?;
371    set_optional_usize(
372        &mut limits.max_ptys,
373        config.max_ptys,
374        "limits.resources.maxPtys",
375    )?;
376    set_optional_usize(
377        &mut limits.max_sockets,
378        config.max_sockets,
379        "limits.resources.maxSockets",
380    )?;
381    set_optional_usize(
382        &mut limits.max_connections,
383        config.max_connections,
384        "limits.resources.maxConnections",
385    )?;
386    set_optional_usize(
387        &mut limits.max_socket_buffered_bytes,
388        config.max_socket_buffered_bytes,
389        "limits.resources.maxSocketBufferedBytes",
390    )?;
391    set_optional_usize(
392        &mut limits.max_socket_datagram_queue_len,
393        config.max_socket_datagram_queue_len,
394        "limits.resources.maxSocketDatagramQueueLen",
395    )?;
396    set_optional_u64(
397        &mut limits.max_filesystem_bytes,
398        config.max_filesystem_bytes,
399    );
400    set_optional_usize(
401        &mut limits.max_inode_count,
402        config.max_inode_count,
403        "limits.resources.maxInodeCount",
404    )?;
405    set_optional_u64(
406        &mut limits.max_blocking_read_ms,
407        config.max_blocking_read_ms,
408    );
409    set_optional_usize(
410        &mut limits.max_pread_bytes,
411        config.max_pread_bytes,
412        "limits.resources.maxPreadBytes",
413    )?;
414    set_optional_usize(
415        &mut limits.max_fd_write_bytes,
416        config.max_fd_write_bytes,
417        "limits.resources.maxFdWriteBytes",
418    )?;
419    set_optional_usize(
420        &mut limits.max_process_argv_bytes,
421        config.max_process_argv_bytes,
422        "limits.resources.maxProcessArgvBytes",
423    )?;
424    set_optional_usize(
425        &mut limits.max_process_env_bytes,
426        config.max_process_env_bytes,
427        "limits.resources.maxProcessEnvBytes",
428    )?;
429    set_optional_usize(
430        &mut limits.max_readdir_entries,
431        config.max_readdir_entries,
432        "limits.resources.maxReaddirEntries",
433    )?;
434    set_optional_u64(&mut limits.max_wasm_fuel, config.max_wasm_fuel);
435    set_optional_u64(
436        &mut limits.max_wasm_memory_bytes,
437        config.max_wasm_memory_bytes,
438    );
439    set_optional_usize(
440        &mut limits.max_wasm_stack_bytes,
441        config.max_wasm_stack_bytes,
442        "limits.resources.maxWasmStackBytes",
443    )?;
444    Ok(())
445}
446
447fn set_usize(target: &mut usize, value: Option<u64>, key: &str) -> Result<(), SidecarError> {
448    if let Some(value) = value {
449        *target = usize::try_from(value).map_err(|_| integer_too_large(key, value))?;
450    }
451    Ok(())
452}
453
454fn set_u64(target: &mut u64, value: Option<u64>, _key: &str) -> Result<(), SidecarError> {
455    if let Some(value) = value {
456        *target = value;
457    }
458    Ok(())
459}
460
461fn set_optional_usize(
462    target: &mut Option<usize>,
463    value: Option<u64>,
464    key: &str,
465) -> Result<(), SidecarError> {
466    if let Some(value) = value {
467        *target = Some(usize::try_from(value).map_err(|_| integer_too_large(key, value))?);
468    }
469    Ok(())
470}
471
472fn set_optional_u64(target: &mut Option<u64>, value: Option<u64>) {
473    if let Some(value) = value {
474        *target = Some(value);
475    }
476}
477
478fn integer_too_large(key: &str, value: u64) -> SidecarError {
479    SidecarError::InvalidState(format!("{key} value {value} does not fit this platform"))
480}
481
482/// Cross-field validation. Fail-by-default: reject any configuration that would deadlock or
483/// violate the wire frame budget with an explicit, actionable message.
484pub(crate) fn validate_vm_limits(
485    limits: &VmLimits,
486    sidecar_max_frame_bytes: usize,
487) -> Result<(), SidecarError> {
488    if limits.http.max_fetch_response_bytes == 0 {
489        return Err(SidecarError::InvalidState(
490            "limits.http.max_fetch_response_bytes must be greater than zero".to_string(),
491        ));
492    }
493    if limits.http.max_fetch_response_bytes > sidecar_max_frame_bytes {
494        return Err(SidecarError::InvalidState(format!(
495            "limits.http.max_fetch_response_bytes ({}) must be <= the sidecar wire frame cap ({})",
496            limits.http.max_fetch_response_bytes, sidecar_max_frame_bytes
497        )));
498    }
499
500    if limits.tools.default_tool_timeout_ms > limits.tools.max_tool_timeout_ms {
501        return Err(SidecarError::InvalidState(format!(
502            "limits.tools.default_tool_timeout_ms ({}) must be <= limits.tools.max_tool_timeout_ms ({})",
503            limits.tools.default_tool_timeout_ms, limits.tools.max_tool_timeout_ms
504        )));
505    }
506
507    let nonzero_usize: [(&str, usize); 13] = [
508        (
509            "limits.tools.max_registered_toolkits",
510            limits.tools.max_registered_toolkits,
511        ),
512        (
513            "limits.tools.max_registered_tools_per_vm",
514            limits.tools.max_registered_tools_per_vm,
515        ),
516        (
517            "limits.tools.max_tools_per_toolkit",
518            limits.tools.max_tools_per_toolkit,
519        ),
520        (
521            "limits.tools.max_tool_schema_bytes",
522            limits.tools.max_tool_schema_bytes,
523        ),
524        (
525            "limits.tools.max_tool_example_input_bytes",
526            limits.tools.max_tool_example_input_bytes,
527        ),
528        (
529            "limits.plugins.max_persisted_manifest_bytes",
530            limits.plugins.max_persisted_manifest_bytes,
531        ),
532        (
533            "limits.acp.max_read_line_bytes",
534            limits.acp.max_read_line_bytes,
535        ),
536        (
537            "limits.acp.stdout_buffer_byte_limit",
538            limits.acp.stdout_buffer_byte_limit,
539        ),
540        (
541            "limits.js_runtime.captured_output_limit_bytes",
542            limits.js_runtime.captured_output_limit_bytes,
543        ),
544        (
545            "limits.js_runtime.stdin_buffer_limit_bytes",
546            limits.js_runtime.stdin_buffer_limit_bytes,
547        ),
548        (
549            "limits.js_runtime.event_payload_limit_bytes",
550            limits.js_runtime.event_payload_limit_bytes,
551        ),
552        (
553            "limits.python.output_buffer_max_bytes",
554            limits.python.output_buffer_max_bytes,
555        ),
556        (
557            "limits.wasm.captured_output_limit_bytes",
558            limits.wasm.captured_output_limit_bytes,
559        ),
560    ];
561    for (key, value) in nonzero_usize {
562        if value == 0 {
563            return Err(SidecarError::InvalidState(format!(
564                "{key} must be greater than zero"
565            )));
566        }
567    }
568
569    if limits.wasm.sync_read_limit_bytes == 0 {
570        return Err(SidecarError::InvalidState(
571            "limits.wasm.sync_read_limit_bytes must be greater than zero".to_string(),
572        ));
573    }
574    if limits.wasm.max_module_file_bytes == 0 {
575        return Err(SidecarError::InvalidState(
576            "limits.wasm.max_module_file_bytes must be greater than zero".to_string(),
577        ));
578    }
579    if limits.js_runtime.v8_ipc_max_frame_bytes == 0 {
580        return Err(SidecarError::InvalidState(
581            "limits.js_runtime.v8_ipc_max_frame_bytes must be greater than zero".to_string(),
582        ));
583    }
584    if limits.python.execution_timeout_ms == 0 {
585        return Err(SidecarError::InvalidState(
586            "limits.python.execution_timeout_ms must be greater than zero".to_string(),
587        ));
588    }
589    if limits.python.vfs_rpc_timeout_ms == 0 {
590        return Err(SidecarError::InvalidState(
591            "limits.python.vfs_rpc_timeout_ms must be greater than zero".to_string(),
592        ));
593    }
594    if let Some(0) = limits.js_runtime.v8_heap_limit_mb {
595        return Err(SidecarError::InvalidState(
596            "limits.js_runtime.v8_heap_limit_mb must be greater than zero".to_string(),
597        ));
598    }
599
600    Ok(())
601}