block / block/buzz

A-RLS-1..5 conformance gate is specified in multi-tenant-relay.md but not implemented; reference deployment discharges none of it

Open
#4,060 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

`docs/multi-tenant-relay.md` proves tenant isolation *relative to* stated axioms, including A-RLS-1..5 covering Postgres row-level security. §Conformance states how each axiom is discharged (`docs/multi-tenant-relay.md:655`):

> **A-RLS-1..5** are admitted by a startup/CI assertion suite: enumerate every tenant-bearing table and assert RLS enabled + restrictive policy present; assert the request role is `NOBYPASSRLS` and non-owner-or-FORCE; assert no `SECURITY DEFINER` function in the request path reads tenant tables without re-establishing context; assert every unique/FK constraint includes `community_id`. A failing assertion rejects the deployment.

I can't find that assertion suite, or any RLS implementation, in the tree.

**Searched** at `19d57b0d46baa55814ac737041a36d0b405c9f64` (current `origin/main` at time of writing): `migrations/`, `crates/`, `deploy/`, `schema/`, `scripts/`, `.github/workflows/`, `Justfile`, `docker-compose.yml`, `Dockerfile`.

**Patterns:** `ENABLE ROW LEVEL SECURITY`, `CREATE POLICY`, `app.community_id`, `pg_policies`, `relrowsecurity`, `rolbypassrls`, `NOBYPASSRLS`.

**Result:** zero hits outside `docs/multi-tenant-relay.md` itself.

Against the repo's reference `docker-compose.yml`, the axioms evaluate as:

| Axiom | Status |
|---|---|
| A-RLS-1 — RLS enabled + restrictive `community_id` policy on tenant tables | Not met — no `ENABLE ROW LEVEL SECURITY` or `CREATE POLICY` in `migrations/` |
| A-RLS-2 — request role non-superuser, `NOBYPASSRLS`, non-owner | Not met — the single role in the reference compose owns the tenant tables |
| A-RLS-3 — `app.community_id` set via `SET LOCAL` per transaction | Not met — no `SET LOCAL` of that setting anywhere in `crates/` |
| A-RLS-4 — `SECURITY DEFINER`/`leakproof` audit | Not applicable — no such functions found |
| A-RLS-5 — unique/FK constraints include `community_id` | Appears met in `migrations/0001_initial_schema.sql` |

To be clear about what this is *not*: the document is marked `draft` and its abstract is explicitly forward-looking about elevating `community` to the tenant boundary, so this isn't "you documented a control you never built." The row-level half has shipped — `communities` exists, `community_id` is on every scoped table, and the fail-closed host→community bind is live in `crates/buzz-relay/src/router.rs`. The gap is specifically that §Conformance describes a discharge mechanism for A-RLS-1..5 that doesn't exist.

### Why this matters rather than being pedantry

The document states the RLS layer is load-bearing, in its own words (I4, `docs/multi-tenant-relay.md:477`):

> A dropped application predicate yields ∅ under A-RLS, and NI still holds; removing the RLS guard makes the dropped predicate produce a cross-label row — **proving RLS load-bearing, not decorative.**

So under the model as specified, the application-level fence in `router.rs` is one of two layers, and I4 is the argument for what happens when the first one has a bug. Deployments matching the reference compose have one layer, not two — and §Conformance reads as though a gate would have caught that.

### Precedent for the fix

§Conformance already handles exactly this situation for a different axiom, naming the non-conforming shipped artifacts by file and line (`docs/multi-tenant-relay.md:684`):

> the shipped HA examples (`replicaCount: 3` in `deploy/charts/buzz/examples/argocd-app.yaml:27` and `deploy/charts/buzz/examples/flux-helmrelease.yaml:35`) are P3-non-conforming as shipped unless the operator adds one of: …

Flagging shipped artifacts that fail to discharge an axiom is therefore the document's established convention. A-RLS-1..5 has no equivalent note, which is what makes the current text misleading rather than merely incomplete — a reader reasonably concludes the gate exists.

### Relationship to #2621

#2621 proposes CI for the formal artifacts themselves — the Tamarin/TLC/NIP-PL models that are load-bearing per the docs but run by hand, and therefore "silently decay" between manual runs. This issue is the complementary half: those models are proven *relative to* the axioms in §Axioms, and A-RLS-1..5 has no discharge mechanism either. Both are needed for VISION.md's "proven, not asserted" to hold continuously — #2621 keeps the proof green, this keeps the premises true. If maintainers prefer, resolution 2 below could fold into #2621's proposed `formal.yml` as an additional job rather than landing separately.

### Suggested resolutions, cheapest first

1. **Docs only** — add an A-RLS non-conformance note in §Conformance mirroring the P3 one, stating that the reference `docker-compose.yml` does not discharge A-RLS-1/2/3 and what an operator must add. Removes the misleading reading immediately.
2. **Implement the gate the doc already specifies** — the startup/CI assertion suite. The doc specifies its checks precisely enough to implement directly, and it fails closed by design.
3. **Ship the layer** — an RLS migration plus a dedicated `NOBYPASSRLS` non-owner request role and `SET LOCAL app.community_id` in the request path.

Happy to be told I've missed an implementation that lives somewhere I didn't search.

Contributor guide

Open the contributing guide

Research direction

Start with docs/multi-tenant-relay.md, especially §Conformance around lines 655 and 684, then compare its A-RLS-1..5 claims with docker-compose.yml, migrations/0001_initial_schema.sql, and crates/buzz-relay/src/router.rs. Decide which proposed resolution is accepted, and verify that the document accurately states the reference deployment's conformance or that the described gate and RLS layer are present.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker-compose, postgresql, rust
Domain
backend, databases, documentation, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.