# The customer facade

A customer session reaches the application through a surface that is closed by default. In the browser, an operator path is rewritten to its customer equivalent, and anything unrecognised is turned into a blocked one on purpose. On the server, an allowlist decides what a customer session may call at all — and anything not on it is refused, whatever the browser did.

- **Status:** Available
- **Audience:** both
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/security/customer-facade/

## Two mechanisms, only one of which is a boundary

|  | The rewrite | The allowlist |
|---|---|---|
| Where it runs | In the browser, before the request is sent | On the server, before anything is routed |
| What it does | Maps an operator path to the customer equivalent | Decides whether a customer session may call this at all |
| On something unknown | Turns it into a blocked address deliberately, rather than passing it through | Refuses it |
| Is it a security control? | No. It is consistency, and a fail-closed default | Yes. This is the boundary |

The distinction matters. Nothing enforced in a browser is enforced at all — a browser is the one place an attacker fully controls. The rewrite exists so that one set of screens can serve both audiences without maintaining two, and its fail-closed behaviour keeps a mistake in that mapping from quietly producing an operator-shaped request. The refusal that actually protects anything happens on the server.

## Closed by default, and what the refusal says

The list is of what is permitted, not of what is forbidden. A new address is unreachable by a customer session until somebody deliberately adds it, which means the failure mode of forgetting is a customer who cannot do something rather than a customer who can do something they should not.

When the refusal happens, it is written for the person who hit it. It says that what they asked for is not part of their workspace, that Connect keeps every business behind its own surface, that the address they reached belongs to the platform itself — and, plainly, that if a screen sent them there the screen has a gap and it is not something they did wrong. It does not print an internal name, a milestone, or a technical explanation aimed at whoever wrote the guard.

> **Note** That wording is deliberate and it was earned. The sentence it replaced talked about sending mail, and was being returned for requests that sent nothing at all — leaving people to debug a message that described a different subject entirely.

## The symptom this explains

*It works on my account and gives a customer a refusal.* That report is almost never about the feature. It is the surface: either the rewrite does not know the new path, or the allowlist does not include it. Both are one-line omissions in the place a new capability was added, and neither shows up while the person building it is testing as the operator.

- New behaviour belongs in a module both audiences' routers call; each router adds only the route. A second implementation for the other audience is the thing this design exists to prevent.
- Every new operator path needs either a customer rewrite or a written reason for not having one.
- The gaps are enumerated with reasons rather than asserted, and the enumeration is checked: it may shrink, and it is not allowed to grow.
- The reverse symptom — something a customer can reach that the operator cannot — is treated as the same class of defect, because parity runs both ways.

## What the facade is not

**Not the isolation boundary** — It decides what a session may call. Which records answer is decided twice more, in the object layer and in the database — see [Workspace isolation](/docs/security/workspace-isolation/).
**Not a smaller feature set** — The capability set is one implementation for both audiences. The facade changes the door, not what is behind it.
**Not permissions** — It does not distinguish between people inside a workspace. That is [Authorisation](/docs/security/authorization/).
**Not a published map** — This page describes the shape of the surface on purpose, and does not list what is on it. The list is not documentation; it is a control.

## Questions

### I got a refusal saying this is not part of my workspace. Did I do something wrong?

No, and the message says so. Either you reached an address that belongs to the platform rather than to any workspace, or a screen offered you something it should not have. The second is worth reporting, because it is a gap on our side rather than a mistake on yours.

### Could I reach operator functions by changing the address myself?

The allowlist is on the server and refuses anything not explicitly permitted for a customer session, so what a browser sends does not decide the answer. Beyond that, the workspace comes from the session, so even a permitted call answers with your own workspace's records and nobody else's.

### Why is a feature available to the operator and not to me?

Usually it is not — it is a gap in the surface, and it is treated as a defect. Two capabilities are genuinely operator-only because they are about running the platform rather than using Connect. Anything else is worth reporting.

## Related

- [Security and isolation](https://connectbyjbrh.com/docs/security/)
- [Workspace isolation](https://connectbyjbrh.com/docs/security/workspace-isolation/)
- [Authorisation](https://connectbyjbrh.com/docs/security/authorization/)
- [The operator's workspace and a customer's](https://connectbyjbrh.com/docs/account/owner-vs-tenant/)
- [A screen returned 403](https://connectbyjbrh.com/docs/troubleshooting/403-on-a-screen/)
- [A tenant facade that fails closed](https://connectbyjbrh.com/research/closed-by-default-facade/)

## What this page is based on

- `backend/app/main.py` — the customer allowlist, its default and its refusal text
- docs-source/sources/GENERAL.md section 1 — the rewrite, the surface and enumerated parity
- Connect capability registry (docs-source/facts.py)
- `backend/app/workspace_kernel.py` — why an address list alone was not enough
