Configuration
File discovery
The configuration file is discovered in this order:
--config <path>CUMMENTS_CONFIGenvironment variable$XDG_CONFIG_HOME/cumments/cumments.toml(or~/.config/cumments/cumments.toml)/etc/cumments/cumments.toml./cumments.toml(local development fallback)
Once a file is selected, effective value precedence is:
- Environment variables (
CUMMENTS__prefix,__level separator) - Values from the discovered config file
- Built-in defaults
Example AppService configuration
For local development, set mode = "logging"; no matrix.homeserver or
matrix.appservice section is needed.
Notes
matrix.homeserver.addressis the CS API endpoint used by the AppService.matrix.homeserver.domainis the Matrix ID domain; they are deliberately separate because reverse proxies and well-known delegation make them diverge.matrix.appservice.urlis the callback URL the homeserver uses to reach Cumments, so it must be reachable from the homeserver, not from your browser.- Environment variables are spelled with the
CUMMENTS__prefix and__separators, e.g.CUMMENTS__MATRIX__APPSERVICE__AS_TOKEN. The whole configuration can come from environment variables alone (the bundledmisc/docker/compose.yamldoes exactly this); a config file is optional, and--config <path>overrides file discovery. - The schema is strict: unknown keys are rejected, so old flat field names
(
matrix.homeserver_url,matrix.server_name, ...) fail fast instead of being silently ignored. - CORS headers are derived from the site registry (see below); the legacy
server.cors_originskey is not part of the schema and fails startup like any unknown key. security.operator_tokenenables the Operator API (see API). Placeholder values and tokens shorter than 32 characters are rejected at startup.- SQLite files are created automatically, but the parent directory must exist
(the repo has a
data/directory). - All timestamps are stored in UTC with millisecond precision.
server.trusted_proxiesdeclares which reverse proxies may setX-Forwarded-For; see Reverse proxy trust below. When overriding through the environment, use a comma-separated string (CUMMENTS__SERVER__TRUSTED_PROXIES="loopback,10.0.0.0/8") or a JSON-style array string (["loopback", "10.0.0.0/8"]).server.public_base_url(optional) is the externally reachable base URL of this API (for examplehttps://comments.example.net). Media proxy URLs are minted as absolute URLs against it, which is required when comment sections are embedded on other origins. When unset, the base is derived from each request instead: theHostheader, plusX-Forwarded-Proto/X-Forwarded-Hostwhen the peer is inserver.trusted_proxies. Set the explicit value when forwarded headers are unreliable or you want a fixed public origin regardless of how clients reach the API.security.allow_private_verification_origins(defaultfalse) permits verification of loopback/private/link-local IP-literal origins; keep it disabled in production becauseconfirmmakes outbound HTTP/DNS probes.
Reverse proxy trust
server.trusted_proxies accepts an array of named presets and CIDR
networks:
Each entry is either a preset or a CIDR:
Bare IPs are rejected to keep intent explicit: use 127.0.0.1/32 or
::1/128 instead. Unknown presets, invalid CIDRs, and 0.0.0.0/0 /
::/0 all fail startup with a message naming the offending entry.
Rate limiting honors X-Forwarded-For only when the direct peer is inside
the trusted set. The list is then walked right-to-left, skipping every
trusted entry, and the nearest untrusted address becomes the client key.
Only list networks you actually control behind the reverse proxy; a wide
network means any host on it can forge the client IP seen by the limiter.
Forwarded protocol and host
When the direct peer is trusted, Cumments also accepts
X-Forwarded-Proto and X-Forwarded-Host to mint absolute media URLs and to
derive the public API base. For either header, a comma-separated value uses
the rightmost entry: in an append-mode proxy chain, that entry is the one
added by the proxy closest to Cumments and is less likely to be a
client-supplied prefix.
Your proxy contract must make that value trustworthy:
- Prefer
server.public_base_urlwhen comments are embedded across origins, when multiple proxy paths exist, or when forwarded headers may be unreliable. - Configure the trusted proxy to overwrite
X-Forwarded-ProtoandX-Forwarded-Host, or ensure that its value is appended as the rightmost entry. - Do not mark a proxy as trusted unless it controls these headers. An
untrusted peer's forwarded protocol/host values are ignored; the connection
peer and
Hostare used instead. - Do not include a path, query, userinfo, or multiple hosts in the final forwarded host entry. A malformed value falls back to relative media URLs rather than trusting an attacker-controlled origin.
X-Forwarded-For remains separate: rate limiting walks its list right-to-left
while skipping entries produced by trusted proxies, so the nearest untrusted
client address becomes the limiter key.
Site verification and write-path authentication
security.site_verification controls the instance-wide policy:
Regardless of the policy, the site_id must be registered through the
API/CLI or declared in [sites] before it can be written to; unknown ids
return 404 code=site-not-registered. Sites registered under a
caller-chosen id additionally need an origin verified before writes in
optional mode (random ids keep the relaxed behavior).
In optional mode the WARN relaxation applies only to sites that were
registered through the API and still hold a claim token. A site whose
[sites] entry was removed (or a legacy/backfilled row with no claim token)
is rejected with site-verification-required, so removing configuration
tightens rather than opens the write path.
Per-site trust comes from two sources whose union is the effective rule set:
[sites."<site_id>"]in this file: operator-declared trust.auth_modeis"origin"(default) or"secret";allowed_originsaccepts exact origins andhttps://*.example.comsubdomain wildcards;auth_mode = "secret"requires asecret(HMAC key, at least 32 chars) that is best injected throughCUMMENTS__SITES__<site_id>__SECRET.- Self-service registration through
POST /api/v1/sites: takes an optional, first-comesite_id(a random id is generated when omitted) and returns a one-time claim token. The owner proves domain control via/.well-known/cumments.jsonor a DNS TXT record, then switches to secret auth via the secret endpoint. The CLI equivalent iscumments sites register [--site-id ID]. See API.
Origin-mode requests are accepted only when the browser Origin matches the
effective allowlist; Origin: null and missing Origin are rejected. The
dev-only disabled policy is the exception: it accepts every origin,
including Origin: null from file:// demo pages, and answers with
Access-Control-Allow-Origin: *. Secret-mode requests must carry
X-Cumments-Timestamp and X-Cumments-Signature (HMAC-SHA256 over
timestamp\nMETHOD\npath\nsha256(body)), with the timestamp within ±5
minutes.
Rate limits
Every endpoint family's rate-limit budget is configurable under
[rate_limit]. Values are applied at startup (a restart is required) and
default to the historical hardcoded budgets:
requestsis the maximum per window, per client key; it must be at least 1.claim_tokenis a pre-auth admission budget for all claim-token authentication attempts, including missing/invalid credentials. Successful site-owner operations are still governed bygovernanceand other endpoint budgets.- For SSE,
requestsis the sustained refill rate andburstis the bucket capacity for short-term reconnects/page reloads; both must be at least 1. windowaccepts human durations ("500ms","30s","1h") and must be at least one second.- Environment variables follow the usual mapping, e.g.
CUMMENTS__RATE_LIMIT__WRITE__REQUESTS. - The 429
Retry-Aftervalue derives from each endpoint's configuredwindow.