# Security Policy ## Reporting a vulnerability Please use **GitHub's private vulnerability reporting** for this repository (Security tab → "Report a vulnerability"). Do **not** open a public issue for security problems, and never include real financial data or secrets in a report. You can expect an initial response within a few days. Fixes ship as regular commits/releases; credit is given unless you prefer otherwise. ## Scope & posture Kist is a **local-first, single-user** app: it binds to `127.0.0.1`, stores data in a local SQLite file outside the repo, and has no accounts or server-side components. The most relevant vulnerability classes are therefore: anything that makes the app listen beyond localhost, code execution via crafted local data, secrets/PII leaking into the repository, and CSRF/DNS-rebinding against the local server or a local LLM. ## What already guards the repo - `server/security_review.py` — secret scan of the tree **and full git history**, static dangerous-pattern checks, maintainer personal-data audit, config hygiene, endpoint smoke tests and an active probe of any local LLM server; runs on every push/PR + weekly in CI and is available from the Control Center. - Scanner-efficacy tests plant synthetic leaks and assert they are caught. - CI enforces a coverage floor and a bandit gate. CodeQL (default setup), Dependabot alerts and security updates, and secret scanning with push protection are on. ## Threat model — the localhost assumption (read before self-hosting) The API has **no authentication by design**: the security model is "one user, on their own machine, over loopback." That assumption is what keeps a keyless, open API safe. **Do not expose it beyond `127.0.0.1`** (no `0.0.0.0` bind, no reverse proxy to the public internet, no port-forward) — if you do, anyone who can reach the port has full, unauthenticated access to your finances. If you need remote access, put it behind a VPN or an authenticating proxy; the app itself does not authenticate. Because a browser can reach `127.0.0.1` on your behalf, the app defends the two attacks that don't need your machine, only your browser: - **DNS rebinding** — a `before_request` guard rejects any request whose `Host` header isn't loopback, so a malicious page that rebinds its domain to `127.0.0.1` is refused. - **CSRF** — state-changing (`POST`/`PUT`/`DELETE`) API calls with a non-loopback `Origin`/`Referer` are rejected, so another site can't drive your app. - Defense in depth: a strict **Content-Security-Policy** (`script-src 'self'`, no inline scripts) neutralizes injected markup even if crafted data — e.g. market text synced from an external source — reaches the DOM unescaped; plus `X-Frame-Options: DENY` and `nosniff`. - **Mass-assignment guard** — the generic `PUT /api/settings` writer drops security-sensitive keys (`commit_repos`, `commit_author`, `ai_mode`, `backup_dir`, `app_config`, …); those are set only through their own dedicated endpoints, so a same-origin write can't repoint the commit tracker at an arbitrary path or flip the AI to cloud mode through one open key/value setter. The `security_review` suite **actively pentests these guards** (a forged `Host` and a cross-origin write must both return 403, and a protected settings key must stay unwritable) so a regression fails CI. ## Not in scope - Multi-user / shared-host deployments (see above — add auth yourself). - The security of your external integrations (Supabase, n8n, your cloud LLM key): treat their data as untrusted input; the app escapes it and frames RAG content to the model as data, not instructions, but you own those services.