From d111efd23e587694c8e2de24ec123346491ef37f Mon Sep 17 00:00:00 2001 From: anti Date: Fri, 1 May 2026 06:08:25 -0400 Subject: [PATCH] docs(bus): document email.received + ttp.tagged / ttp.rule.fired.* / ttp.rule.suppressed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sibling of decnet@e395306 (E.1.2 of the TTP-tagging rollout). The producers don't exist yet — these rows are reserved entries so subscribers can wire patterns now and consume the moment publishers land. PII discipline on email.received is captured inline so it can't get lost in folklore. --- Service-Bus.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Service-Bus.md b/Service-Bus.md index 3f1e054..374167f 100644 --- a/Service-Bus.md +++ b/Service-Bus.md @@ -172,6 +172,11 @@ Current topic families: | `canary.{token_id}.triggered` | `decnet canary` worker | `{token_id, decky_id, src_ip, user_agent?, request_path?, dns_qname?, occurred_at, raw_headers?}` — attacker hit the HTTP slug or DNS subdomain; correlator + webhook fanout consume to attribute and forward | | `canary.{token_id}.revoked` | API (`DELETE /tokens/{id}`) | `{token_id, decky_id, revoked_at}` — operator removed a token; subscribers may evict cached lookups by token id | | `system.canary.health` | `decnet canary` worker | standard worker heartbeat | +| `smtp.probe.pending` | Ingester | `{decky, attacker_ip, stored_as, mail_from, rcpt_to}` — fired when an smtp_relay decky stores a new inbound message. The realism worker subscribes and forwards the email to the upstream relay if this source IP has not yet reached `probe_limit`. `stored_as` is the quarantine filename (relative to the per-decky smtp dir). `mail_from` / `rcpt_to` are the envelope sender/recipients captured at SMTP time. The worker writes a `probe_relay` bounty row on success or failure so the limit check is DB-backed and survives container restarts. | +| `email.received` | _reserved (smtp / smtp-relay services)_ | `{message_uuid, decky_id, session_id, attacker_ip, mail_from, rcpt_count, body_sha256, header_names: [...], attachment_sha256s: [...]}` — fired on full-message receipt, consumed by the TTP `email_lifter`. PII discipline (TTP_TAGGING.md "Hard parts §6"): hashes, counts, header *names*, and rcpt-domain sets only — never rcpt addresses or body bytes. Constant declared with no publisher yet; the SMTP services start emitting it when E.3 (the implementation phase) lands. | +| `ttp.tagged` | _reserved (TTP worker)_ | `{tag_uuids: [...], techniques_added: [...], attacker_uuid?, identity_uuid, session_id?}` — published only when `INSERT OR IGNORE` wrote at least one new row. Idempotent re-evaluations that produce zero new tags publish zero events (loop-prevention invariant — a webhook subscriber re-triggering enrichment on `ttp.tagged` could otherwise loop forever). | +| `ttp.rule.fired.{technique_id}` | _reserved (TTP worker)_ | `{rule_id, technique_id, sub_technique_id?, tag_uuid, confidence}` — per-technique fan-out for SIEM correlation rules that subscribe to one technique. Topic key is the parent technique; `sub_technique_id` lives in the payload. Use `ttp.rule.fired.>` for fleet-wide subscribers. | +| `ttp.rule.suppressed` | _reserved (TTP worker)_ | `{rule_id, technique_id, reason}` where `reason ∈ {"below_floor", "rate_limited", "rule_disabled"}` — observability for tags that *would have* been written but were dropped. Drives the dashboard's per-rule suppression counters. | | `system.log` | _reserved_ | — | | `system.bus.health` | Bus worker heartbeat | `{ts, uptime_s}` |