Stalkrr
Privacy

Privacy

Every network connection the app can make.

Stalkrr records meetings, calls, your screen, and (optionally) your messages. The only honest privacy policy for software like this is one you can check against the code, so this document lists every network connection the app can make, what crosses it, what turns it on, and where it lives in the source. If you find any byte leaving the machine that this page doesn't account for, that's a security bug — report it per SECURITY.md.

Default posture: nothing you captured leaves this Mac. Out of the box, transcription runs on-device (vendored whisper.cpp), extraction runs on a local model (auto-managed Ollama on localhost), and the archive is a local SQLite file under ~/.stalkrr/.

Outbound connections that can carry captured data

These exist only if you explicitly select a cloud provider (an explicit choice in Preferences, never a default — the deliberate friction is the point), and they receive only what the stage needs:

What is sent Where When Code
Meeting audio (transcoded m4a) api.groq.com or api.openai.com Only when you set the transcription engine to Groq/OpenAI in Preferences (default: local Whisper). The Preferences card says "audio is uploaded" whenever this is on. app/Sources/StalkrrPipeline/CloudWhisperTranscriber.swift
Meeting audio (transcoded m4a) api.soniox.com Only when you set the transcription engine to Soniox (the card says "audio is uploaded"). The uploaded file is deleted from their storage right after transcription. app/Sources/StalkrrPipeline/SonioxTranscriber.swift
Redacted transcript text (post-PII-redaction) api.anthropic.com, api.openai.com, generativelanguage.googleapis.com, or api.groq.com Only when you set the text/vision provider to that cloud (default: local Ollama). app/Sources/StalkrrPipeline/Providers/Providers.swift, Providers/Vision.swift
Redacted transcript text (text-extraction backup) same as above Only when "If local AI fails" is set to a named provider in Settings (default Off). app/Sources/StalkrrApp/BackupExtractor.swift
Screen/camera frames (JPEG) — the whole display or a webcam still, not redacted same as above Only when you explicitly select a cloud vision provider (default: local Ollama, nothing uploaded). app/Sources/StalkrrPipeline/Providers/Vision.swift
Meeting audio (transcoded m4a) api.stalkrr.com (Stalkrr Cloud) Only when the transcription engine is Stalkrr Cloud — a paid plan you subscribe to and then choose in Preferences (the card says "audio is uploaded"). The gateway routes each language to the engine measured best for it and keeps nothing once the transcript is back — see "Stalkrr Cloud" below. app/Sources/StalkrrPipeline/CloudWhisperTranscriber.swift (engine stalkrr in app/Sources/StalkrrApp/main.swift)
Redacted transcript text (post-PII-redaction) api.stalkrr.com (Stalkrr Cloud) Only when the text provider, or "If local AI fails", is Stalkrr Cloud. Forwarded to an open-weight model at a host with no-training / no-logging terms; not kept. app/Sources/StalkrrPipeline/Providers/Providers.swift (makeStalkrrExtractor)

The redaction stage (app/Sources/StalkrrPipeline/Redact.swift) masks PII in transcript text before it is sent. It does not touch images — frames are sent as captured.

Outbound connections that never carry captured data

