BaryoDev / BaryoDev/barakoCMS

The event store is the one place tenant isolation is still the application's alone

Open
#623 0 comments 0 reactions 0 assignees View on GitHub
core enhancement migration security
Dominant language
C#
Stars
6
Forks
7
Avg merge
4h 21m
Merged PRs (30d)
307

Description

`docs/tenancy-at-the-database.md` says it in its own words:

> **It does not** cover the event store. `mt_events` and `mt_streams` are outside Marten's row level security, so the tenant filter on those remains the application's alone.

So `Tenancy:DatabaseEnforcement` gives documents a second boundary underneath the application, and events keep exactly one. Every content history read, every workflow run rebuild and every projection is filtered by application code and nothing else.

## Why it matters

The whole argument for database enforcement is that a bug which opens a session without a tenant has something underneath it. That argument does not extend to the event store, and the event store is where content history lives — arguably the most sensitive per-tenant data in the product, since it holds every prior value of every field including ones later marked Sensitive.

This is not a live vulnerability and should not be filed as one. It is the gap between what the feature promises and what it covers, and the doc is honest about it. What it should not be is permanent.

## What Marten offers

Per-tenant partitioning of the event tables:

```csharp
opts.Events.TenancyStyle = TenancyStyle.Conjoined; // already set
opts.Events.AppendMode = EventAppendMode.Quick; // required for this
opts.Events.UseTenantPartitionedEvents = true;
```

That puts each tenant's events in their own partition, which is physical separation rather than a filter, and gives RLS policies something per-partition to attach to.

## The cost, stated plainly

`EventAppendMode.Quick` is a prerequisite and it changes how sequence numbers are assigned at append time. Anything that assumes gapless, globally ordered sequences has to be re-read before this lands — `TriggeringEventSequence` on `WorkflowRun` is the first thing to check.

Partitioning an existing `mt_events` is a migration of the largest table in the database. That is why this is filed against 5.0.0 rather than a minor: it is the kind of change that gets a documented upgrade path and a rehearsal, not a flag flip.

## What has to stay true

- Conjoined tenancy for events stays; this is isolation underneath it, not a replacement for it.
- The known limitation the doc already names — Marten represents "no tenant" as the default tenant, so a session opened without one sees the default partition — is not solved by this either. Do not let the changelog imply it is.
- Content erasure (`ContentEraser`) deletes from `mt_events` and `mt_streams` by stream id. Partitioning must not make GDPR erasure slower or partial.

## Done when

- Events are partitioned per tenant on a fresh database, with a test proving a session for tenant A reads no tenant B events with the application filter disabled.
- Sequence-dependent code is audited against Quick append and the findings are written down, whether or not anything needed changing.
- `docs/tenancy-at-the-database.md` loses the event store exception, and the upgrade path for an existing database is documented.

Found while auditing the stack for unused Marten capabilities.

Contributor guide

Open the contributing guide

Research direction

Start with docs/tenancy-at-the-database.md, then inspect WorkflowRun.TriggeringEventSequence and ContentEraser for assumptions affected by Quick append and event partitioning. Add fresh-database isolation coverage, verify erasure behavior, audit sequence-dependent code, and document the existing-database upgrade path and findings.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, postgresql
Domain
databases, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.