Skip to content

Row-level security between members, and audited support access - #182

Merged
chandralegend merged 3 commits into
mainfrom
feat/tenant-isolation
Oct 25, 2026
Merged

chandralegend merged 3 commits into
mainfrom
feat/tenant-isolation

Conversation

@chandralegend

Copy link
Copy Markdown
Contributor

For the hosted pilot (one server, many unrelated members), isolation no longer rests on the application's filtering alone.

Row-level security (core_0051). Every table holding a person's data is FORCEd with a policy that shows and accepts a row only for the person the transaction acts for (salli.user_id, set per transaction by ActingSession from application/acting.py). Requests act for the signed-in person (get_principal, the MCP gate, the consent page); jobs act for each person in turn; no one means nothing visible. Cross-person paths are named in acting.SYSTEM_PATHS with reasons: migrations, sign-in, members, member-lookup, audit-chain, erasure, usage, jobs, support. Tests: a table without a policy fails; raw queries in a person's transaction can't read, update, insert or delete another's rows; an HTTP test through the real boundaries; the integration suite now runs as a non-superuser role with each service called as its person.

Roles. compose.yaml's database-role service makes salli_app (no superuser, no BYPASSRLS) and gives it the tables on every start, so existing installs move at their next up; salli-server doctor warns while the role bypasses RLS. docs/self-host.md and docs/hosting.md say how to set it up otherwise.

Support access (core_0052). salli-server support open <email> --reason "…" [--minutes 60] (max 240) gives a read-only token for the books' GET routes; open, each read and the end are in the audit chain. The member sees them at GET /v1/auth/support-access (salli support-access, the console's Settings › Sign-ins) and ends one with DELETE. Policy in docs/security.md and docs/privacy.md.

Console: Settings (Sign-ins section) gains a Support access block, so its screenshots change.

🤖 Generated with Claude Code

chandralegend and others added 3 commits October 26, 2026 01:36
… way in

A hosted pilot puts many unrelated people on one server, and until now only
the application's filtering by user id kept their books apart. Postgres now
checks it again: every table holding a person's data has a forced row-level
security policy (core_0051) that shows and accepts a row only for the person
the transaction acts for. Each transaction says whom that is
(adapters/db/session.ActingSession, from application/acting.py): the API's
get_principal, the MCP gate and the consent page set the signed-in person,
jobs act for each person in turn, and a transaction acting for no one sees
nothing. The few paths that must reach across people (migrations, sign-in
lookups, the operator's members commands, sharing's email lookup, the audit
chain, erasure, usage counts, scheduled jobs, support sessions) name one of
acting.SYSTEM_PATHS, each with its reason; a test fails on any other name
and on any table holding a person's data without a policy. compose.yaml's
new database-role service makes salli_app, a role with no superuser or
BYPASSRLS, and gives it the tables, so existing installs move at their next
up; salli-server doctor warns while Salli's role bypasses the policies. The
integration suite now runs as such a role, every service called as its
person.

Support access is the operator's only way into a member's books through
Salli: salli-server support open <email> --reason ... [--minutes 60, at most
240] gives a read-only token for the books' GET routes; opening, every read
and the end (by the member, the operator or its time) are in the audit
chain. The member lists sessions at GET /v1/auth/support-access (salli
support-access, the console's Settings, Sign-ins) and ends an open one with
DELETE. core_0052 adds support_sessions. The policy is in docs/security.md
and docs/privacy.md; the roles in docs/self-host.md and docs/hosting.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chandralegend
chandralegend merged commit 13f0e5b into main Oct 25, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant