The four processes
This could have been one program. It is four roles instead, and the split is a trust boundary, not a modularity preference: each role is missing a capability the others have, so a bug in one cannot become the failure you actually care about.
collectors ---> gate ---> store ---> uploader ---> the platform
(no network) (single writer) (on disk) (only credential |
^ holder) |
| v
indicator <--- console <--------------------------- the person
(must prove
visibility)
| Role | Has | Deliberately lacks |
|---|---|---|
| Collectors (one per signal) | OS capture APIs | any network path — an outbound firewall rule per executable, applied before the process starts |
The gate (here-gated) |
the only write handle to the store | any capture code, and any credential |
The uploader (here-uploaderd) |
the platform credential | any capture code, and any write path into the store's content |
| The console + indicator | the person's attention | any direct store access — they talk to the gate over a control channel |
Why each boundary is where it is
A collector cannot leak, because it has no network. This is the one claim that does not depend on the collector's code being correct. The supervisor applies a per-executable outbound Block rule and then starts the process; a collector it could not block is not started at all. That is a real enforcement point rather than a code review, and it is also the claim that gets weaker off Windows — see Porting and platform support.
One writer means one pipeline. Every gate stage lives in one process on one path, so there is no second way into the store that could skip L2. A collector that wanted to write a secret to disk would have to get it past the gate, because it has no file handle of its own.
The credential holder captures nothing. The uploader can talk to the platform but has no capture code in it. So a compromise of the thing that holds the token yields the token and the queue, not a live tap. And the reverse: a compromise of a collector yields pixels with nowhere to send them.
The indicator is a precondition, not a UI. The gate refuses content capture unless the indicator has proved itself visible and recent. That makes the pill part of the enforcement path — which is why it is the one window whose geometry the gate verifies, and why a frozen one stops capture rather than being ignored.
Consequences worth knowing before you change anything
- The pill is excluded from screen capture (
WDA_EXCLUDEFROMCAPTUREon Windows,sharingType = .noneon macOS), so it never appears in the screenshots it is announcing. A useful side effect: the pill is a hole in every screenshot, including any you take to check what it looks like. Render it to a bitmap instead — there is a harness for that. - The collectors exit rather than linger. They used to retry a missing gate forever, which left orphaned capture processes holding their own binaries and broke the build twice. Now they wait 45 seconds at startup and 4 seconds after losing an established gate, then exit.
- No console windows anywhere. Every spawn uses
CREATE_NO_WINDOWwith output redirected tologs/<role>.log. Five black terminal windows appearing on start is both useless and an excellent impression of malware. - The browser collector is launched by the browser, not by the supervisor, because it is a native-messaging host. The supervisor still creates its deny rule without starting it.
Turning it off
One control closes the session, and every process is gone within seconds — measured at nine
processes to zero in about three seconds. The session's end reason names the control that was
used (stopped_by_hotkey, stopped_by_console, and so on), because a stop that leaves no
trace is indistinguishable from a crash, and a crash is indistinguishable from tampering.
The pill's own off button asks twice: it arms for four seconds and shows SURE?. The keyboard
chord does not ask, because an armed state on a pill that might be covered is invisible.
here-telemetry
How it works
Working on it
Taking it further
In the repo: README.md for the
invariants, HANDOFF.md for
current state.