# your domain, e.g https://example.com APP_URL=http://localhost:3000 PORT=3000 # --- Security / reverse proxy --- # The app derives the client IP (req.ip) from the `X-Forwarded-For` header via # Fastify `trustProxy`. That header is client-forgeable, so XFF is trusted only # from proxies on the configured trusted networks. Deploy this app behind a # trusted reverse proxy that SETS/OVERWRITES (not appends) `X-Forwarded-For` # with the real client IP. If XFF is trusted from an untrusted source, any # per-IP throttling — including the /mcp Basic brute-force limiter — can be # bypassed by an attacker who simply spoofs `X-Forwarded-For` to rotate IPs. # (The /mcp limiter keeps a global per-email key as an IP-independent backstop, # but the per-IP and per-IP+email keys rely on a trustworthy X-Forwarded-For.) # # TRUST_PROXY controls which proxies are trusted to set X-Forwarded-For. # Default (unset/empty): `loopback, linklocal, uniquelocal` — XFF is trusted # ONLY from private/loopback proxies, so a public-IP client cannot spoof req.ip. # This is the safe default for the common case where the reverse proxy runs on # loopback or a private network; req.ip still resolves to the real client. # WARNING: this changed the previous default of trust-all. If your reverse proxy # sits on a PUBLIC IP, the default will NOT trust its XFF and req.ip will be the # proxy's IP — set TRUST_PROXY accordingly. Accepted values: # - true restore trust-all (ONLY safe if a trusted proxy ALWAYS overwrites # X-Forwarded-For; otherwise clients can spoof their IP) # - false never trust X-Forwarded-For (req.ip is the socket peer) # - number of trusted proxy hops in front of the app # - comma-separated CIDR/IP list of trusted proxies, e.g. # `127.0.0.1, 10.0.0.0/8` # TRUST_PROXY= # APP_SECRET has a DUAL role: it signs JWTs AND derives the AES-256-GCM key that # encrypts stored AI-provider credentials (API keys) at rest. CONSEQUENCE: if you # change APP_SECRET after setup, every stored AI API key becomes undecryptable — # you must re-enter them in AI settings — and all existing sessions/JWTs are # invalidated. Choose it ONCE, keep it stable, and back it up alongside your DB. # minimum of 32 characters. Generate one with: openssl rand -hex 32 APP_SECRET=REPLACE_WITH_LONG_SECRET JWT_TOKEN_EXPIRES_IN=30d DATABASE_URL="postgresql://postgres:password@localhost:5432/docmost?schema=public" REDIS_URL=redis://127.0.0.1:6379 # options: local | s3 | azure STORAGE_DRIVER=local # S3 driver config AWS_S3_ACCESS_KEY_ID= AWS_S3_SECRET_ACCESS_KEY= AWS_S3_REGION= AWS_S3_BUCKET= AWS_S3_ENDPOINT= AWS_S3_FORCE_PATH_STYLE= # Azure Blob Storage driver config AZURE_STORAGE_ACCOUNT_NAME= AZURE_STORAGE_ACCOUNT_KEY= AZURE_STORAGE_CONTAINER= # default: 50mb FILE_UPLOAD_SIZE_LIMIT= # options: smtp | postmark MAIL_DRIVER=smtp MAIL_FROM_ADDRESS=hello@example.com MAIL_FROM_NAME=Docmost # SMTP driver config SMTP_HOST=127.0.0.1 SMTP_PORT=587 SMTP_USERNAME= SMTP_PASSWORD= SMTP_SECURE=false SMTP_IGNORETLS=false # Postmark driver config POSTMARK_TOKEN= # for custom drawio server DRAWIO_URL= # Gotenberg URL for server-side PDF export GOTENBERG_URL= DISABLE_TELEMETRY=false # Allow other sites to embed Docmost in an iframe. IFRAME_EMBED_ALLOWED=false # Only used when IFRAME_EMBED_ALLOWED=true. When empty, any origin is allowed. # Example: https://intranet.example.com,https://portal.example.com IFRAME_ALLOWED_ORIGINS= # Comma-separated list of additional origins allowed to call the API via CORS. # The APP_URL origin and native mobile (Capacitor) origins are always allowed. # Leave empty for a same-origin (web-only) deployment. CORS_ALLOWED_ORIGINS= # Expose OpenAPI/Swagger docs at /api/docs (development/debugging aid only). SWAGGER_ENABLED=false # Capacitor (mobile shell): hosted client URL loaded by the iOS shell so the # AGPL web client is NOT bundled into the .ipa (see docs/mobile-app-plan.md §9). # Leave empty for Android bundled mode / local development. CAP_SERVER_URL= # Enable debug logging in production (default: false) DEBUG_MODE=false # Log database queries DEBUG_DB=false # Log http requests LOG_HTTP=false # MCP server (community): the embedded /mcp endpoint authenticates PER USER. # An MCP client authenticates with one of: # - HTTP Basic: `Authorization: Basic base64(email:password)` — the user's own # Docmost login/password. The server validates the credentials and the MCP # session then acts under that user's permissions (edits attributed to them). # - Bearer access JWT: `Authorization: Bearer ` (the user's # `authToken` cookie value). Validated as an ACCESS token. # # OPTIONAL service-account fallback. When a request carries NEITHER Basic NOR # Bearer credentials and these are set, the MCP session falls back to this # shared service account (back-compat; useful for CI/scripts). Leave BLANK to # require per-user credentials. MCP_DOCMOST_EMAIL= MCP_DOCMOST_PASSWORD= # MCP_DOCMOST_API_URL=http://127.0.0.1:3000/api # Optional shared guard for the /mcp endpoint. When set, every /mcp request must # carry a matching `X-MCP-Token` header (separate from `Authorization`, which now # carries the per-user credentials). When unset, /mcp relies on the per-user # credentials above plus the workspace MCP toggle and network isolation (do not # expose the port publicly). # MCP_TOKEN= # MCP_SESSION_IDLE_MS=1800000 # Per-embedding-call timeout in milliseconds for the RAG indexer. # A slow/hung embeddings endpoint fails after this and the batch continues. # AI_EMBEDDING_TIMEOUT_MS=120000 # --- Anonymous public-share AI assistant --- # Opt-in per workspace (AI settings -> "public share assistant"; off by default). # When enabled, anonymous visitors of a published share can ask an AI about that # share at POST /api/shares/ai/stream. The assistant is read-only and hard-scoped # to the single share tree, but every call spends real tokens on the workspace # owner's configured AI provider. # # DEPLOYMENT REQUIREMENT: the per-IP rate limit on this endpoint is only # effective behind a trusted reverse proxy that OVERWRITES (not appends) # X-Forwarded-For with the real client IP. The app runs with trustProxy, so # without such a proxy an attacker can rotate X-Forwarded-For to evade the # per-IP limit. Put this endpoint (and the app) behind a proxy you control that # sets X-Forwarded-For to the real client IP. # # Backstop: a cluster-wide, sliding-window cap per workspace (IP-independent, # keyed by the server-resolved workspace id) bounds the owner's bill even if the # per-IP limit is fully evaded. It is a COST backstop, not an access control, and # FAILS CLOSED if Redis is unavailable (an optional assistant briefly going # offline is safer than an unbounded bill). Override the hourly cap below # (default: 300 calls per workspace per rolling hour). # SHARE_AI_WORKSPACE_MAX_PER_HOUR=300 # # Per-request output-token ceiling for the anonymous assistant (default: 512). # Worst-case output per accepted call = agent steps (5) × this value. # SHARE_AI_MAX_OUTPUT_TOKENS=512