Trust boundaries (as implemented)
This page documents the concrete database and transport controls behind Architecture overview's control-plane summary, each grounded in the actual migration or source file that enforces it.
Row Level Security plus a full privilege revoke
Every application table has Row Level Security enabled, and every client-facing database role (anon, authenticated) has every ordinary table privilege (select/insert/update/delete) revoked outright:
alter table public.files enable row level security;
revoke all on public.files from public, anon, authenticated;
This is deliberately belt-and-braces: RLS alone would still require a correct policy on every table and every command to fail closed; revoking table privileges entirely means there is no row-level policy to get wrong in the first place for ordinary client roles, because those roles cannot touch the table at all except through a function that has been explicitly granted execute.
SECURITY DEFINER RPCs are the only mutation path
Every read and write goes through a SECURITY DEFINER PL/pgSQL function (set search_path = '' to prevent search-path hijacking), granted to service_role or anon explicitly per function, never to authenticated directly for application tables. A function performs authentication (resolving a tv_ bearer token or a Supabase session), authorization (resolve_file_access, organization role checks), input validation, and mutation together in one call — there is no separate "check access, then act" round trip a race condition could split. admin_audit_list is a representative example: it is the sole SECURITY DEFINER surface over audit_events_view, so authorization and keyset pagination happen exactly once, in SQL.
Service-role isolation
The Node application server holds the Supabase service role key; it is never sent to the browser or the CLI. authenticate_cli_access_token and its callers accept a hash of the CLI's bearer token (sha256, hex), never the raw token — so a database-level compromise that leaks query logs still cannot recover a usable credential. Two migration decisions push this further:
- Purge is structurally forbidden.
filesandfile_versionscarryBEFORE DELETE(and, for a committed version,BEFORE UPDATE) triggers that unconditionally raise a custom SQLSTATE ('TVPRG') for every role, includingservice_role. This matters specifically because Supabase'sservice_rolebypasses RLS and ordinary privilege grants by default — a trigger has no such bypass, so this is the one control that holds even if the service-role credential itself were ever misused.file_metadata_revisionsand every audit table carry the equivalent append-only trigger. No RPC, HTTP route, or CLI command anywhere in this codebase ever issues aDELETEagainst any of these tables, and a client-shaped attempt at the HTTP layer (an HTTPDELETEagainst/api/internal/files/*) is already refused with405 Method Not Allowedbefore any RPC runs. - Append-only audit is a database guarantee. The same trigger pattern applies to every audit table feeding
audit_events_view.tv admin audit listcan filter and page; nothing intv, and no administrator override, can edit or remove an event.
Signed cookies vs. bearer tokens — two separate session mechanisms, by design
Thalix Vault deliberately runs two independent session mechanisms that never share storage:
| Surface | Credential | Storage |
|---|---|---|
| Browser (login, account, device approval) | Supabase access + refresh token | Secure; HttpOnly; SameSite=Lax cookies, server-set only |
CLI (tv) | Opaque tv_ device token, account:read scope | A private, permission-locked local file (see Security and authorization) |
The CLI never receives, stores, or transmits a Supabase access or refresh token — it only ever holds the CLI-specific opaque token issued through the device-approval handshake, and that handshake is validated in the browser using the browser's own HttpOnly cookie session, never by handing the Supabase session itself to the CLI. This means a stolen tv_ token cannot be used to sign in to the web app, and a stolen browser cookie cannot be used as a tv credential — the two blast radii are disjoint by construction, not by convention.
The release trust root
Standalone tv binaries verify every release against an Ed25519 public key embedded directly in CLI source (packages/cli/src/release/trust.ts's EMBEDDED_RELEASE_TRUST_ROOTS), not a key fetched at runtime — so a compromised distribution channel (a hijacked thalix-vault-releases repository, a DNS attack, a malicious mirror) cannot make tv update trust a forged signature; the trust anchor never leaves the binary that ships it. The active production key is a563c039672742d7. Rotation appends a new entry with status: "active" and flips the old entry to status: "retired" — retired keys are kept forever (never deleted) so manifests signed before a rotation keep verifying, while a freshly compiled binary only trusts currently-active entries. A test-only override (TV_RELEASE_TEST_TRUST_ROOT) exists purely for the fixture-server test suite: a real compiled standalone binary (BUILD_KIND === "standalone") hard-refuses with RELEASE_TEST_OVERRIDE_FORBIDDEN if that environment variable is ever set, so no environment variable can talk a real installation into trusting an attacker-supplied key.
Continue with Security and authorization for the role/grant model this infrastructure enforces, or Current state for what remains outside these guarantees today (unsigned Windows builds, unnotarized macOS builds, a single Supabase data plane).