How a byte becomes a record
Five gates, in this order, all of them before the write. That last part is the whole design: a secret that reaches disk and is scrubbed before upload has already been written to a file a backup client can read. So the pipeline runs at ingest, not at egress.
collector -> admission -> L0 scope -> L1 structural -> hold -> L2 secrets -> L3 pixels -> store
(declared?) (in scope?) (safe to look?) (visible?) (in the text?) (in the image?)
Admission — is this event even a thing?
The gate joins the incoming event type against the generated registry and the grants table.
An event type nobody declared in contracts/ is refused over the wire, so a buggy or tampered
collector cannot invent a new kind of data to send.
A collector also cannot send a signal the person has not granted: it learns its own grant from the gate's reply to a heartbeat. That is why the screenshot collector takes no pixels at all until screenshots are switched on, rather than capturing and letting the gate discard the result. Every collector must read that reply, or the pipe desynchronises.
L0 — scope
Is the app or site in scope? Two modes live in the settings table: deny_by_default (the
default) and allow_by_default. Deny rules beat the blanket allow in both. An absent
setting means deny, so an old store cannot widen its own scope by being upgraded. Switching
modes is itself written to the gate log as a decision.
One subtlety that was a live bug: collectors report the full image path
(C:\Windows\System32\cmd.exe), and rules are written as file names (cmd.exe). The comparison
handles both. A rule containing a path separator means that one path only.
L1 — structural suppression
Cheap, certain refusals that need no content inspection: a password or other secure field has focus, capture is paused, or the window is one the policy never looks inside. This gate is structural-first on purpose — the reliable way not to capture a secret is not to read the field, rather than to read it and then decide.
The hold — no verified indicator, no content capture
Before any content is written, the gate asks whether the indicator has proved itself visible and recent. Both halves matter: a hung indicator that reported "visible" once and then froze used to keep content flowing forever, so the check requires a report inside the last five seconds. If the indicator cannot prove itself, the gate holds and records a gap — an observable absence, not silence.
L2 — secrets in text
Pattern detection over anything textual, before it is stored: tokens, keys, connection strings, private keys. Measured against a corpus that includes real command lines from a working machine: 0% leak, 1.9% over-redaction against a 3% bar. The one false positive is a folder name that genuinely reads as password-like. It was left alone deliberately — loosening a password detector to tidy up a path is the wrong trade.
L3 — pixels
Redaction inside captured images: secure-field rectangles and anything else the policy marks. When a frame is downscaled to fit the transport, redaction rectangles are scaled outward, so no redaction ever shrinks under rounding.
Then it is written, and what it is written as
Every row carries a gate_state:
gate_state |
Meaning |
|---|---|
pass |
stored as captured |
redacted |
stored, with something removed, and the row says so |
suppressed |
timing only — the event happened, the content did not survive |
Content fields are marked x-content in the schemas, and suppression strips exactly those
fields. This is what makes a denied app absent rather than masked: no pixels are captured
for it at all, because capture is per-window rather than per-screen, and its row keeps only
timing.
Getting that marking right is fiddly and worth checking when you add a field. The clipboard's keyed fingerprint was once not marked as content, which meant a denied app's copies stayed linkable to an allowed app's — a hash is not a safe thing to keep just because it is not the text.
Payloads and blobs are then encrypted with AES-256-GCM under a hardware-wrapped key. The
index is deliberately not encrypted, and every document says so in the same words: timestamps,
event types and app ids stay readable so the person can be shown what exists without the process
having to decrypt it first. Deletion tombstones stay as a literal {}, so a deleted record is
visibly deleted from outside the process.
And then the person is told
A capture from an app nobody has been told about always raises a card on screen, at every rung of the verbosity ladder. The ladder climbs on cards shown (10, then 30, then 60), never on elapsed time — sitting idle for a week does not earn you silence. An explicit "quiet this" beats the ladder; a newly seen app beats the quiet.
here-telemetry
How it works
Working on it
Taking it further
In the repo: README.md for the
invariants, HANDOFF.md for
current state.