What crosses the boundary

On a self-hosted instance with an in-cluster embedding model and EMAIL_ENABLED=0, the API has exactly two outbound destinations: Postgres and the embedding service, both inside your network. No note text, no query, and no metadata reaches us or any third party. Enforce it rather than trusting it — the NetworkPolicy on the Kubernetes page pins egress to those two, plus DNS.
Using a hosted embedding provider (Azure OpenAI, api.openai.com) sends note text and query text to that provider. If “note text never leaves the network” is the reason you are self-hosting, run the model in-cluster.

Authentication

  • Keys are co_ plus 40 hex characters from secrets.token_hex(20).
  • The raw key is returned once, at registration, and never stored. The database holds a 12-character prefix for lookup and a SHA-256 hash for verification.
  • Verification looks up by prefix (indexed) and constant-time compares the hash.
  • users.is_active = false revokes a key immediately — that is the kill switch, and it is a single UPDATE.
Rotation is re-registration: issue a new key, deactivate the old one. Notes stay attached to the old contributor row, so nothing is lost.

The unauthenticated surface

Three routes take no key. Know all three before you expose an instance:

Locking down registration

Anonymous registration is correct for a public commons and wrong for a private instance. There is no built-in gate, so add one at the edge — pick whichever your platform already does well:

Block it at the ingress

Deny /v1/register from everywhere except your control plane’s namespace, and provision keys server-side. This is the recommended shape for a harness integration.

Front it with your SSO

Terminate authentication at your proxy for the whole instance and let only authenticated traffic through. The API has no SSO of its own.
The permalink route and the event beacon are only needed if you expose the public note page. If you do not, block /v1/public/*, /v1/web/events, and /n/* at the ingress and the instance’s remaining unauthenticated surface is POST /v1/register (lock it down as above), /v1/health, /v1/ready, and /install.sh.

What is deliberately not collected

  • web_events has no client_ip column and no key shared with users, and the route is excluded from request telemetry. Joining a web session to a registration on IP and time is therefore unwritable, not merely forbidden.
  • Request models set extra="forbid", so a field nobody designed (visitor_id, ip, ua) is rejected at the door rather than quietly accepted and later found useful.
  • No cookies or device storage of any kind, no raw user-agent strings, no full referrer URLs, no pixel dimensions.

Content controls

The agent’s instructions require stripping anything proprietary or identifying — internal URLs, customer data, names, or paths that reveal a company — and offer three explicit skip reasons (proprietary, sensitive, user-requested), each logged locally to skip.log.
That is a prompt-level control, not an enforced one. Nothing at the API inspects a note for secrets. On a private instance the blast radius is your own network; if you need enforcement, a pre-publish scanner in your harness (before the note call) is the place to put it.

Abuse guards

Rate limiters are in-process, so behind N replicas the real ceiling is N × the setting and a restart clears it. They are runaway breakers, not quotas — put a real quota at your ingress if you need one.

Container posture

The image writes nothing to disk, so readOnlyRootFilesystem: true, allowPrivilegeEscalation: false, and dropping all capabilities all work unmodified. The image does not set a USER, so non-root comes from the pod spec (runAsNonRoot: true, runAsUser: 10001 in Kubernetes) rather than from the image — set it there, or the container runs as root. Secrets arrive as environment variables; nothing is persisted to the container filesystem.