Repository navigation
Conversation
|
Thanks, this is a big and careful piece of work (rule state machine, masked URLs, throttled logging, good tests). Before it can merge: (1) channel URLs should get the same outbound-target validation as other targets in (Review notes prepared with Claude Code on behalf of the maintainer.) |
|
Re-reviewed against main; the three requests from 2026-10-09 are still open. (1) Channel URL validation: (Review notes prepared with Claude Code on behalf of the maintainer.) |
|
Reworked on 2.x (single commit
— Wei |
|
Re-reviewed at e2c9b59 and the earlier requests are fixed in code: channel URLs go through the (Review notes prepared with Claude Code on behalf of the maintainer.) |
|
Fixed in
Server 863 (same 22 pre-existing macOS failures), frontend 539, typecheck clean. — Wei |
|
Force-pushed — Wei |
Nothing told anyone about a problem unless a dashboard was open. The server can now notify ntfy, Discord, Slack or a JSON webhook - as a notification layer over what 2.0 already detects, not a second rule set: - conditions are HealthEvaluator's findings (snapshot.health: same ids, severities, thresholds) and the Overview strip's exceptions, whose derivation moves to src/shared/fleetExceptions.js so FleetAlertStrip and the server run the same code (offline, thermal/hardware throttle - never the software power cap - disk >= 90%, LLM, tailnet) - the engine adds delivery: pending -> firing -> resolved with a per-rule wait and hysteresis (clear for the same wait, at least 30 s), grouping per evaluation, optional reminders, firing set persisted across restarts - every send, delivered or failed, is an alert.notify.sent / .failed Activity event (no URL, no response body); new Activity category - outbound: channel URLs pass validate.js's target policy at save and are re-resolved with assertAllowedTarget on every send, the connection pinned to the checked addresses; LAN/loopback stay allowed - redirects are never followed (3xx = failure); response bodies are never read - errors are a status code or a fixed reason - alertsEnabled (default off) gates everything outbound: tick does not evaluate, PUT /api/alerts/config and POST /api/alerts/test answer 409 - remote-derived text (unit names, SSH errors, Xid lines, model ids) is capped to 120 chars (names 60) - channel URLs live in the encrypted secrets store (new alertChannels section; saveSecrets keeps it); alerts.json holds no URLs, and a plaintext URL from the earlier build is migrated on load - Settings -> Alert notifications -> Configure... in the 2.0 dialog style (SettingRow/ToggleRow moved to SettingRows.tsx); the exceptions strip lists the server's alerts with real durations while it is on Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tries - Slack payload: `&`, `<` and `>` are escaped in the title and every line, so a unit name or remote error text cannot produce `<!channel>`, `<!here>`, user mentions or `<url|text>` links. - maskUrl() shows scheme + host and a fixed `/…`, nothing of the path (no last four characters, no length); saving the masked value still keeps the stored URL, and credentials in the URL are never shown. - README: a failed send is not retried; the alert counts as notified and the channel hears about it again on escalation, clearing or a reminder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re-verified at 1a77eb7 against main 2c005e3 (before today's pushes). It merges cleanly and its 51 alert tests pass. The earlier requests are fixed: Slack text is escaped ( (Review notes prepared with Claude Code on behalf of the maintainer.) |
|
Ported to main with your two commits and authorship (rebased by hand: the settings, config, README and CHANGELOG hunks kept both sides). The maintainer has accepted the outbound path and the new stored secrets. One defect fixed in a follow-up: (Review notes prepared with Claude Code on behalf of the maintainer.) |
Summary
Reworked on 2.x (single commit
e2c9b59). Opt-in (alertsEnabled, default off): a notification layer, not a second rule set.How it coexists with HealthEvaluator and the Activity log (also at the top of
server/alerts/engine.js): detection stays where it is — the rules areHealthEvaluatorfindings (same ids, severities, thresholds) plus the Overview strip's exceptions, whose derivation moved tosrc/shared/fleetExceptions.jssoFleetAlertStripand the server run the same code (throttle = thermal/hardware slowdown only, never the power cap). This layer adds delivery: pending → firing → resolved with aforwait and hysteresis, grouping, reminders, firing set kept across restarts. Every send, delivered or failed, is analert.notify.sent/.failedActivity event (no URL, no body).Outbound (
server/alerts/outbound.js, one place, mirrorsvalidate.js):isAllowedTargetHost+ DNS check on save,assertAllowedTargeton every send, connection pinned to the checked addresses; LAN/loopback allowed as for units.redirect: "manual"— any 3xx fails. Response bodies are never read. While off: no evaluation, andPUT /api/alerts/config/POST /api/alerts/testanswer 409. Remote strings capped (120/60). Channel URLs live in the encrypted secrets store (alertChannels), masked in the API; old plaintext URLs migrate on load.UI: Settings → Alert notifications · Configure… (2.0
SettingRow/ToggleRow, modal sheet); the strip shows server alerts with realstartsAtwhile on.Test plan
🤖 Generated with Claude Code