Some Rooms Are Yours Alone: Local-Only Boards and Export Control

July 2, 2026

The moment you connect your community server to federation, you’ve made a promise on behalf of everyone who posts there: what’s written here travels. For a public board that’s the point. For the board where a handful of regulars plan the group’s next event — or anything else that was only ever meant for the local room — that promise is a privacy incident waiting to happen.

So Phosphor made the boundary explicit, per board, at the schema level.

Every board has an export switch

Each board record carries a syncArea field. The semantics are deliberately coarse:

  • syncArea set — the board participates in echomail federation under that area name. Local posts relay to peers; inbound echomail for the area lands in the board. Backfill (catch-up history for a connecting peer) includes it.
  • syncArea null — the board is local-only. No live relay, no backfill, not offered in area lists to peers. As far as the network is concerned, the board does not exist.

Null is the default posture for new boards a sysop creates without thinking about federation, which is the right failure mode: forgetting to configure privacy keeps a room private; forgetting to configure sharing just means one less federated area until you flip the switch. Opt-in sharing, opt-out nothing.

The sysop manages it like any other board property — a “Network Area” field in the board manager’s create/edit dialogs. Blank means local-only; the field is honest about what it controls.

Enforcement has to be in the write path

The naive implementation — check the flag at display time — would be wrong, because the leak wouldn’t be in the display. It would be in the relay: the moment a local post event hits the federation bridge, a copy exists on another machine.

So the export check lives exactly where posts leave. The thread and post listeners that feed the federation bridge check the board’s syncArea first and return early on null — a local-only board’s post events simply never reach the wire. The backfill path (which replays history to a newly connected peer) applies the same filter, and the inbound matcher only routes echomail into boards that actually declare the area. Every path that could carry a post off the server asks the same question first.

// Post listener — before anything touches the network
String syncArea = boardNetworkAreaById(boardId);
if (syncArea == null) return;   // local-only: nothing leaves
bridge.onLocalPost(boardId, syncArea, ...);

Why per-board and not per-user

You could imagine a model where each poster chooses visibility. We rejected it on third-place grounds: posting is a social act with shared context, and “who can see this?” is a property of the room, not the speaker. A room’s contract should be stable — you know what a board is for, so you know what happens to what you write there. Per-message visibility settings turn every post into a privacy decision; per-room export turns it into one clear rule the community can learn.

It also matches how the old boards worked. Echo areas were public by definition, and every sysop also ran local areas that never left the machine. The line between them was drawn on the area, by the sysop, once — not negotiated per message, forever, by everyone.

The general principle

Federation features get pitched as reach: your community, everywhere. The engineering that makes it trustworthy is the opposite feature: the ability to say this part, nowhere. A DTP server that can’t keep a secret isn’t a place — it’s a broadcast station. Draw the boundary in the schema, enforce it in the write path, and let the private rooms stay rooms.

Full post: https://phosphorbbs.net/blog/local-only-boards-export-control/

#BBS #federation #digitalthirdplace #dtp #socialterminal #privacy #selfhosted #jterm