wifi-densepose/v2/crates/homecore-automation
rUv 5bc3b634b7
fix(automation security): template-bomb DoS (100MB/11s render → fuel-bounded, HIGH) + delay panic-on-config (MEDIUM) (#1083)
* fix(homecore-automation): bound template render to stop unbounded-expansion DoS (HC-SEC-01)

A `template:` condition / value_template comes straight from user
automation config and was rendered with MiniJinja's default (no
instruction budget, no output cap). A single condition such as
`{% for i in range(5000) %}{% for j in range(5000) %}xxxx{% endfor %}{% endfor %}`
rendered a 100 MB string over ~11 s on one render call (proven
empirically) — a CPU/memory denial of service, the bfld-class
"unbounded expansion".

Fix:
- Enable MiniJinja's `fuel` feature and set a per-render instruction
  budget (`set_fuel(Some(1_000_000))`). A nested loop burns one unit
  per iteration, so the budget caps total work regardless of nesting;
  the attack now fails fast (~90 ms) with "engine ran out of fuel".
- Reject template sources over 64 KiB before compilation (defense in
  depth so a pathological literal can neither compile nor emit verbatim).

Legitimate HA templates (a few dozen instructions) are unaffected.

Tests (fail on old — unbounded render / no rejection):
- nested_loop_template_is_bounded_not_unbounded_dos
- single_huge_repeat_template_is_bounded
- oversized_template_source_is_rejected
- legitimate_template_still_renders_within_fuel (no regression)

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(homecore-automation): stop crafted delay/timeout from panicking the run task (HC-SEC-02)

