Files
FabledGrimoire/docs/configuration.md
T
bvandeusenandClaude Opus 5.5 dbd6d5b8b5
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 37s
CI & Build / Python tests (push) Failing after 1m20s
CI & Build / integration (push) Successful in 2m27s
CI & Build / Build & push image (push) Skipped
feat(6838): read the client address and HTTPS from the trusted proxy hop
grimoire/proxy.py is the one reader of X-Forwarded-For and X-Forwarded-Proto.
It takes the entry TRUSTED_PROXY_HOPS in from the right (default 1; a shorter
header reads its leftmost entry; 0 trusts no proxy), so an entry a client puts
left of the proxy's is never believed. Before this, with TRUST_PROXY_HEADERS
on, the leftmost X-Forwarded-For entry keyed the login, register, forgot and
reset rate limits and the audit log, and a client could choose it.

The session cookie is Secure, and HSTS (max-age=31536000) is sent, exactly
when the request is HTTPS by the trusted proxy's word. Plain HTTP is
untouched and nothing redirects. SECURE_COOKIES is gone (a startup warning says
so when it is still set); TRUST_PROXY_HEADERS=false still maps to 0 hops, with
a warning to move to TRUSTED_PROXY_HOPS. Admin audit lines now use the same
client address instead of the socket peer.

The placeholder-key refusal keyed on SECURE_COOKIES, so it now refuses either
placeholder key outright; the instance makes its own key when none is set.

Family idea #5004 (IsHTTPS, trusted-hop X-Forwarded-Proto), canon v2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:32:20 -05:00

7.6 KiB

Configuration

Grimoire reads its configuration from environment variables. Most settings people change day to day (registration, SMTP, retrieval thresholds, backups, forge connections) live in Settings in the web UI instead.

Any variable marked secret can also be read from a file: set <NAME>_FILE to the path, for example a Docker secret at /run/secrets/grimoire_key.

Environment variables

Core

Variable Default Description
DATABASE_URL (secret) postgresql+asyncpg://grimoire:grimoire@localhost:5432/grimoire PostgreSQL connection string, using the asyncpg driver
SECRET_KEY (secret) kept in DATA_DIR Signs sessions and seals stored credentials. Unset, the instance makes its own on first start and keeps it in DATA_DIR/secret_key. Set it only to manage the key yourself; it wins over the kept one (see below)
DATA_DIR /data Where the instance keeps its own SECRET_KEY. Mount a volume here
BASE_URL http://localhost:5000 Public URL, used in email links and OIDC redirects
TRUSTED_PROXY_HOPS 1 How many reverse proxies stand in front of Grimoire. The client address and the scheme are read that many entries in from the right of X-Forwarded-For and X-Forwarded-Proto; 0 ignores both headers. See Behind a reverse proxy
LOG_LEVEL INFO DEBUG, INFO, WARNING or ERROR
LOG_RETENTION_DAYS 90 Days of app logs to keep
FASTEMBED_CACHE_DIR /data/fastembed-cache Where the embedding model is stored after its first download

Sign-in

Variable Default Description
LOCAL_AUTH_ENABLED true false turns off username/password login, leaving SSO only
OIDC_ISSUER — OIDC issuer URL
OIDC_CLIENT_ID — OIDC client ID
OIDC_CLIENT_SECRET (secret) — OIDC client secret
OIDC_SCOPES openid profile email Scopes to request

SSO turns on when the issuer, client ID and secret are all set. See SSO / OAuth.

Email

Admins can set SMTP in Settings; these are the defaults until they do.

Variable Default
SMTP_HOST —
SMTP_PORT 587
SMTP_USERNAME —
SMTP_PASSWORD (secret) —
SMTP_FROM_ADDRESS —
SMTP_FROM_NAME Fabled Grimoire
SMTP_USE_TLS true

Integrations

Variable Default Description
PLUGIN_MARKETPLACE_URL this repo's git URL The git URL shown in the plugin install command under Settings → MCP Access. An admin can override it in Settings
FORGE_KIND, FORGE_BASE_URL, FORGE_TOKEN (secret) — A git forge connection for admins' projects, so a token can live in a Docker secret. Each user can add their own under Settings → Integrations; a stored connection for the same host wins
FORGE_WEBHOOK_SECRET (secret) — Verifies push webhooks from the forge
SEARXNG_URL — A SearXNG instance, listed as the web-search backend under Settings → Integrations

