Repository navigation
v2.27.0
Added
redistribute_statisticsspreads 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 optionalstart/end. Detection finds every run of at least that many hours without a change, followed by an hour abovemin_catchup, and treats all of them in one preview. detect_ondetects 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.
- given as
- 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'svalues_neededsays 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 (normalizescales a miss of up to 2 %), with none negative or non-finite and none abovemax_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_statisticsseries 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.
- Windows:
Findings of a full code audit, each fixed with a test that failed before.
Security
create_helper/update_helpercould run any WebSocket command as the calling admin. The helper'sconfigwas spread into the WebSocket message after itstype, so atypekey 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.idandtypecan 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.
- 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
- 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_logslinesabove 1000, alist_mqtt_topicslisten 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 returnserror_type: "Invalid". A tool error that escapes is now returned as a tool error and logged, rather than raised raw. packagecould point a write at another allowlisted file.write_automation/write_script/create_template_entityjoined it aspackages/<package>, and the path check normalised..away. Withpackage="../scripts.yaml", an automation was written intoscripts.yamlas a script namedautomation.packagemust now be a.yamlpath insidepackages/. 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/tosecrets.yamlwas 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/configno 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/repoform. - 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_commandreturned literal credentials. Passwords, API keys,Bearertokens and URL passwords came back as written, even thoughget_config_filewithholds the same block (#105). They're now masked, and!secretreferences are shown as before. The same gap inget_automation/get_scriptis 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_logsonly takes an add-on slug.- The credential checks also recognise
passphrase,psk,encryption_key,network_keyand header spellings likeX-Api-Key. - An inactive user is never resolved as the caller.
- Mirror paths are percent-encoded in GitHub API URLs, and a
Fixed
get_logsignoredsince/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, andlinesno longer has a default that would overrideoffset/limitpaging.- Tools say which integration provides them (
Tool.integration), which HA 2026.10 logs a warning about otherwise. Returningllm.ToolResult(HA 2026.10's other deprecation, breaking in 2027.11) is tracked in #152.