From d0b07bdf528d512d87b4649f61fee950e6e040a7 Mon Sep 17 00:00:00 2001 From: anti Date: Thu, 30 Apr 2026 12:43:41 -0400 Subject: [PATCH] fix(smtp_relay): inject From: header if absent so attacker address shows in client Relay-test scripts send minimal DATA with no headers. Without a From: header the mail client falls back to displaying the envelope sender (upstream_sender). Inject From: before forwarding when the message has no existing From: header. --- decnet/orchestrator/drivers/smtp_relay.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/decnet/orchestrator/drivers/smtp_relay.py b/decnet/orchestrator/drivers/smtp_relay.py index 6e8bf12f..4c1be0df 100644 --- a/decnet/orchestrator/drivers/smtp_relay.py +++ b/decnet/orchestrator/drivers/smtp_relay.py @@ -8,6 +8,7 @@ Called by the realism worker's smtp probe listener, not the main tick loop. """ from __future__ import annotations +import email import smtplib from pathlib import Path from typing import Any @@ -15,6 +16,19 @@ from typing import Any _ARTIFACTS_ROOT_DEFAULT = "/var/lib/decnet/artifacts" +def _ensure_from_header(body: bytes, mail_from: str) -> bytes: + """Return body with a From: header added if one is absent.""" + try: + msg = email.message_from_bytes(body) + except Exception: + return body + if msg["From"]: + return body + # Prepend the header before the existing content. + header_line = f"From: {mail_from}\r\n".encode() + return header_line + body + + def forward_probe( *, svc_cfg: dict[str, Any], @@ -47,6 +61,11 @@ def forward_probe( upstream_pass = (svc_cfg.get("upstream_pass") or "").strip() envelope_from = (svc_cfg.get("upstream_sender") or "").strip() or mail_from + # Ensure the message has a From: header so mail clients show the attacker's + # address rather than falling back to the envelope sender (upstream_sender). + # Minimal relay-test scripts often omit headers entirely. + body = _ensure_from_header(body, mail_from) + try: with smtplib.SMTP(upstream_host, upstream_port, timeout=15) as conn: conn.ehlo()