Running it

Docker Compose

docker-compose.yml runs the published image with its own database, and needs nothing set:

docker compose up -d

Open http://localhost:5000. The first account registered becomes the admin; after that, registration stays closed until an admin opens it in Settings or sends an invite.

Update with docker compose pull && docker compose up -d. The key and the data stay in their volumes.

From source

docker compose -f docker-compose.dev.yml up --build

Docker Swarm

docker-compose.prod.yml is a stack file: the app and database on a private overlay network, trusting one proxy (TRUSTED_PROXY_HOPS: "1"). A Swarm can move the app to another node, away from a node-local /data volume, so the stack sets its own key: provide DB_PASSWORD and SECRET_KEY in the environment (or mount a Docker secret and set SECRET_KEY_FILE):

DB_PASSWORD=... SECRET_KEY=... docker stack deploy -c docker-compose.prod.yml grimoire

The database user, database and volume default to grimoire. An install created under the name Scribe keeps its data by setting POSTGRES_USER, POSTGRES_DB and PGDATA_VOLUME to the names it was created with.

The shipped files leave out healthchecks and restart policies on purpose. The app waits for its database on startup, and the orchestrator's defaults restart it if it ever exits.

Volumes

  • Database (pgdata): everything Grimoire stores.
  • App data (/data): the instance's own SECRET_KEY, the embedding model cache and diagnostics. Losing it costs the key: Settings then asks for saved forge tokens and the backup secret again, and the model downloads again.

Back up the database with Settings → Data → Scheduled backups (Backups). A database backup doesn't carry the key; to move sealed credentials to a new instance, copy /data/secret_key with it.

Behind a reverse proxy

Grimoire doesn't terminate TLS. Put it behind Nginx, Traefik or Caddy, forward to port 5000, and keep port 5000 off the internet. Set BASE_URL, and TRUSTED_PROXY_HOPS to the number of proxies a request passes through (one proxy is 1, the default; Cloudflare in front of Traefik is 2).

location / {
    proxy_pass http://127.0.0.1:5000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # MCP responses can stream.
    proxy_buffering off;
    proxy_read_timeout 600s;
}

Each proxy appends its entry to X-Forwarded-For and X-Forwarded-Proto, so Grimoire reads the entry TRUSTED_PROXY_HOPS in from the right: the one your own proxy wrote. Whatever a client puts to the left of it is ignored. A header with fewer entries than that (a LAN road with one proxy beside a Cloudflare road with two) is read at its leftmost entry, the one the first proxy wrote.

When that entry says https, the session cookie is marked Secure and the response carries Strict-Transport-Security: max-age=31536000. Over plain HTTP neither is sent, and Grimoire never redirects to HTTPS, so a LAN install keeps working.

A hop count higher than the real number of proxies lets a client choose its own address, so count them. With 0, the headers are ignored and the socket peer is the client: use it when clients reach port 5000 directly.

Security checklist

  • Keep the key. Forge tokens and the backup destination's S3 secret are sealed under a key derived from SECRET_KEY, so a copy of the database alone can't read them. Unset, the instance makes a random key and keeps it in /data/secret_key, readable by its owner only; keep that volume. If the key changes (say, a restore onto an instance with a different key), Settings marks those credentials and asks for them again; nothing else breaks. The app warns at startup when SECRET_KEY is a public placeholder or /data is not a mounted volume, and refuses to start with a placeholder key (dev-secret-change-me, change-me-in-production).
  • TRUSTED_PROXY_HOPS matches the number of proxies in front of Grimoire, so rate limits and the audit log see the real client and the cookie is Secure over HTTPS.
  • Registration is open only for the first account. Invite people, or open it briefly from Settings.
  • Signing everyone else out: changing a password ends that user's other sessions; Settings → Account has a button for it too.
  • API keys are per user, shown once, and can be read-only. Revoke any you no longer use under Settings → MCP Access.