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