// Security
What it sends, and what it does not.
This is the page to forward to whoever signs off on software. It is written to be checked: every host, port, file and permission below is read out of the app release behind the download button, and each one carries the line of source it came from. It runs offline. It is not silent — two hosts, both named here.
What it reaches
2 hosts. One code path in the whole app opens a connection to a machine that is not this one, and it is the licence check.
| Host | What for | Read from |
|---|---|---|
| live.dodopayments.comLicence check | Confirms this Mac still holds the seat the licence was activated on. It runs at launch, and only on a Mac that has already been licensed — a free or trial copy never reaches it. | Sources/VizallKit/Control/DodoValidator.swift:16Sources/VizallKit/Control/DodoValidator.swift:48Sources/VizallKit/Control/LicenseRevalidation.swift:27Sources/VizallKit/App/AppDelegate.swift:1661 |
| github.comUpdate check | Fetches a small XML feed describing the newest signed build. The updater does this on its own schedule; an update that is not signed with the key inside the app is refused. | Resources/Info.plist:42Resources/Info.plist:43Sources/VizallKit/App/AppDelegate.swift:816 |
The licence request carries 3 fields
and no more: license_key, name, license_key_instance_id. The seat is labelled with the Mac's own name so an administrator can tell one machine
from another in the vendor's dashboard. There is no API key and no secret in the binary —
the endpoints are public ones. It gives up after
10 seconds.
One more host appears in the app and is not a call it makes: checkout.dodopayments.com is the buy link Sources/VizallKit/Control/DodoValidator.swift:22, handed to the default browser. The app never fetches it. Sources/VizallKit/App/AppDelegate.swift:1665
What it opens on the Mac
Reaching out is one question; listening is the other. 3 sockets, and the last column is the one that matters. On a fresh install these start on: OSC state out — and everything in that list is addressed to loopback, so it never leaves the Mac.
| Endpoint | Reachable from | What for | Read from | Ships |
|---|---|---|---|---|
| UDP 127.0.0.1:9001OSC state out | This Mac only | Publishes what the visuals are doing, thirty times a second, so a lighting desk or a bridge on the same Mac can follow. It is addressed to loopback, so nothing leaves the machine unless an operator changes where it points. | Sources/VizallKit/IO/OSCBroadcaster.swift:9Sources/VizallKit/App/Controls.swift:216 | On |
| UDP 0.0.0.0:9000OSC control in | The local network | Lets a tablet, a desk or a setlist controller drive the visuals over the LAN. Unauthenticated by design, which is why it ships off and stays off until an operator turns it on. | Sources/VizallKit/IO/OSCController.swift:15Sources/VizallKit/App/Controls.swift:219 | Off |
| TCP 127.0.0.1:9100Local control endpoint | This Mac only | The JSON endpoint an assistant on the same Mac drives. The socket is bound to loopback rather than filtered per connection, so it is not reachable from another machine at all. | Sources/VizallKit/Control/LocalControlServer.swift:29Sources/VizallKit/App/Controls.swift:213 | Off |
One of those can be reached from another machine, and it ships off: OSC control in. It is unauthenticated when it is on — that is what an OSC control port is — so treat it the way you would treat any other desk on the show network.
How that list is kept honest
Every file in the app's source that so much as names a socket API, and what each one is. 8 files. The sweep is part of the build for this page: a file that starts opening a socket fails it until somebody has written down what the socket does.
- egress Sources/VizallKit/Control/DodoValidator.swift The licence check. The only code in the app that opens a connection to a host off this machine.
- loopback Sources/VizallKit/Control/LocalControlServer.swift The opt-in control endpoint an assistant drives. Bound to 127.0.0.1, and non-loopback connections are dropped.
- loopback Sources/VizallKit/IO/OSCBroadcaster.swift Broadcasts live state as OSC. Its destination defaults to loopback, so nothing leaves the Mac unless an operator points it somewhere.
- unused Sources/VizallKit/IO/OSCClient.swift An OSC client that can chase a foreign service. Nothing in the shipping app constructs one.
- lan Sources/VizallKit/IO/OSCController.swift The OSC control port a lighting desk or a tablet drives. A LAN listener, and it ships off.
- none Sources/VizallKit/IO/OSCDiscovery.swift A pure list model. It names a browser API in one comment and opens nothing.
- helper Sources/vizall-mcp/HTTPListener.swift The bundled MCP command-line helper, not the app. Loopback unless it is started with an explicit LAN flag.
- helper Sources/vizall-mcp/main.swift The same helper, talking to the loopback control endpoint above. It reaches no host.
Marked none or unused: 2 of those files open nothing at all — one names a discovery API in a comment, the rest are code the shipping app never constructs. Marked helper: 2 belong to the command-line helper that ships beside the app for an assistant to talk to, not to the app itself.
The app links 30 modules. All but 2 resolve out of Apple's own SDK; the rest are declared dependencies, and the package manifest lists every one of them: github.com/sparkle-project/Sparkle from 2.5.0 Package.swift:9, github.com/PostHog/posthog-ios pinned to 3.69.8 Package.swift:13. Those are PostHog, an analytics SDK that is compiled in and switched off, and Sparkle, the updater. There is no crash-reporting service and no ad or attribution SDK anywhere in it.
The analytics SDK is worth being exact about, because "we link it but it is off" is the kind of sentence that deserves a citation rather than your trust. Two separate things have to change before it can produce a single event. There is a master switch that every reporting path in the app checks before it does anything, and it is false Sources/VizallKit/Telemetry/Telemetry.swift:73. And there is a project key, which ships empty Sources/VizallKit/Telemetry/Telemetry.swift:66 — the SDK refuses to start without one, so flipping the switch on its own would still send nothing. Until both change, the app opens no connection to us.i.posthog.com Sources/VizallKit/Telemetry/Telemetry.swift:56 — which is something you can watch for and fail to see, rather than something we are asking you to believe. If that ever changes, this page and the privacy policy change first.
What it asks macOS for
3 prompts, each shown once, each with the sentence the app puts in front of the person clicking. Nothing here is asked for silently.
- AudioCapture
- “Luxaudica listens to your system audio to generate live visuals.” Resources/Info.plist:32
- Microphone
- “Luxaudica uses your audio input to generate live visuals.” Resources/Info.plist:35
- SpeechRecognition
- “Luxaudica transcribes the audio on your Mac, on-device, to show live captions. Audio never leaves your machine.” Resources/Info.plist:38
There is no screen-recording prompt, because the app draws a picture rather than reading
one. The signed bundle requests exactly 1 entitlement —
com.apple.security.device.audio-input — and the file
contains nothing else.
It is not App-Sandboxed. That is worth stating plainly rather than leaving to be inferred from a badge: the App Sandbox is a requirement of the Mac App Store, and this is a directly distributed app. What replaces it here is the hardened runtime, a Developer ID signature and Apple's notarization — the checks that path actually uses. Scripts/release.sh:32
How the download is signed, and how an update arrives
- Hardened runtime. The bundle is signed with the runtime hardening option set. Scripts/release.sh:174
- Developer ID. Signed with a Developer ID Application certificate, with a secure timestamp. Scripts/release.sh:73
- Notarized. Submitted to Apple and waited on; a rejected build stops the release. Scripts/release.sh:96
- Stapled. The notarization ticket is attached to the app and to the disk image, so a Mac with no network can still verify it. Scripts/release.sh:202
- Updates are signed separately. Each release is EdDSA-signed Scripts/release.sh:231 and the public key travels inside the app; a feed or a build that does not match is refused. Resources/Info.plist:44
The update check runs on its own schedule as well as on demand, and it asks before it installs. Resources/Info.plist:46 If you would rather it never looked, block the update host: the check fails and the app carries on.
What it stores, and where
| Location | What | Read from |
|---|---|---|
| ~/Library/Application Support/VizallSettings, looks and licence | Plain JSON, written and read only by the app. cues.json · factoryRetune.json · factorySeed.json · license.json · logo.json · midi.json · notePresets.json · notes.json · playback.json · presets.json · programBindings.json · segmentVocabulary.json · session.json · source.json · tour.seen · userScenes.json · welcome.seen | Sources/VizallKit/App/PresetStore.swift:298 |
| login KeychainLicence anchor | One generic-password item holding the licence record — kept in the Keychain because that is where macOS keeps credentials, rather than in a plain file. | Sources/VizallKit/Control/TrialAnchor.swift:99 |
| ~/Library/Logs/Luxaudica/last-crash.logCrash breadcrumb | A backtrace after a hard crash, written for the operator to retrieve. Nothing uploads it. | Sources/VizallKit/App/CrashReporter.swift:16 |
| ~/Movies/Vizall · ~/Pictures/VizallRecordings and stills | Only what an operator asked the app to record or capture. | Sources/VizallKit/App/AppDelegate.swift:34 |
A recording is a file an operator asked for, and it carries the audio that was playing — that is what a recording is. Nothing in that list is transmitted, and the crash breadcrumb is written for somebody to fetch by hand rather than uploaded.
What this website sends
Everything above is about the app. This page reached you over a network too, and a reader who has just been told to verify hosts against a traffic capture deserves the same list for the site itself: four parties. The citations in this section point into the website’s own source rather than the app’s — and your browser’s network tab is the faster check.
PostHog — the site’s analytics, and it is on; the status of the app’s copy of the same SDK is generated above, from the app’s own source. On the wire it is two hosts. The script itself arrives from us-assets.i.posthog.com — a name that appears nowhere in this repo’s source, because the loader builds it at runtime from the ingest host, and the first PostHog line in any network tab. src/layouts/Base.astro:205 Everything after that reports to us.i.posthog.com: which pages get read, what gets clicked, and — when one of these pages breaks in your browser — the JavaScript error it broke with. src/layouts/Base.astro:206 What each of those events carries, and the identifier that makes your second visit count as the same person, is spelled out in privacy.
Cloudflare — the host. Every request for a page here reaches their edge, which is what serving a website is. A second, deliberately small Cloudflare script adds a pageview count and no more — the tally that survives content blockers, kept as a cross-check on the tool above. The script is Cloudflare’s, served from static.cloudflareinsights.com src/layouts/Base.astro:230 — a hostname check in front of it means it runs on luxaudica.com and nowhere else src/layouts/Base.astro:227 — and the count it takes posts back to /cdn-cgi/rum on this site’s own origin, a path Cloudflare’s edge intercepts.
Google Fonts — the type this page is set in. Every page here fetches a stylesheet from fonts.googleapis.com src/layouts/Base.astro:165 and the CSS that comes back pulls the font files from fonts.gstatic.com — a host our own source names only as a preconnect hint, because the request for the faces is written in Google’s stylesheet, not ours. src/layouts/Base.astro:163 Unlike the two scripts above, nothing gates it — no analytics switch, no hostname check; the request rides every load of every page. What Google receives is what any fetched host receives: your IP address, your browser’s User-Agent, and which pages here asked for type.
Dodo Payments — where the purchase happens, and where a licence is managed. This site makes no call to them; it hands your browser to checkout.dodopayments.com when you choose to buy src/config/site.ts:668 and to customer.dodopayments.com, the licence portal, when you recover a key. src/config/site.ts:557 That checkout is the one place in Luxaudica’s licensing life where an email address exists at all: no page on this site asks for one, and the address you type there stays with Dodo as the customer record your licence key is issued against. Links handed over, never fetched by a page here — both appear in your network tab only after you click.
What this is not
- It is not a certificate.
- We hold no audit, no attestation and no compliance mark, and there is nothing here to put a badge on. What this page offers instead is the mechanism, in enough detail to check — which is the thing a badge is a proxy for.
- It is not a claim that the app is offline.
- It runs offline, which is a different sentence. Two hosts are named above, both with the line of source that reaches them, because a list that quietly omitted one would be found by the first person who looked.
- It is not the privacy policy.
- This page is about the app on the Mac, plus one section on the website that serves it — and that section only says where things go. What is collected and kept, and what a purchase involves, is a separate document — read privacy for that, and terms for the licence.
- It is not a promise about your network.
- The OSC control port is unauthenticated when an operator turns it on, and a show network is a network like any other. Segment it the way you would for a lighting desk.
The questions IT asks
- What do we need to allow through the firewall?
- Two hosts, and nothing else: live.dodopayments.com and github.com. Block both and the app still runs — it paints, it records, it recalls looks. A licensed Mac that cannot reach the licence host keeps its licence; an unreachable vendor is treated as a network problem, not as an answer.
- Does it need an account, a sign-in or a directory integration?
- No. There is no account to create and nothing to connect to a directory. A licence is a key an operator pastes in once; until that happens the app makes no licence request at all.
- Is the audio sent anywhere?
- No. The whole of what the app ever puts in a request body is 3 fields —
license_key,name,license_key_instance_id— and none of them is audio. Captions are transcribed on the Mac; the permission prompt macOS shows says so in the app's own words: “Luxaudica transcribes the audio on your Mac, on-device, to show live captions. Audio never leaves your machine.” - Is it in the Mac App Store, and is it sandboxed?
- Neither. It is distributed directly, signed with a Developer ID certificate, notarized by Apple and stapled, which is the direct-distribution path. The App Sandbox belongs to the other path, and the app does not request it — the entitlements file asks for one thing,
com.apple.security.device.audio-input, and stops. - How do updates arrive, and can we stop them?
- The app checks a signed feed on its own schedule and offers the update; a build whose signature does not match the public key inside the app is refused. Block the update host and the check simply fails — nothing else changes. There is no silent install.
- What does it leave on the machine?
- ~/Library/Application Support/Vizall, login Keychain, ~/Library/Logs/Luxaudica/last-crash.log, ~/Movies/Vizall — and nothing outside those. Everything there is written by the app for the app; our privacy page covers what the website does, which is a separate question.
- Can we verify any of this ourselves?
- Every line on this page carries the file and line number in the app source it was read from, at the release behind the download button. The figures are generated from that release and a spec re-derives them from it on every run, so a change in the app arrives here as a failed build rather than as a paragraph nobody re-read.
Read next: flash safety · what a licence covers · privacy · the assistant and the local endpoint