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.
Chie is a self-hosted analytics product with two distinct surfaces:
- A public ingestion surface that any browser can reach. It is hostile territory by definition.
- 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.
- 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=laxcookie.secureis 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.
- 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.
- The
/api/eventand/api/perfendpoints are public by design. - They reject events whose
domainis not a registered site. - When the request has an
Originheader, the host is checked against the site's allowed hostnames before the event is stored. - The endpoints honor
DNT: 1andSec-GPC: 1and 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.
- 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.
- The dashboard sets
Strict-Transport-Security,X-Content-Type-Options, andReferrer-Policyheaders vianext.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=falseis 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.
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.
Open a private security advisory on the Chie GitHub repo. Include the affected version and steps to reproduce.