Skip to content

Security checklist

Work through these before you store private knowledge.

Network

  • Publish only TCP 80 and 443, from Caddy.
  • Keep the Docker daemon, the database admin port, and SSH behind a firewall.
  • Confirm REMENTUM_PUBLIC_URL uses the same HTTPS origin as REMENTUM_DOMAIN.
  • Keep REMENTUM_DEV_AUTH=false in production. The API refuses to start with it on there.
  • Keep REMENTUM_TRUSTED_PROXIES limited to the reverse proxies you actually run. Any address in that list can spoof its client address and reset its rate-limit bucket. Pin the proxy's address when clients reach Rementum from a private network, because the default loopback,uniquelocal preset trusts any private address.

The reference Compose file binds PostgreSQL to 127.0.0.1 for administration. It does not expose PostgreSQL or the embedding service to the public network.

Secrets

  • Restrict .env to the deployment administrator.
  • Escrow REMENTUM_MASTER_KEY off the server and out of database backups.
  • Use generated database, cookie, and OAuth signing keys.
  • Keep provider keys and Resend keys out of source control and support logs.
  • Leave the superuser blanks in docker-compose.yml in place: the api and worker services override REMENTUM_DATABASE_ADMIN_URL and REMENTUM_POSTGRES_SUPER_PASSWORD with empty values so a superuser connection, which bypasses row-level security, never exists inside those containers. Only migrate, backup, and restore receive it.
  • The API, worker, and embedding images contain only production dependencies and built output, every service runs with no-new-privileges, and the Node services drop all Linux capabilities.

Envelope encryption and data boundary

Rementum implements application-layer envelope encryption with searchable metadata:

  • Envelope encryption: Article bodies, version history, and staged bodies are encrypted with AES-256-GCM under individual per-brain data keys. The instance master key wraps those data keys and is held in memory; it is never stored in PostgreSQL or included in database backups.
  • Position-bound authentication (AAD): Every version body is sealed with Additional Authenticated Data that names its brain, article, and version number. Readers recompute that value from the row's position rather than trusting a stored copy, so ciphertext moved between versions or articles fails to decrypt.
  • Searchable metadata (plaintext): PostgreSQL stores routing summaries, titles, slugs, links, audit metadata, MCP usage metadata, and vector embeddings in plaintext so hybrid metadata, full-text, and vector search operate without decrypting article bodies. MCP usage metadata is limited to the workspace and optional brain/article ids, the OAuth client id and name, the tool name, and the timestamp (it does not keep tool arguments, prompts, queries, outputs, errors, IP addresses, or user ids). Use encrypted disks and encrypted backups.

Article generation mode

Rementum stores submitted titles, one-sentence summaries, and bodies as they are. Nothing is generated, no article text is sent to an external model, and there is no AI provider to configure. The summary is searchable metadata and is not covered by article-body encryption.

An agent connected over MCP can propose a brain invitation but never receives a link: the proposal waits on the brain page until an owner approves it in the browser, which is also where the link is issued and sent. An instruction planted in an article the agent read therefore cannot grant anyone access on its own.

Accounts

Keep public registration off unless you need it. When on, it requires verified email delivery and applies request rate limits. Prefer team invitations for controlled access, and remove members who no longer need the team's workspaces.

The web interface uses a 14-day opaque session cookie. PostgreSQL stores only its hash. Logout revokes the current session; a password reset revokes every web session, MCP OAuth grant, and API key for the account. OAuth bearer tokens and API keys are accepted only at the exact workspace MCP URL, never by the REST API, so an MCP client cannot reuse the browser cookie as an API credential.

MCP access tokens last 15 minutes. Refresh tokens and their authorization grants have a 360-day idle lifetime: issuing a replacement refresh token extends the still-valid grant to cover it. Clients allowed to refresh tokens are independent of the browser OAuth session, including clients that omit offline_access. Active clients can stay connected beyond the original authorization date. Expired or revoked grants are never revived, and replaying a refresh token whose replacement was used invalidates its token family. Existing valid grants adopt the longer lifetime when refreshed; an already expired grant requires new authorization. Connections issued by older releases may still carry a browser-session binding; re-authorize those once to obtain an independent connection.

