Skip to content

Add the response contract: signed alert webhook and Home Assistant events - #82

Merged
mattmacrocket merged 1 commit into
mainfrom
feature/response-webhook
Oct 19, 2026
Merged

mattmacrocket merged 1 commit into
mainfrom
feature/response-webhook

Conversation

@mattmacrocket

Copy link
Copy Markdown
Collaborator

Item 7 from the 2026-10-17 defense review (recorded internally, not an issue).

Why

The sensor detects and reports; it never blocks a device or touches a router, and that stays true. But an operator who wants their UniFi, OPNsense, or Home Assistant to isolate a device that reached write activity on a decoy had nothing machine-readable to act on: Slack text was the only outbound channel. This adds one documented event with exactly the fields an automation needs, signed so the receiver can trust it.

The contract: squirrelops.alert/1

{"schema":"squirrelops.alert/1","event":"alert.updated","sent_at":"...","sensor":{"id":"...","version":"2.1.3"},
 "alert":{"id":42,"type":"decoy.trip","severity":"critical","title":"...","summary":"...","created_at":"..."},
 "source":{"ip":"192.168.1.77","mac":"AA:BB:CC:DD:EE:77","device_id":12},
 "decoy":{"id":7,"name":"Studio Build Mac","port":445,"protocol":"tcp"},
 "campaign":{"intent":"ransomware","stage":4,"stage_label":"write activity"}}

decoy and campaign are null when not applicable. mac and device_id follow Include Device Identifiers, which defaults to on for these two methods because the MAC is what a controller blocks on. Free text is bounded (title 256, summary 512, decoy name 128). Nothing internal rides along (no event sequence, read markers, or detail blobs); a test asserts the exact key set. Full spec, request format, signature recipe, HA automation example, and the "sensor reports, you act" boundary: docs/ALERT_WEBHOOK.md.

Delivery methods

  • alert_methods.webhook: POST of compact sorted JSON. Headers X-SquirrelOps-Schema, X-SquirrelOps-Event, User-Agent: squirrelops-home-sensor/<version>, and with a signing secret X-SquirrelOps-Signature: t=<unix>,v1=<HMAC-SHA256(secret, "<t>." + body)> (timestamp inside the signed material, so replays are detectable). Non-2xx raises the existing credential-safe AlertDeliveryError and the dispatcher's backoff applies. Only http(s) URLs are accepted.
  • alert_methods.home_assistant: fires squirrelops_alert with the same payload through HomeAssistantClient.fire_event, which keeps the LAN-only resolution and host pinning of the existing connectivity check. Requires the HA integration enabled with URL and token.

Supporting changes

  • AlertDispatcher.dispatch(payload, event_type=...): handlers receive a copy carrying alert.new or alert.updated. Existing handlers ignore it.
  • Decoy alerts now publish device_id, decoy_id, decoy_name, dest_port, protocol at top level on both alert.new and alert.updated (Swift AlertSummary ignores unknown keys).
  • signing_secret added to CONFIG_SECRET_PATHS, ALERT_METHOD_SECRET_PATHS, and the vault's _ALERT_METHOD_SECRET_FIELDS. webhook_url was already redacted by the wildcard rule and vaulted.
  • ConfigurableAlertDispatcher(config, sensor_id=, sensor_version=) wired in __main__.
  • App Settings: Automation Webhook and Home Assistant Events blocks mirroring the Slack one.

Tests (red first)

  • tests/unit/test_response_event.py (17): exact event shape, opt-in identifiers, null decoy/campaign, bounds, signature algorithm plus the docs' receiver recipe, webhook handler headers/body/signature, unsigned mode, credential-safe HTTP and transport errors, build_methods_from_config for both methods (disabled, missing URL, non-http, HA not configured), and new-vs-updated event typing through the dispatcher.
  • TestFireEvent (3) in the HA client suite via pytest-httpx.
  • signing_secret redacted, restored, and stripped from persisted YAML.
  • TestOutboundIdentifiers (2): alert.new and alert.updated carry device and decoy identifiers.

Full sensor suite in a fresh worktree venv: 2585 passed, 6 skipped (opt-in live modules). ruff clean. swift test: 424 + 121 + 39 passed.

Deception integrity

Control surface only. Nothing under decoys/ changes and no attacker-visible behavior changes.

Data leaving the network

Two new rows in the USER_GUIDE table: the event fields above go to the endpoint you configured, or to your Home Assistant over the LAN. Off by default.

…ents

The sensor detects and reports; it never blocks. Until now an operator who
wanted a router to act on a decoy trip had only Slack text to parse. This
adds one documented outbound event, squirrelops.alert/1, carrying what an
automation needs: source IP and (by default) MAC and device id, the decoy
and port, alert type, severity, title and summary, campaign intent and
stage, and the sensor id and version.

Two delivery methods carry it:

- alert_methods.webhook: HTTP POST of compact sorted JSON to an operator
  endpoint, with X-SquirrelOps-Schema and X-SquirrelOps-Event headers and,
  when a signing secret is set, X-SquirrelOps-Signature as
  t=<unix>,v1=<HMAC-SHA256(secret, "<t>." + body)>, so a receiver can
  refuse unsigned or replayed events before it blocks anything.
- alert_methods.home_assistant: the same event fired as a squirrelops_alert
  HA event through the existing LAN-pinned client (new fire_event).

Supporting changes:
- dispatch() now carries event_type (alert.new or alert.updated) to
  handlers so the contract can say which it is.
- decoy alerts publish device_id, decoy_id, decoy_name, dest_port, and
  protocol at top level; they were buried in detail.
- signing_secret joins webhook_url as a vaulted, redacted alert-method
  field. webhook_url was already covered by the wildcard rule.
- ConfigurableAlertDispatcher learns the sensor id and version at startup.
- App Settings: Automation Webhook (URL, signing secret, severity, device
  identifiers on by default) and Home Assistant Events (severity; requires
  the HA integration).
- docs/ALERT_WEBHOOK.md documents the contract, request, signature recipe,
  an HA automation example, and the sensor-reports/you-act boundary.
  USER_GUIDE and SECURITY_MODEL updated.

Red first: 17 contract tests, 3 HA fire_event tests, secret redaction and
vault tests, and two decoy-handler identifier tests all failed before the
implementation. Full sensor suite 2585 passed; swift test 424+121+39.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants