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. 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 } is 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 | None | 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_URLandOPENROUTER_API_KEY(read bydesktop/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), with 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 Monitor → Network 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, with
no vision model, cloud or local
(
docs/goals/native-ui-trust-backend-decisions.md, "Vision model already removed";.env.examplenotes 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.