Clients should persist each replacement refresh token before exiting and coordinate refreshes across processes sharing credentials, including background processes that share a CLI's credential store. A client that never read a refresh reply, for example because the machine slept mid-request, may retry with the old token while the lost replacement is still unused: the API consumes that replacement and issues a new one. If the lost replacement surfaces later, two holders exist and the grant is revoked. The trade-off is that a leaked, already rotated token works until the legitimate client next refreshes, which then revokes the grant for both. The API logs oauth_refresh_failed with a fixed reason category to distinguish missing or expired grants from refresh-token reuse, and oauth_refresh_retried when it recovers a lost reply; it does not log token values or raw provider error details.

The browser portion of MCP OAuth uses the current web session as its identity. A browser without one is redirected through the normal sign-in page. Before Rementum grants the scopes requested by the client, it verifies that the signed-in account belongs to the team that owns the workspace. There is no second OAuth login. A new client, workspace, or expanded scope set shows a consent screen with the client identity, callback destination, resource, scopes, and a deny action; an already approved grant can reconnect silently. Knowing a workspace URL alone grants nothing, and the issued bearer token remains audience-bound to that one workspace. Successful clients remain individually revocable on Connections.

API keys are the alternative for clients that cannot keep a refresh chain alive. Only the web app can create, list, or revoke one: the key routes need the connection:* scopes, which neither an OAuth connection nor a key carries, so an agent cannot mint a key for itself. Rementum generates 32 random bytes, shows the rmk_ key once, and stores only its SHA-256 hash. A key is bound to the workspace chosen when it was created and is refused at any other MCP URL. On each request the API checks the owner's account is still enabled and narrows them to that workspace exactly as it does an OAuth token, so the key gets the four workspace scopes, never the instance-owner flag, and stops working when the owner leaves the team. Keys do not expire, so a leaked key works until you revoke it on Connections, reset the password, or delete the workspace. MCP usage analytics list a key's calls under its name.

A client sends the key as the apiKey query parameter of the workspace MCP URL, so connectors that only take a URL can use it. A URL is copied and logged more readily than a header. The API's request log and the bundled Caddy's access and error logs replace the value with [redacted], but the client's own configuration and logs keep it in full, and so does any proxy or CDN in front of Rementum that logs request URLs. The API reads the key only from that parameter; a key sent as a bearer token is rejected.

An MCP client identifies itself in one of two ways. A client that publishes an OAuth Client ID Metadata Document, as Claude Code does at https://claude.ai/oauth/claude-code-client-metadata, uses that HTTPS URL as its client id. Rementum fetches the document, requires it to name the same URL as its own client_id, and takes the allowed callback addresses from it, so no client record is created or stored. The fetch has a 2.5-second timeout and a 5 KB size limit, refuses hosts that resolve to loopback, private, or other special-use addresses, and the document is cached for as long as its cache headers allow. Anything the document says about itself, including its name, is chosen by whoever controls that URL. Clients without a hosted document register themselves dynamically and appear under the name they registered. In both cases Rementum validates the redirect URI before it returns an authorization code.

The instance owner, the account create-owner made, has a read-only panel at /admin that counts what the instance holds and lists every registered account (see the operations guide). It shows metadata only: addresses, names, verification state, memberships, connection counts, and timestamps, never an article body, a tool argument, or a network address. The owner flag is checked by the API and again inside the database functions that read across teams, so a missed check in one layer exposes nothing. A bearer token never reaches the panel because the REST API accepts browser sessions only, and an OAuth actor narrowed to a workspace drops the flag altogether.

Deletion is permanent, immediate, and web-only. There is no trash, and MCP agents cannot delete anything:

  • Workspaces. Owners and admins create or rename them; only the team owner deletes one. The last workspace is protected, and deletion needs the exact workspace name, because it permanently removes every brain, note, and MCP usage record inside. Revoking a connection or deleting one brain does not erase that workspace's historical usage metadata; it lasts until the workspace is deleted.
  • Brains. Only the brain owner deletes a brain, and deletion needs the exact name. It also destroys the only copy of the brain's wrapped data key, so its encrypted bodies become unrecoverable even from backups taken afterward.
  • Teams. Owners and admins create or rename them; only the team owner deletes a team, a user's last team is protected, and deletion needs the exact name.

Backups

The backup job refuses to write an unencrypted archive. Test a restore on a separate host, and keep the age identity apart from the archives. A valid archive without REMENTUM_MASTER_KEY cannot decrypt article bodies.

Report vulnerabilities through the private channel named in the repository SECURITY.md. Do not open a public issue with exploit details or instance secrets.