Repository navigation
Row-level security between members, and audited support access - #182
Merged
Merged
Conversation
… 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 byActingSessionfromapplication/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 inacting.SYSTEM_PATHSwith 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-roleservice makessalli_app(no superuser, no BYPASSRLS) and gives it the tables on every start, so existing installs move at their nextup;salli-server doctorwarns 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 atGET /v1/auth/support-access(salli support-access, the console's Settings › Sign-ins) and ends one withDELETE. 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