Transparency

Network activity inventory

This is the complete, published list of every network request Noesa can make on your behalf. It is derived directly from the source: the endpoint set is exactly the eight routes declared in worker/src/config.ts (ROUTES), and the router in worker/src/index.ts returns 404 for any other path. There are no hidden endpoints.

Two things are true of every row below and are enforced in code, not by policy:

  • No screenshots or pixels are ever sent — to any endpoint, ever (see Screenshots and pixels never egress).
  • Cloud AI is the only thing that transmits your content, and only when you opt in — and even then it transmits derived text, never images.

The eight endpoints

All paths are on the Noesa proxy Worker (the host your app is configured to use). "Auth header" is what the client sends to identify itself; the proxy strips all such headers before any call to the upstream provider (buildUpstreamHeaders, worker/src/headers.ts).

# Method + path When it fires Carries Does not carry Auth header
1 POST /api/llm/v1/chat/completions Only when you use cloud AI (session synthesis or activity attribution) — opt-in cloud text OpenAI-compatible chat body = derived text (messages); X-Noesa-Model, X-Noesa-Feature headers Screenshots/pixels; hardware identifiers (the device id is random, not hardware-derived); identity headers to the provider (stripped); the body is never read/buffered/logged X-Noesa-License (paid) or X-Noesa-Device (free)
2 GET /api/warm TLS pre-warm while a session is active, to cut first-request latency Nothing — handleWarm returns 204 and ignores the request Any payload; any identity None required
3 POST /api/license/validate At launch and periodically, on a paid plan { key } — the license key Content; the device id None (key is in the body); returns { valid, plan, periodEnd } from cache, never secrets
4 POST /api/usage/report Periodically on a paid plan, for aggregate billing { period, counts } — integer token counts per feature Content; prompts; per-request detail X-Noesa-License (rejected 401 if not a valid license)
5 GET /api/usage/free To read the free-tier meter for display Nothing beyond the device id header (it is a GET) Content; local date is display-only, never sent as the budget key X-Noesa-Device (rejected 401 if not free-tier)
6 POST /api/analytics Opt-in only — anonymous product events { event } — an event name (server truncates to 64 chars) and increments a per-UTC-day counter Content; the current handler reads/stores only the event name None (opt-in)
7 POST /api/diagnostics Opt-in only — crash / degraded reports { errorClass, code } — each truncated to 64 chars; increments a per-UTC-day counter Content; stack traces; message strings; user data None (opt-in)
8 POST /api/billing/webhook Server-to-server, from the billing provider — never from your device The provider's webhook payload → writes the license cache in KV X-Signature = HMAC-SHA256 of the raw body under BILLING_WEBHOOK_SECRET (constant-time verify)

Source for each row: worker/src/proxy.ts (handleLlm, row 1); worker/src/endpoints.ts (handleWarm, handleLicenseValidate, handleUsageReport, handleUsageFree, handleAnalytics, handleDiagnostics — rows 2–7); worker/src/webhook.ts (handleBillingWebhook, row 8).

Field whitelisting is structural. For analytics and diagnostics the server reads only the named fields and truncates each to 64 characters (String(b.event ?? "").slice(0, 64) etc. in worker/src/endpoints.ts); there is no code path that stores a free-form body. Aggregate counters are written to KV with an auto-expiry TTL of 60 * 60 * 24 * 90 seconds (90 days) — see expirationTtl in handleAnalytics/handleDiagnostics.

Metadata-only logging. For the LLM route, the only thing recorded is the metadata struct LogRecord (worker/src/log.ts): model, feature, auth kind, an 8-character id prefix, HTTP status, elapsed ms, and an optional integer token total. There is no field for a body, prompt, header value, or query string, so content cannot be logged even by accident.

Two bypass paths

Neither of these touches Noesa's proxy or any endpoint above.

  • Bring Your Own Key (BYOK). Set your own OPENROUTER_BASE_URL and OPENROUTER_API_KEY (read by desktop/app/Sources/NoesaKit/Synthesis/ProviderClient.swift). The desktop app then talks directly to the OpenAI-compatible endpoint you chose, with your key. Noesa's infrastructure sees nothing.
  • Local model / Offline. Point BYO-endpoint at a local OpenAI-compatible model (e.g. Ollama at http://localhost:11434/v1) so inference stays on your machine; or flip the per-session Offline toggle, which hard-stops every outbound LLM call and uses the on-device deterministic rollup. Either way nothing egresses off-device at all.

Verify it yourself (OS-level)

You do not have to trust this document — you can watch the sockets. With the app running (start a session and trigger a synthesis so cloud AI actually fires), you should see connections only to:

  • the Noesa proxy Worker host your app is configured to use; and
  • in BYOK mode, the endpoint host you configured instead; or
  • with BYO-endpoint pointed at a local model, only that local host (e.g. localhost:11434) — nothing off-device (with Offline on, no LLM connections at all).

Note the proxy's own onward call to the upstream provider (OpenRouter) happens server-side, on Cloudflare — not from your machine — so you will not see the provider host in your local capture unless you are in BYOK mode.

macOS

# Live per-process socket throughput (find Noesa's pid first, e.g. via Activity Monitor):
nettop -p <pid>

# Snapshot of the process's current network endpoints:
lsof -i -nP -p <pid>

For an outbound firewall with per-connection prompts and a persistent log, use Little Snitch (or the open-source LuLu): you will be asked to allow each destination host and can confirm the list matches the two/three hosts above.

Windows

  • Resource MonitorNetwork tab → filter by the Noesa process to see its live TCP connections and remote addresses.
  • Fiddler or Wireshark to inspect the actual requests/hosts (note HTTPS bodies are encrypted; you are verifying destinations, and can confirm no image data is uploaded by size).

If you see traffic to any host other than the ones listed above for AI, licensing, or telemetry, that is not this codebase — please report it.

Screenshots and pixels never egress

This is stronger than "screenshots don't leave the device": nothing off-device ever consumes a screenshot, so there is nothing to leak.

  • Perception is on-device OCR plus a deterministic idle/active tag — there is no vision model, cloud or local (docs/goals/native-ui-trust-backend-decisions.md, "Vision model already removed"; .env.example notes per-frame perception is model-free).
  • No endpoint accepts an image. The only endpoint that transmits your content is the LLM route (row 1), whose body is the derived-text chat payload. The proxy declares which model is allowed via MODEL_ALLOWLIST (worker/src/config.ts) — a text model only.
  • Screenshots are used solely for on-device OCR and local evidence replay; only derived text (signals/OCR snippets for synthesis, redacted titles/intent for attribution) ever leaves the device, and only via the opt-in cloud AI path.