Reliquary Alpha
Docs menu

Docs / Concepts

Security model

Reliquary's rules live in the database, each has a hostile test, secret values are encrypted with a key the database never holds, and the limits of all that are stated plainly.

The database enforces access

Every permission is a Postgres row-level security policy, a trigger, or a checked function, not a check in the web app or the MCP server that a bug could skip. The web app and the MCP server log in as limited roles; they establish who is calling, and the database decides what that caller may do. Among the rules:

  • A member of one vault sees no trace of another, and a token limited to some vaults can't see the rest.
  • Canon files change only through a proposal that a quorum of people approves.
  • An agent acts as its person, minus the ceiling. Every request through a token or connection carries a claim naming it, and the database refuses ceiling actions for any request that carries one.
  • The activity log and the variables access log are append-only. Deleting a whole vault is the one path that removes rows.

Hostile tests on every push

Each rule has a test that tries to break it: a session for one vault reading another, an agent approving a proposal, anyone editing a log row, an agent revealing a variable, a revoked token reading anything. They run against a real database on every push and pull request, with the MCP server, the web app and the CLI tested end to end. Each new rule is also broken on purpose once, to see its test fail.

One test reads the database's own schema rather than its data: a column whose name suggests a secret (a token, a key, a password, a credential, a code) must be a hash or an encrypted value, or the test fails. Encrypted values all use the one scheme variable values use, so rotating the key re-encrypts every one of them. A short list of exceptions names each column and why it holds no secret, such as the id of a token rather than the token itself.

Agents and text

  • The ceiling keeps approving, rules, members, grants, secret values, export and deletion with people.
  • Text people and agents wrote reaches an agent fenced as data, with a random marker no text can forge.
  • Reliquary sends your content to no model provider. The one model it runs is gte-small, inside Supabase, which turns file text into numbers so search can match by meaning (see Vaults and files); it writes nothing. Those numbers are stored beside the text, readable by the same people, and removed with it. Your AI tools get what their agent reads, under your agreement with them.

Connections and sign-in

  • Sign-in to the web app is by an emailed code or link. Sessions are HttpOnly, Secure, __Host- cookies, bound to the app's own host (app.reliquary.redmage.cc). The public site and docs (reliquary.redmage.cc) set no cookies at all. A session renews itself through Supabase Auth about every hour. If Auth is busy or down when that happens, you stay signed in and the page says why and to try again; only Auth refusing the session itself signs you out.
  • The OAuth sign-in for MCP clients and the CLI runs on the app host: its issuer is https://app.reliquary.redmage.cc.
  • MCP clients and the CLI use OAuth 2.1 with PKCE (S256 only), and tokens bound to the one service they are for: an MCP token is refused by the env API and a CLI token by the MCP endpoint. Refresh tokens rotate, and a reused one revokes the whole grant.
  • Clients are identified by a metadata document at an https URL, fetched with guards against reaching private networks.
  • Access tokens are stored only as hashes, always expire (at most a year), and are revocable at once. See Connections.
  • Sign-in, OAuth, invites, the MCP endpoint, the env API and web forms are rate limited, with counters in the database shared by every server. Entering sign-in codes is limited per email address and per IP address, so a 6-digit code can't be guessed. Addresses are counted under a keyed hash, never stored. See Limits.

The web app

  • No client-side script at all: the content security policy forbids scripts, and nothing loads from another site.
  • Every form carries a per-session token and must come from the app's own pages.
  • The app's pages are never indexed by search engines. Moving between the public site and the app only ever redirects to one of Reliquary's own two web addresses.
  • Everything people and agents wrote is escaped; a file's Markdown is rendered with raw HTML off.

Secret values

  • Values are encrypted by the web app with AES-256-GCM, a fresh nonce each, under a key kept outside the database. The database holds only ciphertext, and no role that serves requests can read it directly.
  • Ciphertext leaves the database only through functions that check the caller's grant and write the access log in the same transaction.
  • The MCP server refuses to start if it is given the key: it can't decrypt anything.
  • The key can be rotated without downtime; re-encryption runs as a separate operator role that serves no requests. Link credentials are encrypted with the same key and re-encrypted with the values.
  • Server logs never carry a token, a sign-in code, file text or a value; the test suite checks the logs.

Vault content

  • File text, proposals, notes, thread messages and search vectors are sealed by the server that writes them (AES-256-GCM under the content key set, CONTENT_KEYS, with the vault's id bound in, so a body moved to another vault does not open). The database holds ciphertext and never the key; the server opens a body when a member or their agent reads it. Content written before this was switched on is plain bytes until the operator's backfill seals it in place, a batch at a time, changing nothing else about it; the same run moves every body to a new key when the content key set is rotated. Only the operator's database role can run it, not either server. The content key set is separate from the values key, so no key opens both.
  • Both servers hold the content key set, since the MCP server serves files. The MCP server still refuses to start with the values key.
  • The schema test above also keeps a list of every column that holds content, with its type, so a new content column, or one that changes type, fails the test until it is accounted for.

Hosting

The database (Supabase) is in Frankfurt (AWS eu-central-1); the app itself runs in the United States (Netlify, AWS US East), so requests are processed there before anything reaches the database. Connections to the database use TLS verified against Supabase's certificate authority. Data is backed up daily. Email (sign-in codes, account notices and vault invites) is sent through Resend, from Ireland; Resend keeps its delivery records in the US for 30 days. The companies involved are on the sub-processors page.

What we can't promise

  • Agents can read what reaches them. An agent that can run commands in a process holding a variable can read that variable. reliquary run limits exposure to one process; it doesn't stop that process.
  • The operator can decrypt. Someone with both the database and the web app's key, which means Reliquary's operator, could technically decrypt variable values. We don't, and access is limited to running the service.
  • No certification yet. No SOC 2 report, ISO certificate or SSO. Reliquary is built and run by one person, and is in alpha: things change and may break.

To report a vulnerability, see the security page.

For agents: this page as Markdown, and every page at /llms.txt.