For security reviewers. Companion to PRIVACY. One page, deliberately.
A Windows tray application (.NET 10, WinForms) giving employees AI writing/reading assistance via global hotkeys in any application. There is no RefineLoop account and no store of your content. The only server-side state that exists at all is on the trial relay, in Cloudflare KV, and every key of it expires: the day a token was first seen (60-day TTL), its request count for today (2 days), a global monthly total across all tokens (40 days), and two keys recording whether the relay's own six-hourly health check is currently failing (40 and 2 days). No text, no transcript, nothing identifying a person. Anonymous usage telemetry exists but is off by default and carries no content (see the table below).
Where text goes is decided by one question (whose AI key is in use), and there are exactly two answers:
1. Your own endpoint (any configured key). Text goes straight from the desktop to the endpoint you chose: your Azure OpenAI deployment, your OpenAI/Gemini/Claude/Groq account, an OpenAI-compatible server of your own, or Ollama on the machine itself, which leaves the machine not at all. No RefineLoop server is in the path. We cannot see the text, because it never reaches us. This is the configuration an organization deploys, and it can be enforced: pinning the provider and endpoint in the policy file locks those fields so a user cannot redirect company text anywhere else (see Controls below).
2. The 14-day trial (no key at all). A fresh install works immediately with nothing to sign up for, and in that mode (and only that mode) text is forwarded through a RefineLoop-operated Cloudflare Worker to an upstream AI provider under our key. The relay stores no text and no transcript; the install is identified only by a random token that identifies a trial, never a person. It is not a general proxy: it forces its own model server-side, accepts only chat messages, refuses tools and images, caps input at 32 KB and does not forward client headers. Entering any key of your own moves you permanently to case 1, and the policy file can disable this mode outright for a fleet.
| Destination | When | What is sent |
|---|---|---|
| Your configured AI endpoint: case 1, the deployed configuration | user presses an action hotkey | the selected text / transcript, after the local credential scan. Direct from the desktop; no RefineLoop server involved |
| RefineLoop trial relay (Cloudflare Worker): case 2, and mutually exclusive with the row above | only while the keyless 14-day trial is in use; ends the moment any key of your own is saved, and can be disabled by policy | the selected text, forwarded to an upstream AI provider under RefineLoop's key, plus a random per-install trial token. Nothing is stored; no client headers are forwarded |
| Your transcription endpoint | speech features | audio; can be a fully on-device whisper server (recommended config) |
| Static update manifest (vendor host) | app start, then on a 24-hour timer while it runs | nothing: a GET for version.json (version + revoked-license hashes); fails silently offline |
| Openverse image API | off by default; opt-in | a 2–3-word search term only |
| Anonymous usage ping | off by default; opt-in | once per day: a randomly generated per-install id, app version, trial/licensed state, trial day, OS version. No content, no machine name, no licence key |
Nothing else is contacted while the app is simply running. Three further destinations exist, and none is reached without an explicit click: ollama.com and python.org for the one-click local-AI and local-speech installers, and the vendor host again for the optional preset gallery (presets.json, a public file, no identifying information). The bundled speech servers download their own Python packages and model files on first use, by the same consent. None of these carries user text.
OCR, drills, flashcards, spaced repetition, the mistake report and the knowledge bundle are computed entirely on-device and reach no network at all.
ask | redact | block | off.audit_log = on): one line per outbound request (timestamp, feature, endpoint host, size, findings, outcome). Never message content.policy.ini, admin-writable locations only: HKLM pointer or Program Files; see policy.sample.ini): disable features org-wide (recording, replay, dictation, pronunciation, OCR, content logging, the selection widget, scenarios) and lock the endpoint so users cannot redirect company text to personal AI accounts. Policy wins over user config at the single config-load point.Correction history and vocabulary as JSONL/Markdown in the user's data folder (content logging can be policy-disabled), config INI files (the API key is in config.ini: deploy it via the policy mechanism if central control is preferred), spaced-repetition state, meeting transcripts if recording is enabled. Opt-in (default off, capture_archive): a local archive of every text a hotkey captures under knowledge\captures. It is text only, never audio, and captures the secret scanner flags are never archived at all.
RegisterHotKey global hotkeys · clipboard read/write around each correction · SendInput keystroke injection (paste) · WASAPI loopback + microphone capture (only while the tray indicator shows) · screen capture for the OCR snip · an HttpListener bound to http://127.0.0.1:<port> (the local UI; the first free port in 51763–51792, loopback only) · child python.exe processes for the optional on-device speech servers, which run pip install and download their model files (~340 MB for the voice) on first use · schtasks.exe process creation when "Start with Windows" is enabled · one HKCU key for trial state, and a second one only in the autostart fallback described below.
One further behaviour is worth naming rather than leaving for an alert to find: with the optional selection widget on, the app installs a WH_MOUSE_LL low-level mouse hook (Win/SelectionWatcher.cs), the same API class as a keylogger, and deliberately not one: there is no keyboard hook in the binary at all, structurally rather than by promise. It is off by default (selection_widget), policy-removable, runs in-process, and only decides where to draw a button.
Persistence is "Start with Windows" and nothing else: a Task Scheduler logon task named RefineLoop (created by shelling out to schtasks with generated XML, LeastPrivilege), with an HKCU …\CurrentVersion\Run value as the fallback when task creation is refused. It is a scheduled task rather than a Startup shortcut because RegisterHotKey is first-come, first-served, and the installer drops no Startup-folder shortcut at all. No elevation, no driver, no process injection. Binaries are (to be) Authenticode-signed; hashes available on request for allowlisting.
.NET binaries are decompilable; license enforcement is honest-user-grade. The update manifest host is a single static file the vendor controls. The app is currently distributed by a sole developer. Escrow and support terms per contract.
Contact: support@refineloop.app · Source of this document: SECURITY.md in the product repository.