Skip to main content

leviath_scripting/
lib.rs

1//! # Leviath Scripting
2//!
3//! Rhai scripting integration for custom validators, transforms, and dynamic logic.
4//!
5//! This crate provides a sandboxed Rhai engine that allows users to define custom
6//! validators, context transforms, and compaction strategies without modifying
7//! Leviath's core code.
8
9pub mod engine;
10pub mod functions;
11pub mod output_validator;
12pub mod region_hook;
13pub mod sandbox;
14pub mod stage_hook;
15pub mod tool;
16pub mod types;
17
18/// Apply the sandbox limits every Leviath Rhai engine shares.
19///
20/// One function rather than a block copied into each engine constructor. There
21/// were three such copies - `ScriptEngine::new`, `build_tool_engine` (whose
22/// comment read "Same hardening as `ScriptEngine::new`", which it was not
23/// entirely), and the provider engine - with divergent limits and no way to add
24/// a control to all of them at once. This is a security control; it should have
25/// exactly one definition.
26///
27/// `max_operations` stays a parameter because it is a genuine policy difference:
28/// a provider script driving a streaming HTTP response legitimately runs longer
29/// than a validator.
30pub fn harden(engine: &mut rhai::Engine, max_operations: u64) {
31    // Bound runaway loops. The only wall-clock limit on pure computation.
32    engine.set_max_operations(max_operations);
33    engine.set_max_string_size(1_000_000);
34    engine.set_max_array_size(10_000);
35    engine.set_max_map_size(10_000);
36    // Bound *recursion*: without a call-depth cap, a script recursing to
37    // exhaustion overflows the native stack, which aborts the process rather
38    // than raising a catchable Rhai error. Rhai does not cap this by default.
39    engine.set_max_call_levels(64);
40    // Generous expression nesting. Rhai's default is much lower in debug builds
41    // (a stack-overflow guard for unoptimized code) and would reject legitimate
42    // scripts under `cargo test`.
43    engine.set_max_expr_depths(128, 128);
44    // `eval` compiles a fresh string at runtime, and the default module resolver
45    // lets `import` pull another `.rhai` off disk relative to the process CWD.
46    // Both reach code that never passed whatever review the script itself did.
47    engine.disable_symbol("eval");
48    engine.set_module_resolver(rhai::module_resolvers::DummyModuleResolver::new());
49    // No print/debug: script output would otherwise leak into daemon logs.
50    engine.on_print(|_| {});
51    engine.on_debug(|_, _, _| {});
52}
53
54pub use engine::ScriptEngine;
55pub use sandbox::SandboxConfig;
56pub use tool::{
57    ParamSpec, ScriptHost, ScriptToolMeta, ScriptToolSet, SkippedTool,
58    execute as execute_script_tool,
59};
60
61use thiserror::Error;
62
63/// Result type alias using Scripting's Error type.
64pub type Result<T> = std::result::Result<T, Error>;
65
66/// Error types for scripting operations.
67#[derive(Error, Debug)]
68pub enum Error {
69    /// Script execution failed
70    #[error("Script execution failed: {0}")]
71    ExecutionFailed(String),
72
73    /// Script compilation failed
74    #[error("Script compilation failed: {0}")]
75    CompilationFailed(String),
76
77    /// Script validation failed
78    #[error("Script validation failed: {0}")]
79    ValidationFailed(String),
80
81    /// Rhai engine error
82    #[error("Rhai error: {0}")]
83    RhaiError(#[from] Box<rhai::EvalAltResult>),
84
85    /// Core error
86    #[error("Core error: {0}")]
87    CoreError(#[from] leviath_core::Error),
88}