# 0004d — Data-plane access control on NATS (audit H4) ## The finding The NATS authenticator (`pkg/busauth`) decides one thing per connection: *is this identity registered on the bus?* It does **not** scope what a connected client may subscribe to or publish. There is a single NATS account with no `Permissions`, so any registered peer can subscribe to, or publish on, **any** subject. Concretely: - A cleartext room (`ModeNATS`) carries its payload in the clear on its subject. A registered peer that knows or guesses the subject subscribes and reads the content directly (the auditor's `TestAudit_NoSubjectACL`: eve, never invited, receives `"internal: salary numbers"`). - An encrypted room (`ModeMatrix`) keeps its **content** confidential (the payload is AEAD ciphertext), but the **metadata of traffic** — that a subject is active, message sizes and timing, who is publishing — is still observable by any registered peer that subscribes to the subject. ## Why the "complete" fix does not fit here The preferred fix is per-subject permissions derived from room membership: when a client connects, the authenticator looks up the rooms it belongs to and grants `Sub`/`Pub` only on those subjects. NATS supports this — `CustomClientAuthentication` can register a `*server.User` carrying `Permissions`. The blocker is that **NATS evaluates permissions once, at connect time, and never re-evaluates them on a live connection.** unibus clients routinely *connect → create or get invited to a room → publish/subscribe* within the **same** connection (`TestSecureBusEndToEnd` does exactly this: A connects, then creates `room.secure`, then publishes to it). Permissions frozen at connect time would not include a room created or joined afterwards, so the legitimate owner could not publish to the room it just made. Making per-subject ACLs work would therefore require the client to **reconnect on every membership change**, an invasive change to the client library and to every peer (worker, chat, mobile) — and the prompt for this issue scopes the client changes to the minimum. That dynamic-membership reconnection model is precisely the redesign that issue **0003** (decentralization) already has to do: it moves the control-plane state to a replicated JetStream KV and reworks how nodes and clients (re)establish sessions. Per the issue's own guidance ("if a complete strategy does not fit, implement the minimum defense and document the rest"), the full subject ACL is deferred to 0003, where the session/permission model is being rebuilt anyway. ## The strategy implemented here: forbid cleartext rooms in public `Server.RequireEncryptedRooms` (set by `membershipd` on any non-loopback bind) refuses to create a cleartext (`ModeNATS`) room. Every room on a public deployment is therefore end-to-end encrypted, so **message content stays confidential even though the transport offers no subject isolation**: a peer that sniffs another room's subject receives only AEAD ciphertext it has no key for. This composes with the 0004c control-plane authorization: a non-member cannot even learn a room's subject through the control plane (`GET /rooms/{id}` → 403), so to sniff it an attacker must already know or guess the subject out of band. ## What this does NOT close (residual exposure, by design) - **Traffic metadata.** A registered peer that already knows a subject can still subscribe and observe that the subject is active, the ciphertext sizes, and the timing/cadence of messages. It cannot read content. - **Cross-room publish.** A registered peer can still *publish* arbitrary bytes on any subject. In an encrypted room those bytes fail AEAD open and the signature check (`SignMsgs`), so receivers drop them — it is a nuisance/spam vector, not a confidentiality or integrity break. - **WireGuard-only deployments** may still use cleartext rooms (the guard only trips on a public bind), because the network already restricts who can reach the bus. Closing the residual metadata exposure requires the per-subject ACL described above, tracked for issue 0003. ## Regression evidence - `pkg/membership` — `TestRequireEncryptedRoomsRejectsCleartext`: with `RequireEncryptedRooms` on, `POST /rooms` for a cleartext policy returns 403 while an encrypted-room create returns 201. - `pkg/client` — `TestAudit_NoSubjectACL`: under the public posture, creating a `ModeNATS` room fails; alice creates an encrypted room and publishes; eve (a registered non-member) raw-subscribes to the subject and receives only ciphertext — she never recovers the plaintext.