Run your instance / Operate
Security and privacy
Accounts, keys and tokens, and exactly what data is collected.
knotel runs on your machine. Spans, logs and web events are in your ClickHouse, and accounts, projects, keys and alerts in the SQLite file in /data. None of it is sent to a third party. The only outbound connections it makes are ones you turn on or can turn off: the monthly DB-IP download (GEOIP=off), alert webhooks, and an anonymous daily report to knotel.dev, chosen when the instance is claimed and changed in Settings (Telemetry lists every field).
Accounts
- There is no public sign-up. The first account claims the instance with the setup code and becomes its owner; after that, admins invite everyone else.
- Each account has a role. The owner can do everything, and is the only one who makes admins or hands the instance to someone else. Admins see every project and manage members, invites, deleting projects, storage, webhooks and the live stream. Members see only the projects they're given.
- A member's access to each project is viewer (reads everything in it) or editor (also changes its alerts, filters, keys, source maps and retention). An admin can let a member create projects; they become an editor of each one they create. An invite sets all of this up front.
- Access tokens act as the member who made them, so MCP sees the same projects they do. Instances upgraded from before roles make the first account the owner and everyone else an admin.
- Passwords are at least 8 characters. Sessions last 30 days and renew while in use. Passkeys are bound to the instance's host name.
- Removing a member signs them out immediately and revokes their access tokens.
Keys and tokens
- Ingest keys and access tokens are random secrets shown once. Only their SHA-256 hash is stored.
- Ingest keys can only write data to one project. They can't read anything, which is why they're safe in a web page.
- Every ingest key lists where it accepts data from, on the project's Send data page: domains (
example.com,*.example.com), matched against theOriginheader a browser sends, and IP addresses or CIDR ranges (203.0.113.7,2001:db8::/64), matched against the client's address. Anything else gets a403before the body is read, and is counted as blocked on the Usage page. A key can't be saved with an empty list. Changes reach ingest within a minute. - To accept data from anywhere - a sender whose address you don't know yet, or a first look at an instance - list
*, which the Send data page shows as Anywhere.0.0.0.0/0and::/0fold into it, so a list can't end up covering IPv4 but turning away every IPv6 sender. It's still something you write down rather than a default, and it swallows the rest of the list: a key carrying it works for whoever copies it, so treat it as public. - A domain stops another site from reusing a key copied out of your page, because a browser won't let a page lie about its origin. It doesn't stop a script, which can send any
Originit likes. An IP address is only as trustworthy as the proxy in front of knotel, so expose knotel's port to that proxy alone. Give servers their own key with only IPs on it and keep the domain-restricted key for the browser. If junk still arrives, revoke the key and create another. - An app on Cloudflare Workers has no address of its own: its requests arrive from a shared Cloudflare address. List the zone the Worker runs on instead (
example.com); it's matched against theCF-Workerheader Cloudflare adds to a Worker's outgoing requests. - Keys created before allowed sources existed accept data from anywhere until you give them a list. The Send data page marks them.
- Access tokens read every project their member can see. Keep them out of repositories and client-side code.
- Revoking a key or token takes effect at once in the database, but the server keeps recent key lookups in memory, so a key used in the last minute may be accepted for up to a minute more.
The instance's own secrets
BETTER_AUTH_SECRET, CLICKHOUSE_PASSWORD and the OAuth client secrets live in the environment: .env, or your platform's settings. Keep .env out of the repository; .gitignore already does. The ClickHouse in docker-compose.yml publishes no port, so only the knotel container can reach it. A backup of ClickHouse or of knotel.db holds everything the instance holds, so store backups as carefully as the server.
What gets collected
The browser script sends:
- URLs without query strings, and the page referrer
- Timings of page loads and fetches, and response status codes
- Error messages and stack traces
- The user agent and a random per-tab session id
It doesn't read cookies, storage, form fields, or request and response bodies, and knotel doesn't store the client IP addresses it sees. Where the browser can't keep a visitor id, web analytics count a visitor by a hash of the address, browser, day and project, which can't follow them past the day or across projects; the address itself isn't kept.
Page views and browser spans are located by the country (and, if sent, region and city) headers a proxy in front of knotel adds, such as CF-IPCountry. With no such proxy, the sender's own address is looked up in the DB-IP database below, on your server; the address itself isn't stored.
The Workers SDK, for apps running on Cloudflare Workers, records method, URL (without query string), status, user agent, which Cloudflare location served the request (cloudflare.colo, a three-letter code like SIN), and where the caller is as Cloudflare located them: country, region, city, continent and network (geo.* and cloudflare.as_organization). It never records the IP address.
A backend sending OTLP from its own servers is located differently: when a span names its caller's address (client.address, or the older http.client_ip, which most HTTP instrumentations record), ingest looks it up in the DB-IP database and adds geo.country.iso_code, geo.region.name, geo.locality.name and geo.continent.code, with knotel.geo.source set to ip. The lookup happens on your server, against a copy of the database it downloaded; no address is sent anywhere. The address stays on the span as your instrumentation sent it; drop it with an attribute filter in your collector if you'd rather not keep it. Private addresses are skipped. See Geolocation.