Skip to content

v2.27.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 18:04
· 11 commits to main since this release
7179880

Added

  • redistribute_statistics spreads a catch-up hour back over the hours a meter missed (issue #155). When a reader goes quiet, through a cloud outage or with the recorder down, HA books everything missed into the first hour after it comes back: one spike instead of the usage spread over the silent hours. Totals are right, but hour, day and month views, and the costs derived from them, are not. This moves the energy back without changing any total. The sum at the end of the catch-up hour stays what it is, so every later hour, the live state and the next compile are untouched; only hours inside the window change.
    • Windows:
      • given as windows: [{start, catchup_hour}]; or
      • detected with detect: {min_silent_hours, min_catchup} (both required), with optional start/end. Detection finds every run of at least that many hours without a change, followed by an hour above min_catchup, and treats all of them in one preview.
      • detect_on detects on another meter, and moves only that meter's catch-up. This is for a merged target, whose other components went on recording through the stall.
    • The shape, per hour, from the first source that has it:
      • reference_statistic_ids: another meter's hourly changes, e.g. an inverter's own export counter, scaled so the window total is exactly the catch-up. Several can be given, in priority order.
      • profile_weeks: the meter's own mean by hour of the week over that many weeks either side of the window.
      • Even, as a last resort, with a warning.
    • Or values: the caller's own numbers, one per hour, and 12 per hour (5-minute slots) for the part of the window the meter still has 5-minute rows for. The preview's values_needed says how many of each. They can also be [{start, value}], with every other period 0. They must add up to the window total within 0.001 (normalize scales a miss of up to 2 %), with none negative or non-finite and none above max_per_hour. References given with values are only compared: their ratio and fit, and their hours next to the values' per-hour before/after.
    • 5-minute rows inside the window are rewritten too: each hour split by the first reference's 5-minute changes, evenly, or as given.
    • The preview, per window:
      • the moved amount, and which source shaped how many hours;
      • each reference's total against the moved amount, with a warning beyond 15 % or when the reference looks like a flat estimate rather than a measurement;
      • each reference's fit to the meter (mean absolute error, ratio) on clean hours around the window;
      • before/after by day and month, and the largest hour before and after.
      • It also names any derive_statistics series built from the meter, to run again.
    • Refused: a window with a negative hour or a meter reset, overlapping windows, and an incomplete catch-up hour.
    • Backed up first, like the other statistics writes, and checked against the recorder after writing. Tested on SQLite and on MariaDB.

Findings of a full code audit, each fixed with a test that failed before.

Security

  • create_helper / update_helper could run any WebSocket command as the calling admin. The helper's config was spread into the WebSocket message after its type, so a type key in the config replaced the command. That allowed any service call, bypassing the deliberately narrow service tools (#76), and could mint a long-lived access token (verified: it returned one). That token outlives the arm window. id and type can no longer be passed as command arguments.
    • How to check whether it was used: in your HA profile, under Security, look for long-lived access tokens you didn't create, and delete them. With mirroring on, the mirror repo's .storage/<domain> history shows every helper write.
  • Tool arguments were never checked against their schema. Home Assistant passes the client's JSON straight to a tool, so the declared types, ranges and required fields weren't enforced. Examples: get_logs lines above 1000, a list_mqtt_topics listen beyond its 10 s cap, a string where a list belongs (iterated character by character), or an unknown key crashing a tool. Every call is now validated first, with the schema's defaults filled in, and an invalid one returns error_type: "Invalid". A tool error that escapes is now returned as a tool error and logged, rather than raised raw.
  • package could point a write at another allowlisted file. write_automation / write_script / create_template_entity joined it as packages/<package>, and the path check normalised .. away. With package="../scripts.yaml", an automation was written into scripts.yaml as a script named automation. package must now be a .yaml path inside packages/. File paths with a .. component are refused outright.
  • A symlink could lead around the denylist. The denylist was matched against a path's name, not against where it leads. A link inside packages/ to secrets.yaml was readable. It's now also matched against the resolved path. Containment in the config folder is now a path check, not a string prefix, so /config no longer also matches /config_old.
  • The mirror token was sent to the browser. The options form pre-filled the token field, and a form's defaults are sent to the frontend, password field or not. The field is now left empty, and leaving it empty keeps the stored token. The mirror repository must be in owner/repo form.
  • A future time in the arm file lifted the 4-hour cap. The cap counts from the time written in the file, so a mistyped or forward-dated one kept dev_tools armed for as long as it stayed in use. A time more than a minute ahead now counts as not armed.
  • get_rest_command returned literal credentials. Passwords, API keys, Bearer tokens and URL passwords came back as written, even though get_config_file withholds the same block (#105). They're now masked, and !secret references are shown as before. The same gap in get_automation / get_script is tracked in #151: those feed write round-trips, so masking needs a decision first.
  • Hardening:
    • Mirror paths are percent-encoded in GitHub API URLs, and a ./.. segment is refused.
    • get_addon_logs only takes an add-on slug.
    • The credential checks also recognise passphrase, psk, encryption_key, network_key and header spellings like X-Api-Key.
    • An inactive user is never resolved as the caller.

Fixed

  • get_logs ignored since / until, which its description offers. They're now applied, as ISO 8601 times (a time without an offset is in HA's time zone). Log times are timezone-aware, and lines no longer has a default that would override offset/limit paging.
  • Tools say which integration provides them (Tool.integration), which HA 2026.10 logs a warning about otherwise. Returning llm.ToolResult (HA 2026.10's other deprecation, breaking in 2027.11) is tracked in #152.