Your address is on the allowlist — does that make the sender you?

You wire an inbox into Hermes with an allowlist that contains exactly one address — yours — and consider the door shut. Then one day the bot answers a message from a domain you have never heard of, signed with your name. The allowlist was not misconfigured. The problem is that the string you put in it and the “sender” in that message were never the same kind of thing.
Two security patches merged on 2026-09-27 fix that same mistake in two places: the email gateway and the bundled SimpleX gateway both authorized senders using a field the sender writes about themselves. Both are P1, both are on main today.
A display name is free text: how SimpleX’s allowlist was bypassed
SimpleX is built for privacy: there are no persistent user IDs. Every contact gets an opaque internal ID (contactId) generated at connection time — that is the identity. The display name is whatever the other side typed into their own client.
Before the fix, the gateway’s authorization rule was “allow if the contactId or the sender’s display name matches the allowlist.” Display names collide and can be changed at any time, so any contact could set their name to a value already in your allowlist and start driving your bot. The hole was reported on 2026-06-12 (#44729) and closed by PR #125034 on September 27.
What happens now:
SIMPLEX_ALLOWED_USERSmatches only numericcontactIds; display names are ignored.- On connect, the adapter logs one warning listing allowlist entries that do not look like contact IDs, because those entries no longer do anything. You will most likely need to replace them.
- The docs,
plugin.yaml, and thehermes gateway setupprompt now all ask for contact IDs.
To find your own: start the simplex-chat daemon, open the conversation with your agent contact, and read the numeric ID in the session logs — or run /contacts in the daemon.
Email is subtler: the second address inside From
Email is worse because RFC 5322 lets From be “display name plus address,” and the display name can contain almost anything. The old parser took the first angle-bracketed address it found:
From: "Victim <[email protected]>" <[email protected]>
The real sender here is [email protected], but the old code resolved [email protected] — the address on your allowlist.
The same patch closes two more shapes:
| Attack shape | Before | After |
|---|---|---|
A second address in From (mailing lists, groups, an address inside the display part) |
A “legitimate” identity was resolved and authorized | The From resolves empty and the message is dropped before auth or dispatch |
A huge or deeply nested From (over 2048 characters, or more than 64 comment parentheses) |
Pure-Python parsing took seconds on hostile input, holding the GIL | Size and nesting caps drop it up front — a 200 KB value in about 2 ms |
Text the sender controls inside the receiving server’s Authentication-Results header (quoted local parts, comments) |
A forged dmarc=pass / spf=pass verdict could be read |
Clauses are split with quoted strings and nested comments respected; an unbalanced quote or paren fails closed, and each verdict is read only from its own method=result |
One more judgement call changed: dmarc=pass is no longer accepted on its own. It only counts when the verdict’s header.from aligns with the From domain that was parsed (equal, or one is a dot-suffix of the other) — otherwise an attacker only needs a domain they control to pass DMARC and then vouch for any From at all. This is the GHSA-rxqh-5572-8m77 fix.
What your install does now
On the email side, “the sender must be authenticated” is on by default:
# ~/.hermes/config.yaml
platforms:
email:
require_authenticated_sender: true # default true
authserv_id: "your-mx.example.com" # optional: pin Authentication-Results to your own server
The environment variable is EMAIL_AUTHSERV_ID. If your mail server does not stamp that header at all, the documented escape hatch is require_authenticated_sender: false (or EMAIL_TRUST_FROM_HEADER=true) — that restores the old behaviour, and the docs say plainly that it means accepting the risk. Full setup for the email platform (IMAP/SMTP variables, pairing codes, EMAIL_ALLOWED_USERS) is in the email docs.
For day-to-day gateway operations, see our hermes gateway command reference. If you want the mirror-image idea — outbound authenticity instead of inbound — the webhook post covers Hermes signing its own events with HMAC so receivers can verify origin.
One thing that did not get stricter: outbound. hermes send --to simplex:alice "hello" still works by display name, because there you are choosing the target yourself. What tightened is only who is allowed to command your agent.
Release status
Both patches merged on 2026-09-27, after the v0.21.5 release tag (v2026.9.24), so they live on main only right now: hermes update picks them up, or wait for the next tag.
The takeaway
A string in an allowlist was never an identity — it is a line someone else wrote for you to read. These fixes re-anchor “who is talking to me” to something the sender cannot decide: the contactId generated at connection time for SimpleX, and a verdict stamped by your own receiving server for email, plus a check that the verdict actually covers the From domain. If you run a bot yourself, two habits are worth adopting: replace allowlist entries with real contact IDs, and make your mail server stamp authentication headers instead of turning that trust off.