`Action::Delay { seconds }` and `Action::WaitForTrigger { timeout_seconds }`
fed the user-supplied float straight into `Duration::from_secs_f64`, which
PANICS on negative, NaN, infinite, or overflowing inputs. All of those are
reachable from a crafted (or simply typo'd) automation YAML —
`delay: {seconds: -1}`, `.nan`, `.inf`, `1e308` — so one hostile config
aborts the spawned automation task with a panic
("cannot convert float seconds to Duration: value is negative", proven
empirically).

Fix: a `safe_duration_from_secs` guard that saturates instead of panicking,
matching Home Assistant's lenient "non-positive delay = no delay":
- NaN / ±inf / negative -> Duration::ZERO
- absurdly large (would overflow) -> clamped to ~100 years (MAX_DELAY_SECS)

Tests (fail on old — panic = failure):
- delay_negative_seconds_does_not_panic
- delay_nan_seconds_does_not_panic
- delay_infinite_seconds_does_not_panic
- wait_for_trigger_negative_timeout_does_not_panic
- safe_duration_saturates_hostile_values (incl. overflow clamp)

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs(homecore-automation): record HC-SEC-01/02 security review (CHANGELOG + ADR-129 §8a)

Document the two DoS findings (template unbounded-expansion HC-SEC-01,
delay panic-on-config HC-SEC-02) and the dimensions probed clean
(condition fail-closed, bounded run-modes, sandboxed read-only templates).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-14 20:22:07 -04:00
..
src fix(automation security): template-bomb DoS (100MB/11s render → fuel-bounded, HIGH) + delay panic-on-config (MEDIUM) (#1083) 2026-06-14 20:22:07 -04:00
tests feat(homecore-automation): implement bounded RunModes Restart/Queued/max (ADR-162, completes ADR-161 §A5) 2026-06-12 01:40:23 -04:00
Cargo.toml fix(automation security): template-bomb DoS (100MB/11s render → fuel-bounded, HIGH) + delay panic-on-config (MEDIUM) (#1083) 2026-06-14 20:22:07 -04:00
README.md docs(homecore-automation): comprehensive README — YAML triggers + conditions + MiniJinja actions 2026-05-25 23:12:41 -04:00

README.md

homecore-automation

YAML-based automation engine for HOMECORE with trigger evaluation, conditions, and MiniJinja template support.

Crates.io License MSRV: 1.89+ Tests ADR-129

Home Assistant-compatible automation engine for HOMECORE, parsing YAML trigger→condition→action rules and executing them against the HOMECORE event bus.

What this crate does

homecore-automation provides the runtime for HOMECORE automations — YAML files that define "if X happens and Y is true, do Z". It includes:

  • Automation struct — YAML-deserializable automation definition with id, alias, triggers, conditions, actions, and run mode (single, parallel, restart)
  • Trigger evaluation — state-changed, time-based, template, and service-call triggers; async EvaluateTrigger trait
  • Condition evaluation — state conditions, template conditions, numeric comparisons, and logical operators (and/or); EvalContext for entity state injection
  • Action execution — call-service, set-state, and script actions via ExecutionContext
  • MiniJinja templating — HA-compatible Jinja2 templates with globals like states, state_attr, is_state, now
  • AutomationEngine — listens to homecore event bus, drives the trigger→condition→action pipeline asynchronously

Automations are stored in YAML files (e.g., automations.yaml) and loaded at startup. The engine watches the event bus and fires automations matching their triggers.

Features

  • YAML automation syntax — familiar HA format: triggers, conditions, actions, mode
  • State-changed triggers — fires when entity.light.kitchen changes to on
  • Time-based triggersat: "15:30:00" or minutes: 5 (cron-like)
  • Template triggersvalue_template: "{{ states('light.kitchen') == 'on' }}"
  • Service-call triggersservice: light.turn_on for chaining automations
  • Condition evaluationcondition: state with entity_id + state matching
  • Template conditionscondition: template with Jinja2 expressions
  • Numeric comparisonscondition: numeric_state with above, below, between
  • Logical operatorscondition: and / condition: or for complex rules
  • Service call actionsaction: service with service: light.turn_on + data
  • State setting actionsaction: set_state to directly update entity state
  • MiniJinja templating{{ now() }}, {{ states('sensor.temp') }}, {{ is_state('light.kitchen', 'on') }}
  • Automation modes — single (queue), parallel (all fire), restart (drop old runs)

Capabilities

Capability Type Method Notes
Parse YAML automation Loader serde_yaml::from_str::<Automation>(yaml_str) Deserialize automation definition
Evaluate trigger Trigger Trigger::StateChanged {...}.evaluate(context) Check if trigger condition met
Evaluate condition Condition Condition::State {...}.evaluate(context) Check if condition passes
Execute action Action Action::Service {...}.execute(context) Call service or set state
Render template Template TemplateEnvironment::render(expr, context) Jinja2 with HA globals
Run automation Engine AutomationEngine::run_automation(automation, context) Execute full trigger→condition→action pipeline
Subscribe to events Engine AutomationEngine::listen(homecore.event_bus()) Drive automations on state changes

Comparison to Home Assistant

Aspect Home Assistant homecore-automation
Automation format YAML in automations.yaml Identical YAML format
Parser Python YAML + voluptuous serde_yaml + serde validation
Trigger types state_changed, time, template, service, mqtt, ... state_changed, time, template, service (core 4)
Condition types state, numeric_state, template, and/or, ... Identical (core types)
Action types call_service, set_state, script, wait_template, ... call_service, set_state (core 2)
Template engine Python Jinja2 MiniJinja (pure Rust, HA-compatible)
Globals states, state_attr, is_state, now, ... Identical set (MiniJinja filters)
Execution model Python asyncio event loop Tokio async tasks per automation
Automation modes single (queue), parallel, restart Identical behavior

Performance

  • Trigger evaluation — < 100 μs per trigger (state-changed lookups are lock-free)
  • Condition evaluation — < 500 μs per condition (includes state machine reads)
  • Template rendering — < 1 ms per expression (MiniJinja cached compilation)
  • Action execution — < 10 ms per action (service call latency dominates; depends on handler)
  • Automation engine throughput — 1,000+ automations per second (single event bus thread)
  • Memory overhead per automation — ~1 KB (YAML struct + trigger enums)
  • No per-crate benchmarks yet — a follow-up issue tracks baseline measurements

Run cargo bench -p homecore-automation for criterion benchmarks.

Usage

Define an automation in YAML:

alias: "Kitchen light on at sunset"
triggers:
  - trigger: time
    at: "17:30:00"
conditions:
  - condition: state
    entity_id: binary_sensor.is_dark
    state: "on"
actions:
  - action: service
    service: light.turn_on
    target:
      entity_id: light.kitchen
    data:
      brightness: 200
mode: single

Load and run it (Rust):

use homecore_automation::{Automation, AutomationEngine};
use homecore::HomeCore;

#[tokio::main]
async fn main() {
    let homecore = HomeCore::new();
    let yaml = std::fs::read_to_string("automations.yaml").expect("read automation");
    let automation: Automation = serde_yaml::from_str(&yaml).expect("parse automation");

    let engine = AutomationEngine::new(homecore.clone());
    engine.listen(homecore.event_bus()).await;
    
    // Engine now drives automations on state changes
}

Programmatic creation:

use homecore_automation::{Automation, Trigger, Condition, Action, RunMode};

let automation = Automation {
    id: "kitchen_light_sunset".to_string(),
    alias: Some("Kitchen light on at sunset".to_string()),
    triggers: vec![
        Trigger::StateChanged {
            entity_id: "binary_sensor.is_dark".to_string(),
            to: Some("on".to_string()),
            ..Default::default()
        },
    ],
    conditions: vec![],
    actions: vec![
        Action::Service {
            service: "light.turn_on".to_string(),
            data: serde_json::json!({"entity_id": "light.kitchen", "brightness": 200}),
        },
    ],
    mode: RunMode::Single,
    ..Default::default()
};

println!("Automation: {}", automation.alias.unwrap_or_default());

Relation to other HOMECORE crates

homecore-automation (automation engine)
├─ homecore (state machine + event bus; automations subscribe to state changes)
├─ homecore-api (exposes automation metadata via REST, P2)
├─ homecore-assist (intents can trigger automations via service calls, P2)
├─ homecore-server (loads automations.yaml at startup)
└─ minijinja (template rendering)

References