Secure your instance
A Costpilot instance has two kinds of clients with two kinds of credentials: people sign in to the dashboard with an account, developer machines and pipelines send telemetry with the ingestion token. Demo mode turns accounts off so the public demo stays open.
Accounts for the dashboard
Outside demo mode the dashboard and /api/* require a sign-in. Accounts
live in the same SQLite database (via Better Auth,
in-process: no extra service, no mail server).
- First start: the server log prints a one-time setup code
(
Auth: no account yet - ... setup code 1a2b3c...). Open the dashboard, enter it and create the first admin. For automated deployments setCOSTPILOT_ADMIN_EMAILandCOSTPILOT_ADMIN_PASSWORDinstead. - Team: admins invite people under Admin → Users. An invite is a single-use link (valid 7 days) that the admin shares directly.
- Roles: Admin manages users, projects, agent settings, branding and budget alerts; Viewer sees the whole dashboard. The last admin cannot be demoted or removed.
- Sessions: httpOnly, SameSite=Lax cookies, stored server-side (7 days, extended while in use), revoked on sign-out and removal; a password change revokes the other sessions. Sign-in is only accepted from the dashboard's own origin and rate-limited in the Docker image (production mode).
| Variable | Purpose |
|---|---|
COSTPILOT_PUBLIC_URL | Public dashboard URL, e.g. https://costpilot.example.com. Recommended: pins the trusted origin and cookie security. Needed when serving over plain HTTP on a non-localhost host, otherwise the browser drops the Secure cookie |
COSTPILOT_AUTH_SECRET | Cookie signing secret (32+ random chars). Unset = generated once and stored in the database |
COSTPILOT_ADMIN_EMAIL / COSTPILOT_ADMIN_PASSWORD | Creates the first admin on startup while no account exists |
COSTPILOT_AUTH=off | Disables accounts, e.g. behind a reverse proxy that authenticates already. DEMO_MODE=1 implies it |
Token for ingestion
/v1/* (telemetry, session reports, agent config) is never behind the
sign-in: the agent has no interactive login. Protect it with a bearer token:
COSTPILOT_INGEST_TOKEN=$(openssl rand -hex 24)
Developers pass it once; the agent writes it into each tool's telemetry
headers (OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <secret>" or the
tool's equivalent) and appends --token to the hook command:
costpilot-agent init --endpoint https://costpilot.example.com --token <secret>
/health always stays open for platform healthchecks.
Rotating the token
Change COSTPILOT_INGEST_TOKEN on the server, redeploy, then have every
developer re-run init with the new --token. Until they do, their
telemetry is rejected with 401 and costpilot-agent check fails on the
server line.
Project-level setups and secrets
init --project writes the token into files that usually live in the
repository: ./.envrc, ./.codex/config.toml, ./.gemini/.env and the
hook command in ./.claude/settings.json, ./.gemini/settings.json,
./.codex/hooks.json and ./.cursor/hooks.json. Keep those files out of
version control, or provide the token through the environment instead. See
Report only some projects.
Upgrading from Basic Auth
COSTPILOT_BASIC_AUTH from earlier versions is gone. After the update,
create the first admin with the setup code (or the admin variables) and
invite the team.
Split hosting
With the dashboard on another origin, set CORS_ORIGINS to that origin
explicitly (that also allows the session cookie). Sign-in then needs both on
the same site, e.g. app.example.com and api.example.com, because the
cookie is SameSite=Lax.