Purpose Where What is sent Code
Daily usage report stalkrr.com/api/pulse One JSON POST under 1 KB, at most once per UTC day: version, macOS, chip family, RAM bucket, engine/providers/toggles, activity and performance as named buckets, crash and warning counts. No identifiers, no content, no free text. The complete accepted field list is served at GET https://stalkrr.com/api/pulse. Toggle: Settings → "Send one anonymous report a day" (on by default; off counts nothing). app/Sources/StalkrrCore/UsageReport.swift, app/Sources/StalkrrApp/UsageReporter.swift, site/functions/api/pulse.js
License refresh (Pro / Cloud keys only) api.stalkrr.com Only when a Pro or Cloud key is present — never on a free install. Once a day for subscription keys, once a week for lifetime keys (a revocation check): the key itself (a signed blob that carries the buyer's name and email), a random per-install id, the app version, and the languages set in Transcription — so a subscription key can be re-minted with a later expiry, and a refunded key revoked. Removing the key in Settings → Plan ends the call. app/Sources/StalkrrApp/LicenseManager.swift
Update check github.com (StalkrrOS/stalkrr-releases) A plain GET for appcast.json; downloads the signed update zip. No identifiers. Always on: updates install themselves and there is no switch in the UI (defaults write com.stalkrr.app autoUpdate -bool false disables it). app/Sources/StalkrrApp/UpdateChecker.swift
Whisper model download github.com (StalkrrOS/stalkrr-releases, self-hosted weights) or huggingface.co (the Japanese/Chinese specialists) A GET for the model file, once per model. app/Sources/StalkrrPipeline/WhisperTranscriber.swift, LanguageModelRegistry.swift
Speaker diarization models github.com (StalkrrOS/stalkrr-releases, models-2) One GET for a 19 MB zip, once; verified against a pinned sha256 before anything is unpacked. Toggle: Settings → Speaker diarization. app/Sources/StalkrrPipeline/SpeakerModel.swift
Local-AI runtime download github.com (ollama releases) + the Ollama model registry GETs for the pinned Ollama build and the local model, once. app/Sources/StalkrrApp/OllamaManager.swift
Self-hosted inference (optional) your own localhost/LAN URLs for Ollama or vLLM Redacted text, to servers you run. endpoint prefs in app/Sources/StalkrrApp/PreferencesWindow.swift

Things that are local by construction

The daily usage report

Stalkrr is a one-person project with no accounts, no crash reporter and no support queue, so until 0.12.0 nobody knew whether an update installed, whether transcription was fast enough on an M1, or whether anything crashed. The daily usage report is the smallest thing that answers those questions. It is on by default — Settings → Send one anonymous report a day turns it off, and off means nothing is counted — and this is all of it:

{
  "archive_gb": "1-5",
  "capture_warnings": 1,
  "chip": "m4",
  "crashes": 0,
  "days_covered": 1,
  "engine": "whisper",
  "extract_cloud": 0,
  "extract_failed": 1,
  "extract_local_ok": 5,
  "extract_wall": "15-60",
  "imported": "0",
  "langs": "en,he",
  "local_model_gb": "5-10",
  "mem_pressure": 2,
  "minutes": "121-480",
  "os": "15.6",
  "ram_gb": "32",
  "recordings": "3-5",
  "rtf": "5-10",
  "text_provider": "ollama",
  "thermal": "fair",
  "toggles": "coreml,diarize,login,mcp_destructive,whatsapp",
  "v": 1,
  "version": "0.12.0",
  "vision_provider": "ollama",
  "whisper_model": "auto"
}

That is a whole report. There is no other one.

No personal data, ever — what makes that true, in the order it stops being a slogan:

  1. No identifier. No install ID, no device ID, no serial, no account, no cookie, no hash of anything. Nothing in the report is derived from your machine's identity, so two reports cannot be joined — not by us, not by anyone reading the dataset.
  2. No content. No titles, no transcripts, no file names, no app names, no people, no search terms. Counts, ranges and speeds only.
  3. No free text. Every string is a member of a closed list written into the code (app/Sources/StalkrrCore/UsageReport.swift), a fixed number pattern (0.12.0, 15.6), or a language code Whisper can produce. There is no field a name could be typed into, deliberately; anything that is not on a list becomes other.
  4. The app can only send this. The self-test (swift run stalkrr-selftest) asserts the report's exact key set and feeds it hostile inputs — a title, an email address, a file path, an API key — and checks none of it survives. Adding a field without updating that list fails the build.
  5. The server can only store this. site/functions/api/pulse.js builds the stored row by walking its own schema, never the request. An unknown key, a wrong type, a number out of range or a string not on its list is rejected and nothing is written. The schema it enforces is the schema it publishes at GET https://stalkrr.com/api/pulse — the same file serves both, so the promise and the enforcement cannot drift apart.
  6. Nothing about the connection is kept. Cloudflare terminates the connection and so sees your IP, exactly as it does when you load any page on any site behind it. The Function never reads it. What it reads is the two-letter country Cloudflare derives at the edge, and that is the only thing the server adds to the report.
  7. The log line before every send. The exact bytes are written to ~/.stalkrr/stalkrr.log (usage: {…}) before they leave; Settings → "Show what would be sent" shows the same document. The server's own log is a count — pulse ok — never a body and never an address.
  8. It is thrown away. Rows live in Cloudflare Workers Analytics Engine for three months and are then deleted. There is no backup.

The honest caveat. Any set of buckets is, in combination, a rough description of a configuration — an M4 with 32 GB running Whisper in English and Hebrew is not one Mac in the world. The buckets are coarse for exactly that reason: chip family and not chip model, six memory buckets, at most three languages, ranges and not counts. What is not there — and it is the part that matters — is anything that makes two reports from the same Mac more linkable than two reports from any two Macs set up alike. We cannot count you twice, and we cannot count you once.

What it is used for. Deciding what to build and what to fix: is anyone on an old version, is transcription fast enough on the machines people actually have, which languages need a model, did that release crash. The numbers are read with python3 site/stats.py; docs/ANALYTICS.md is the map of everything counted.

Stalkrr Cloud (the paid hosted processing)

Stalkrr Cloud is optional and off unless you subscribe and then choose it in Preferences. When it is on:

How to audit this yourself

  1. Enumerate every URL in the code: bash grep -rhoE "https://[a-zA-Z0-9./_-]+" app/Sources --include="*.swift" | sort -u Compare against the tables above. (URLSession is the only HTTP client; there are no third-party networking dependencies to hide in — the dependency list is app/Package.swift, and the single vendored library is whisper.cpp.)
  2. Build it yourself (cd app && swift build && ./scripts/build-app.sh) and run your own bundle — see app/README.md.
  3. Watch it live with Little Snitch/LuLu: with default settings you should see the update check, one-time model downloads, and one small POST to stalkrr.com a day — all three listed above, and nothing else. Point a proxy at that POST if you want to read it; it is plain JSON and it says what it is.

The website

stalkrr.com is static files on Cloudflare Pages plus two small functions: /dl, which resolves the current release download, and /api/pulse, which receives the daily usage report described above. Those are the only times the app talks to this domain — never for updates (those come from GitHub), never for models, never for anything you captured. The site itself uses Cloudflare Web Analytics, which records page views (path, referrer, country, browser family) without cookies, without any identifier, and without storing anything on your device, so there is no consent banner because there is nothing to consent to. Unsampled data is kept for 7 days, aggregates for 6 months. The Download button leads to /download, so that page's views are the download count. Links we post elsewhere may carry a path such as /from/x, which serves the same home page, so the path tells us which place brought you — that, and the referrer your browser sends anyway, is the whole of the "tracking": no cookies, no identifiers, no query strings, no pixels. The pages load their typefaces from Google Fonts and nothing else from anywhere else; the site's Content-Security-Policy header lists every host a page is allowed to talk to, and the build that produces it is site/build.py.

Privacy policy

Effective September 2026. The sections above are the technical description of what the app sends; this section is the policy that goes with it.

Who is responsible. Stalkrr is an independent product operated from Israel ("we"). Contact for anything in this document, including requests about your data: info@stalkrr.com.

The app on your Mac. Recordings, transcripts, extracted memory, the people directory and voice data are processed and stored on your Mac only, under ~/.stalkrr/. We never receive them. You are the controller of what you record; the in-app Terms put the right to record the people you capture on you.

What we collect, and why.

Who else sees data. Only the processors named here: Cloudflare (the website, the gateway and its database), Paddle (payments), Resend (the key email), and — for Cloud only — the transcription and extraction engines listed above (Microsoft Azure, Soniox, the GPU host of Stalkrr's own Whisper specialists, and the open-weight model host). We sell nothing to anyone and run no advertising.

Where. These processors run in the United States and the European Union; Cloudflare processes at the edge nearest to you. Transfers rely on the processors' standard contractual clauses. Nothing about you is hosted in China.

Your rights. You can ask what we hold about you, have it corrected or deleted, and object to processing — email info@stalkrr.com; we answer within 30 days. Deleting a license record ends the plan. Everything the app holds is yours to erase from the app itself (Timeline → Delete, or Erase all captured data). If you are in the EU/EEA or the UK you can also complain to your data-protection authority.

Children. Stalkrr is for people aged 18 and over.

Changes. We update this document when the software changes what it sends; the version of PRIVACY.md in the repository is the record of what applied when. Material changes to the policy are announced in the app.