1 The seven signals
mikebrandon edited this page 2026-09-04 11:52:55 +00:00

The seven signals

Each signal is an independent process with its own manifest, its own grant, and its own event type. Nothing is captured until the person switches that signal on.

The "does" and "does not" columns below are quoted from the manifests themselves (collectors/*/collector.manifest.json), not paraphrased. The same strings are what the app shows the person before they grant anything, which is the point of keeping them in the manifest rather than in a UI file: the promise and the explanation are the same string.

Signal Does Does not
Window activity (focus) records which app and window you are working in, and for how long read the contents of those windows
Typing rhythm (input.metrics) counts keystrokes, clicks and scrolling, and how fast you type record which keys you press or what you type
Screenshots (frames) takes a picture of the one window you are working in when its contents change capture your whole screen, other windows, or apps you have denied
Terminal commands (shell) records the commands you run in a terminal, and whether they succeeded record command output, built-ins like cd, or a command containing a detected secret
Typed text (text.commit) records text you finish typing into a field in an allowed app record key by key, anything in a password field, or the text of a long document
Clipboard (clipboard) records that you copied something, its size, and a one-way fingerprint store what you copied
Websites (browser) records which website you are on, as a host name like github.com record the page address, page contents, what you type into it, or anything from a private window

How the awkward three actually work

Four of these are straightforward. Three needed a design decision to exist at all without being a keylogger, and those decisions are the interesting part.

Terminal commands observes process creation, not your shell

It watches for new processes and reads the command line from the process itself. It never reads shell history — the manifest forbids it, and history would hand over commands you ran before this was installed and before you consented.

The consequences are stated rather than hidden: it cannot see built-ins (cd, export, anything the shell handles without spawning), and it attributes the command to the shell process, not to whichever window happened to be in front.

Typed text samples a field at a boundary, and there is no keystroke buffer

It reads the focused field's value at a boundary — when focus leaves the field, or when the field empties while still focused, which is what "you pressed send" looks like from outside. Nothing accumulates key events anywhere; there is no buffer to leak, subpoena, or accidentally log.

Password fields are never sampled, and that check fails closed. Anything over about two thousand characters reports its length only, because a long document is not a "typed text" event, it is the document.

Clipboard keeps a fingerprint, and the fingerprint is content

It polls a sequence number to notice a copy happened, then stores the format, the length, and a keyed HMAC. Keyed, not a plain hash — an unkeyed hash of a short string is a dictionary attack away from the string.

And the fingerprint is marked as content, which matters more than it sounds: without that, suppression would strip the text but keep the hash, and a denied app's copies would stay linkable to an allowed app's. A test caught that it originally was not marked.

Timing, and why some signals go quiet

input.metrics reports on a 15-second window. The others report on events. The gate's staleness window for deciding a collector is dead is 45 seconds — three of those windows — because an idle machine produces no events, and an event-driven heartbeat made a perfectly healthy collector look dead.

What state each signal can be in

The console shows one row per signal, and it distinguishes cases that all look like "off":

State Means
enabled_active granted, running, and producing
enabled_idle granted and running; nothing has happened
denied_no_grant the person has not switched it on
not_implemented declared in the contracts, no shipping code — "not built", not "broken"
collector_not_running built and granted, but the process is not up — "not connected"
refused this machine cannot honour it; the reason names the setting to change
error it tried and failed

Telling not_implemented from collector_not_running was a real bug: the gate reused one state for both and the console called everything "NOT BUILT", including signals that were merely paused. Two different problems that need two different actions from the person should not share a word.

Platform support

All seven have working bodies on both Windows and macOS. See Porting and platform support for what each OS can and cannot honour, and which promises get weaker where.