feat(wfe-yaml): add deno_core JS/TS executor with sandboxed permissions
Secure JavaScript/TypeScript execution in workflow steps via deno_core, behind the `deno` feature flag. Security features: - Per-step permission system: net host allowlist, filesystem read/write path restrictions, env var allowlist, subprocess spawn control - V8 heap limits (64MB default) prevent memory exhaustion - Execution timeout with V8 isolate termination for sync infinite loops - Path traversal detection blocks ../ escape attempts - Dynamic import rejection unless explicitly enabled Workflow I/O ops: - inputs() — read workflow data as JSON - output(key, value) — set step outputs - log(message) — structured tracing Architecture: - JsRuntime runs on dedicated thread (V8 is !Send) - PermissionChecker enforced on every I/O op via OpState - DenoStep implements StepBody, integrates with existing compiler - Step type dispatch: "shell" or "deno" in YAML 34 new tests (12 permission unit, 3 config, 2 runtime, 18 integration).
This commit is contained in:
81
wfe-yaml/src/executors/deno/runtime.rs
Normal file
81
wfe-yaml/src/executors/deno/runtime.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::RuntimeOptions;
|
||||
use wfe_core::WfeError;
|
||||
|
||||
use super::config::DenoConfig;
|
||||
use super::ops::workflow::{wfe_ops, StepMeta, StepOutputs, WorkflowInputs};
|
||||
use super::permissions::PermissionChecker;
|
||||
|
||||
/// Create a configured `JsRuntime` for executing a workflow step script.
|
||||
pub fn create_runtime(
|
||||
config: &DenoConfig,
|
||||
workflow_data: serde_json::Value,
|
||||
step_name: &str,
|
||||
) -> Result<JsRuntime, WfeError> {
|
||||
let ext = wfe_ops::init();
|
||||
|
||||
let runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![ext],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Populate OpState with our workflow types.
|
||||
{
|
||||
let state = runtime.op_state();
|
||||
let mut state = state.borrow_mut();
|
||||
state.put(WorkflowInputs {
|
||||
data: workflow_data,
|
||||
});
|
||||
state.put(StepOutputs {
|
||||
map: HashMap::new(),
|
||||
});
|
||||
state.put(StepMeta {
|
||||
name: step_name.to_string(),
|
||||
});
|
||||
state.put(PermissionChecker::from_config(&config.permissions));
|
||||
}
|
||||
|
||||
Ok(runtime)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::config::DenoPermissions;
|
||||
|
||||
#[test]
|
||||
fn create_runtime_succeeds() {
|
||||
let config = DenoConfig {
|
||||
script: Some("1+1".to_string()),
|
||||
file: None,
|
||||
permissions: DenoPermissions::default(),
|
||||
modules: vec![],
|
||||
env: HashMap::new(),
|
||||
timeout_ms: None,
|
||||
};
|
||||
let runtime = create_runtime(&config, serde_json::json!({}), "test-step");
|
||||
assert!(runtime.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_runtime_has_op_state() {
|
||||
let config = DenoConfig {
|
||||
script: None,
|
||||
file: None,
|
||||
permissions: DenoPermissions::default(),
|
||||
modules: vec![],
|
||||
env: HashMap::new(),
|
||||
timeout_ms: None,
|
||||
};
|
||||
let runtime =
|
||||
create_runtime(&config, serde_json::json!({"key": "val"}), "my-step").unwrap();
|
||||
let state = runtime.op_state();
|
||||
let state = state.borrow();
|
||||
let inputs = state.borrow::<WorkflowInputs>();
|
||||
assert_eq!(inputs.data, serde_json::json!({"key": "val"}));
|
||||
let meta = state.borrow::<StepMeta>();
|
||||
assert_eq!(meta.name, "my-step");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user