Skip to content

Security: olususus/Chie

docs/security.md

Security model

This page describes the security boundaries of Chie so you can deploy it safely. For findings from a hands-on review, see security-audit.md.

Threat model

Chie is a self-hosted analytics product with two distinct surfaces:

  1. A public ingestion surface that any browser can reach. It is hostile territory by definition.
  2. An authenticated dashboard surface that should only be reachable by the operator and any users they create.

The threats we care about, in order:

  • A third party recording fake events for someone else's site.
  • A third party reading another tenant's stats.
  • A leaked or stolen API key being abused.
  • A malicious browser extension or page exfiltrating user data via the dashboard.
  • The auth secret being committed or replayed.

Authentication

  • Passwords are hashed with bcrypt (cost factor 12). The verify path uses constant-time comparison.
  • Sessions are signed JWTs (HS256) carrying user id and email, set as an httpOnly, sameSite=lax cookie. secure is forced on in production.
  • The first signup creates the admin account. After that, the signup endpoint refuses requests; new users must be invited (planned).
  • API keys are 32 byte random tokens displayed only once. The server keeps sha256(token) and a short prefix for display.

Authorization

  • Every /api/sites/... and /api/stats/... endpoint resolves the domain to a site row and confirms the caller owns it.
  • Shared link URLs (/shared/<slug>) carry a per-site random slug and grant read-only access to that one site only.
  • API keys inherit the permissions of the user that created them.

Ingestion

  • The /api/event and /api/perf endpoints are public by design.
  • They reject events whose domain is not a registered site.
  • When the request has an Origin header, the host is checked against the site's allowed hostnames before the event is stored.
  • The endpoints honor DNT: 1 and Sec-GPC: 1 and silently drop the event in those cases.
  • Per-IP rate limits are enforced in Redis (50 events/min, 100 perf beacons/min by default).
  • A configurable list of bot user agents per site is dropped before any storage or counter is touched.

Privacy

  • No cookies are set on the visited site.
  • The session id is sha256(domain || ip || ua || daily_salt). It rotates every UTC day, so a visitor cannot be linked across days.
  • IP addresses are used in the session hash and for rate limiting. They are never written to ClickHouse.
  • City is opt-in (STORE_CITY=true). Country and region are stored by default and come from the edge proxy headers.
  • Hostname is sent so that one snippet can serve multiple subdomains. It is not personal data.

Network

  • The dashboard sets Strict-Transport-Security, X-Content-Type-Options, and Referrer-Policy headers via next.config.ts. CSP is intentionally permissive for the dashboard because it serves user-defined content for the brand logo. If you do not change branding, you can tighten CSP at the reverse proxy.
  • Run Chie behind TLS. Setting COOKIE_SECURE=false is only for local development.
  • The script endpoint (/t.js, /api/script) is cacheable and safe to put behind a CDN. There are no auth bits in the script.

Data retention

ClickHouse has a 24 month TTL on the events and events_perf tables. The materialized aggregations have no TTL because they are already small. Adjust the TTL in clickhouse/init.sql to match your own retention policy.

Reporting an issue

Open a private security advisory on the Chie GitHub repo. Include the affected version and steps to reproduce.

There aren't any published security advisories