diff --git a/decnet/correlation/event_kinds.py b/decnet/correlation/event_kinds.py index 0af95176..acc7bed1 100644 --- a/decnet/correlation/event_kinds.py +++ b/decnet/correlation/event_kinds.py @@ -41,6 +41,10 @@ INTERACTION_EVENT_TYPES: frozenset[str] = frozenset({ "rcpt_to", "rcpt_denied", "message_accepted", + # probe_forwarded fires when we actually relay the test email upstream + # so the attacker can verify receipt. forwarded=1 means the upstream + # accepted it; forwarded=0 means it failed (but the attacker still got 250). + "probe_forwarded", # File / payload activity "file_captured", "upload", diff --git a/decnet/web/ingester.py b/decnet/web/ingester.py index da686206..7a89e0f5 100644 --- a/decnet/web/ingester.py +++ b/decnet/web/ingester.py @@ -613,6 +613,21 @@ async def _extract_bounty( "content_type": _fields.get("content_type"), }, }) + elif _evt == "probe_forwarded": + # Record whether the upstream relay accepted the probe. forwarded=1 + # means the attacker's test email actually landed in their inbox; + # forwarded=0 means the upstream refused (attacker still got 250). + await repo.add_bounty({ + "decky": log_data.get("decky"), + "service": log_data.get("service"), + "attacker_ip": log_data.get("attacker_ip"), + "bounty_type": "probe_relay", + "payload": { + "msg_id": _fields.get("msg_id"), + "forwarded": _fields.get("forwarded") == "1", + "delivery_count": _fields.get("delivery_count"), + }, + }) # ─── IP-leak detection (XFF / Forwarded / X-Real-IP / CDN variants) ──────────