feat(membership): forbid cleartext rooms on public deployments (H4 min defense)
Audit H4 (Alto). The embedded NATS has a single account with no per-subject permissions, so any registered peer can subscribe to any subject — a cleartext (ModeNATS) room's payload is readable by anyone who knows the subject. A complete per-subject ACL derived from membership does not fit here: NATS evaluates a connection's permissions once at connect time and never re-evaluates them, but unibus clients connect-then-create/join-then-publish on one connection (TestSecureBusEndToEnd). Static permissions would forbid the owner from publishing to a room it just created; the dynamic reconnection model belongs to the 0003 decentralization redesign. See dev/0004d-dataplane-acl.md. Minimum defense implemented: Server.RequireEncryptedRooms (set by membershipd on any non-loopback bind) refuses to create cleartext rooms, so every room on a public deployment is end-to-end encrypted. Message CONTENT stays confidential even with no subject isolation; residual traffic-metadata exposure is documented and tracked for 0003. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,16 @@ type Server struct {
|
||||
authMode AuthMode
|
||||
nonces *nonceCache
|
||||
limiter *ipRateLimiter
|
||||
|
||||
// RequireEncryptedRooms, when true, refuses to create cleartext (ModeNATS)
|
||||
// rooms. It is the minimum-defensive control for the data plane (audit H4):
|
||||
// the embedded NATS has no per-subject ACL, so a cleartext room is readable by
|
||||
// any registered peer that knows (or guesses) its subject. Forcing every room
|
||||
// to be end-to-end encrypted keeps message CONTENT confidential even when the
|
||||
// transport offers no subject isolation. The command sets this on a public
|
||||
// (non-loopback) bind. See dev/0004d-dataplane-acl.md for the full rationale
|
||||
// and the residual metadata exposure this does NOT close.
|
||||
RequireEncryptedRooms bool
|
||||
}
|
||||
|
||||
// NewServer wires the membership store and blob store into an http.Handler. The
|
||||
@@ -341,6 +351,14 @@ func (s *Server) handleCreateRoom(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, http.StatusBadRequest, "subject and owner.endpoint required")
|
||||
return
|
||||
}
|
||||
// Data-plane minimum defense (audit H4): on a public deployment cleartext
|
||||
// rooms are disabled, so no message ever rides the un-ACL'd NATS subject in
|
||||
// the clear for another registered peer to sniff.
|
||||
if s.RequireEncryptedRooms && !req.Policy.Encrypt {
|
||||
writeErr(w, http.StatusForbidden,
|
||||
"cleartext rooms are disabled on this deployment; create an encrypted (Matrix-policy) room")
|
||||
return
|
||||
}
|
||||
roomID := newULID()
|
||||
info := RoomInfo{
|
||||
RoomID: roomID,
|
||||
|
||||
Reference in New Issue
Block a user