feat(engine): operator-configured, redacted breach notifier (off by default) (JEF-144)#62
Merged
thejefflarson merged 1 commit intoJun 25, 2026
Conversation
…efault) (JEF-144)
Surfacing was pull-only: a solo operator never learned protector decided a
breach unless watching the dashboard. Add an opt-in outbound notifier that
POSTs a redacted breach-decision summary to an operator-configured sink
(PROTECTOR_ENGINE_NOTIFY_URL) — the one sanctioned outbound path (ADR-0018).
- Off by default: unset URL ⇒ zero outbound calls, byte-identical to today.
- Redacted by default: decision kind, entry workload, ATT&CK outcome (distinct
tactic/technique IDs + an objective count), sanitized verdict text, and the
shadow-vs-armed posture. No secret names, no peer graph, no CVE list. Richer
per-objective ATT&CK detail is behind PROTECTOR_ENGINE_NOTIFY_VERBOSE.
- Deduped on the journal's decision identity (JEF-141): fired at the same point
the engine appends a new breach line, so one decision is one notification.
- Shadow ("would isolate") vs armed ("isolated") explicit in the message.
- Bounded, fail-safe client (reuses model.rs timeout_only_client); a hung sink
can't stall the engine loop and a failed POST is logged once and dropped.
Records ADR-0018 (the notification trust boundary) and indexes it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP
4e652aa to
dad0220
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Surfacing was pull-only — a solo operator never learned protector decided a breach unless watching the dashboard. This adds an opt-in outbound notifier that POSTs a redacted breach-decision summary to an operator-configured sink (
PROTECTOR_ENGINE_NOTIFY_URL), the inverse of the falcosidekick ingest, documented to target an in-cluster sink (Alertmanager / ntfy / gotify).This is the one sanctioned outbound path, recorded as ADR-0018 (the notification trust boundary).
Behaviour (matches the acceptance criteria)
PROTECTOR_ENGINE_NOTIFY_VERBOSE) and still excludes secrets/peers/CVEs.adjudicate::sanitize.model.rs(never an unboundedreqwest::Client::new()); a failed POST is logged once and dropped. Never affects a verdict, the journal, or actuation. Engine stays shadow; no action-class default changes.Where
engine/src/engine/notify.rs(new) —BreachNotifier,BreachNotice, pureredacted_payload.engine/src/engine/mod.rs— fires the notifier at the journal write site;with_notifierbuilder;from_env()wired inrun_watchonly.engine/src/engine/model.rs—timeout_only_clientmadepub(crate)to reuse the bounded-client pattern.docs/adr/0018-...md+docs/adr/README.mdindex.Tests
Unit (notify): redaction (no secret names / no peer graph / no CVE list in the default payload), verbose still excludes secrets, shadow-vs-armed wording, verdict sanitized before egress, disabled = zero outbound, enabled uses a bounded client (fails fast vs an unroutable address), truthy parsing.
Engine integration (local axum sink): one notification per decision (dedupe across passes) with a redacted body, and zero outbound calls when no URL is set.
Gates:
cargo fmt --check,cargo check -p protector,cargo clippy -p protector --all-targets -- -D warnings,cargo test(180 engine tests pass, behavior crate clean).Closes JEF-144
🤖 Generated with Claude Code