MAS-WP-0005-T12: gateway reads trusted A-R verdict; ticket service replies only on DMARC pass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 252470@bnt-lap001
Assistant-Session: ad628c8f-4be2-4979-94c9-76c7ed6f8241
This commit is contained in:
tegwick 2026-10-18 22:40:30 +02:00
parent ab3346d187
commit cca58d41fb
6 changed files with 128 additions and 13 deletions

View file

@ -65,7 +65,7 @@ CREATE TABLE IF NOT EXISTS outbox (
class TicketService:
def __init__(self, db_path: str = ":memory:", id_factory: Callable[[], str] = default_ticket_id,
smtp: Optional[tuple[str, int]] = None, closed_notice: bool = True,
max_sent: Optional[int] = None):
max_sent: Optional[int] = None, require_dmarc_pass: bool = False):
self.db = sqlite3.connect(db_path, check_same_thread=False)
self.db.row_factory = sqlite3.Row
self.db.executescript(SCHEMA)
@ -74,6 +74,8 @@ class TicketService:
self.smtp = smtp
self.closed_notice = closed_notice
self.max_sent = max_sent # lifetime cap on mail actually sent via smtp
# spec 14 item 10: auto-reply only to authenticated senders
self.require_dmarc_pass = require_dmarc_pass
# -- event handling -------------------------------------------------------------
@ -149,6 +151,8 @@ class TicketService:
bulk = (msg.get("precedence") or "").lower() in ("bulk", "list", "junk")
if not to or auto or bulk:
return False # never auto-reply to automated mail
if self.require_dmarc_pass and (msg.get("authentication") or {}).get("dmarc") != "pass":
return False # unauthenticated sender: keep the ticket, send nothing
if not subject.lower().startswith("re:"):
subject = "Re: " + subject
self.db.execute("INSERT INTO outbox (ticket_id, mail_from, mail_to, subject, body, in_reply_to)"
@ -241,10 +245,12 @@ if __name__ == "__main__":
ap.add_argument("--db", default="tickets.sqlite3")
ap.add_argument("--listen", default="127.0.0.1:8025")
ap.add_argument("--smtp", metavar="HOST:PORT", help="send replies via this SMTP relay")
ap.add_argument("--require-dmarc-pass", action="store_true",
help="auto-reply only when the event says the sender passed DMARC")
ap.add_argument("--max-sent", type=int, metavar="N",
help="stop sending once N replies have been sent in total (kept in the db)")
a = ap.parse_args()
smtp = (a.smtp.rsplit(":", 1)[0], int(a.smtp.rsplit(":", 1)[1])) if a.smtp else None
h, p = a.listen.rsplit(":", 1)
print(f"ticket service on http://{a.listen}/mas-events")
make_server(TicketService(a.db, smtp=smtp, max_sent=a.max_sent), h, int(p)).serve_forever()
make_server(TicketService(a.db, smtp=smtp, max_sent=a.max_sent, require_dmarc_pass=a.require_dmarc_pass), h, int(p)).serve_forever()