Access + Security

Roles say what. Grants say where.

Documentation for forty clients is forty clients' worth of risk. Trove KB scopes every read of company-owned data in the service layer, keeps secrets out of its own database entirely, and has a second sign-in step of its own that single sign-on does not replace.

Per-company access

Out of scope is "not found".

Roles

A role is a set of named permissions: companies, documents, doc types, secret fields, knowledge base writing, the administration area. The three the product starts with (admin, tech, readonly) are fixed; under Admin → Roles you add your own with any mix of the same powers. A role holding the admin area is an administrator in every sense.

Company access

A user or an API key sees every company or only the ones granted. A new account starts with none until an admin grants some, so an SSO user signing in for the first time reads nothing.

Not found

Anything outside the granted set answers "not found", never "forbidden", because saying a company exists but is not yours is itself a disclosure. Holds across search, backlinks, exports, attachments, deep links, the API, and MCP.

Live, not cached

Scope is read from the database on every request, not from the session, so revoking a grant applies on the next page load.

Audit

Append-only. Every save, reveal, grant, key, and second-factor change, with who and when. A viewer under Admin.

Admin → Users, a tech account granted three of the instance's companies
Admin → Users. A tech granted three companies; everything else answers not found.
Admin → Roles listing the built-in roles with their permissions and a form to add one
Admin → Roles. The built-in three are fixed; yours take any subset.
Accounts

Its own second step.

Local accounts hash with Argon2id. API keys are random 32 bytes, shown once, stored as a SHA-256 hash, looked up by prefix. Rich text is sanitized server-side on every write. CSRF protection on session routes and rate limiting on auth and the API are on by default.

Personal settings → Security with an authenticator app enrolled, two passkeys, and recovery codes
Personal settings → Security.
Passwords

8 to 128 characters with mixed classes, never the person's own name or address, never one in a known breach (checked by k-anonymity against Pwned Passwords, so the password never leaves the server). Ten wrong in a row closes the account for fifteen minutes.

Second step

Authenticator app, passkeys (a YubiKey, or the one a phone or password manager holds), or both, with ten single-use recovery codes. Required for admins within a week; offered to everyone.

Step-up

The pages that issue API keys, change people, or change security settings ask for the second step again after fifteen minutes.

Single sign-on

OIDC with discovery: Entra ID, Google, Authentik, Keycloak. Three env vars. SSO in front does not stand in for the second step; it composes with it.

Reset

A reset link by mail when SMTP is set; otherwise an administrator sets a temporary password that must be changed at the next sign-in. Lost everything? Another admin puts it right from Admin → Users.

Attachments

Typed by their bytes.

Images, PDF, Word, Excel, PowerPoint, CSV, Markdown, and plain text, up to a limit you set, on a local volume, an NFS share, or any S3-compatible bucket.

  • →What a file is comes from its own bytes. Renaming an executable to .png does not get it in.
  • →Macro-enabled and legacy Office formats are refused whatever extension they arrive under.
  • →HEIC and HEIF photos are converted to JPEG as they are stored, so a picture from a phone opens for everyone.
  • →Downloads are always served as attachments with nosniff, so an uploaded file can never execute on the site's origin.
  • →Logos are PNG, JPEG, or WebP. SVG is refused, because an SVG can carry script and a logo is drawn on every page.
Deployment

Nothing open to the internet.

Bind the port to 127.0.0.1 and publish it through a Cloudflare Tunnel, with Access in front for a second gate if you like. The vault sidecar never publishes a port at all. The public knowledge base goes on its own hostname behind a proxy that passes nothing else.

A backup is a pg_dump, a tarball of the uploads volume, and your .env. It never contains a secret, because the database never held one.

Cloudflare and backup guides in the docs

Read the source. It's AGPL.

Every rule on this page is a line in the service layer and a test in the e2e suite.