Files
wfe/wfe-yaml/src/schema.rs
Sienna Meridian Satterwhite 6fec7dbab5 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).
2026-03-25 22:32:07 +00:00

94 lines
2.3 KiB
Rust

use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct YamlWorkflow {
pub workflow: WorkflowSpec,
}
#[derive(Debug, Deserialize)]
pub struct WorkflowSpec {
pub id: String,
pub version: u32,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub error_behavior: Option<YamlErrorBehavior>,
pub steps: Vec<YamlStep>,
/// Allow unknown top-level keys (e.g. `_templates`) for YAML anchors.
#[serde(flatten)]
pub _extra: HashMap<String, serde_yaml::Value>,
}
#[derive(Debug, Deserialize)]
pub struct YamlStep {
pub name: String,
#[serde(rename = "type")]
pub step_type: Option<String>,
#[serde(default)]
pub config: Option<StepConfig>,
#[serde(default)]
pub inputs: Vec<DataRef>,
#[serde(default)]
pub outputs: Vec<DataRef>,
#[serde(default)]
pub parallel: Option<Vec<YamlStep>>,
#[serde(default)]
pub error_behavior: Option<YamlErrorBehavior>,
#[serde(default)]
pub on_success: Option<Box<YamlStep>>,
#[serde(default)]
pub on_failure: Option<Box<YamlStep>>,
#[serde(default)]
pub ensure: Option<Box<YamlStep>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct StepConfig {
pub run: Option<String>,
pub file: Option<String>,
pub script: Option<String>,
pub shell: Option<String>,
#[serde(default)]
pub env: HashMap<String, String>,
pub timeout: Option<String>,
pub working_dir: Option<String>,
#[serde(default)]
pub permissions: Option<DenoPermissionsYaml>,
#[serde(default)]
pub modules: Vec<String>,
}
/// YAML-level permission configuration for Deno steps.
#[derive(Debug, Deserialize, Clone, Default)]
pub struct DenoPermissionsYaml {
#[serde(default)]
pub net: Vec<String>,
#[serde(default)]
pub read: Vec<String>,
#[serde(default)]
pub write: Vec<String>,
#[serde(default)]
pub env: Vec<String>,
#[serde(default)]
pub run: bool,
#[serde(default)]
pub dynamic_import: bool,
}
#[derive(Debug, Deserialize)]
pub struct DataRef {
pub name: String,
pub path: Option<String>,
pub json_path: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct YamlErrorBehavior {
#[serde(rename = "type")]
pub behavior_type: String,
pub interval: Option<String>,
pub max_retries: Option<u32>,
}