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: <attacker MAIL FROM> before forwarding
when the message has no existing From: header.
This commit is contained in:
2026-04-30 12:43:41 -04:00
parent 4d12fb6a03
commit d0b07bdf52

View File

@@ -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()