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}