corparius is self-hosted and local-first. You run it, you own the data, and the threat model is mostly about protecting that data on your own machine.
Please report privately, not in a public issue:
- Open a GitHub security advisory, or
- email the maintainer (see the address on the GitHub profile).
Include what you found, how to reproduce it, and the impact. Expect an acknowledgement within a few days. Please give a reasonable window to fix before any public disclosure.
These are properties of the design, not bugs — but they matter to how you deploy.
- The console binds
127.0.0.1by default. If you expose it (reverse proxy, a non-localCORP_UI_HOST), setCORP_UI_TOKENso every call needs theX-Corp-Tokenheader. The doctor fails loudly if you bind off-localhost with no token, because anyone who can reach it can spend money and publish. - The token covers reads as well as writes. It used to guard mutations only,
on the reasoning that nothing but localhost could reach the port. Setting a
token is the operator saying otherwise, and at that point
/api/settingsand/api/companyreturning company configs, filesystem paths and which providers are configured is not a defensible default. With no token set, nothing changes: reads stay open and the first run needs no configuration. - Cross-site requests are refused without any configuration. Binding
localhost does not protect you from the browser you are already running: any
page you visit can
fetch()http://127.0.0.1:8600. Writes are refused unlessSec-Fetch-SiteorOriginsays the request came from the console's own page. Both headers are on the browser's forbidden list, so a hostile page cannot set them, and a client that sends neither — curl, a script, the MCP server — is allowed through, so nothing offline breaks. This is deliberately not a login screen or a CSRF token: a password in front of your own machine is the thing this console refuses to be. CORP_UI_ALLOWED_HOSTSstops DNS rebinding. A hostile domain can re-point its own name at127.0.0.1, at which point the browser considers the request same-origin and theOrigincheck passes — but theHostheader still says the attacker's name. On a loopback bind, only loopback names are accepted. Bound off-loopback (Docker, a reverse proxy) anyHostis accepted by default, because a strict list would break existing deployments on upgrade: setCORP_UI_ALLOWED_HOSTSto the name you serve the console under. It is read from the environment and.envonly, never from the settings store, so a successful write to/api/settingscannot add a host to it.- Request bodies are capped at 1 MiB and chunked bodies are refused, so a single request cannot exhaust memory.
- Secrets at rest. API keys saved from the console live in the SQLite store
(
data/corparius.sqlite) and are included in backups. By default they are stored in the clear. You can turn on at-rest encryption by settingCORP_SECRET_KEY(see docs/securite.md); it is off by default so the offline mock mode needs no dependency. On POSIX, corparius sets owner-only permissions (0700on the data dir,0600on the store); treat the file and its backups like a password regardless. - Live mode calls out. The default mock mode makes no network calls. Enabling a cloud or free provider sends prompts to that provider — that is the point, but it is your data leaving the machine, under your keys.
- The version check is opt-in.
CORP_UPDATE_CHECKis off by default; when on it makes a single request to the GitHub releases API and never downloads.
corparius is pre-1.0; fixes land on main and in the next tagged release. Please
test against main before reporting.