# Inside the sensor: built with care

This is the page for the skeptic. It walks every operation, connection and method
the sensor uses, through one lens: how it stays safe, bounded, transparent and in
your control. The whisper CLI and client tooling are open source (MIT) and publicly
distributed; the sensor's detection engines are proprietary for now, to be opened
later, which is why these pages explain the design rather than pasting the source.
The promises here are not aspirations. Each one is a structural property you can
reason about, and most are pinned by a test that fails the build if the promise is
ever broken.

One line holds the whole page together, and it is what sets this apart from any
host-only sensor: detection never sits in the data path, the brain that fuses the
verdict lives off the box, and every failure mode is arranged to land on the sensor
and never on you. A host-only agent has to carry all three on the machine it is
defending. This one does not.

Scope, stated up front: this page describes the Linux sensor, where deep detection
is live today. Windows and macOS emit telemetry through the same wire contract but
are not this page's claim. And telemetry is not detection: visibility covers 37 of 37
measured ATT&CK techniques, the signature and analytic layer covers 32 of 37 (86%),
and we keep the two strictly apart throughout.

## Forward first: detection can never touch your data

The single most important design law in the telemetry layer is an ordering rule.
Every event a collector produces crosses exactly one boundary, the stream tap, and
the tap forwards it downstream first, unchanged, to the durable spool and the on-host
graph. Only after that does it hand a copy to each detector. Detectors receive copies
and nothing more. Nothing a detector does, fails to do, or panics over can change,
delay or lose what the spool and the graph already received.

```
  COLLECTORS          STREAM TAP                 NDJSON spool (your file)
  exec · file  -----> forward FIRST, ---(1)----> on-host graph, then uplink
  conn · dns          then copy
                          |
                         (2) copies
                          v
                  detectors: event-chain · Sigma · ransomware · YARA
                  each behind a panic firewall; a fault loses one copy
```

Step 1 is durable and always happens first. Step 2 is a set of read-only copies to
detectors, each behind its own recover boundary. A detector that panics loses its
copy of one event, and the spool, the graph and its sibling detectors never notice.

The fan-out is deliberately synchronous and unbuffered. There is no queue behind the
tap, no goroutine, nothing that can grow. With zero detectors registered the whole
cost is a single atomic load per event, and the stream is byte-for-byte identical to
a build compiled with no detectors at all. The rule, in one illustrative sketch:

```
# the tap, as a rule (illustrative, not the source):
forward(event)            # (1) downstream FIRST: spool + graph, unchanged
for d in detectors:       # (2) then a COPY to each, inside a panic firewall
    guard: d.observe(copy(event))
```

This is why we can add analytic depth without ever putting your telemetry at risk.
Detection is a consumer of the stream, never a gatekeeper on it. The heavy
correlators that must live in the event path (the ransomware behavior tap, for one)
obey the same rule: they forward every event before they look at it, so their cost
can delay a detection, never a byte of telemetry.

## Every failure points at the sensor, never at you

The headline that matters to anyone who has lived through a bad agent update: a
broken build of this sensor cannot take your host down. That is not a QA promise. It
is the failure domain, and it holds for stacked reasons, each independently true in
the shipped design.

```
1 · one user-space process, no kernel module, not in the kernel critical path
2 · observe-mostly collectors: the kernel tells us; nothing waits on the sensor      |
3 · per-collector isolation + per-observer panic firewalls                           | fail
4 · in-process breaker + disarm latch: sheds the sensor's OWN work, never yours      | open
5 · kernel cgroup floor: throttle, then kill the sensor's own cgroup, never a user   v
    the brain is off-host: if the sensor is slow or gone, the host keeps serving
```

It is user-space, not a kernel module. The whole sensor is one static binary running
as an ordinary service. There is no driver, no filter, no code the OS loads inside
the kernel at boot. The default build does not even load an eBPF program: exec rides
the kernel's built-in process-events connector, files ride fanotify in notification
class, sockets ride the kernel socket-diagnostics interface, DNS attribution rides a
packet tap. These are stable kernel interfaces consumed over syscalls. Nothing of
ours executes in kernel context. Where the opt-in eBPF build is used, the programs
are verifier-checked, observe-only tracepoint attachments that the kernel detaches
automatically the moment the process closes.

The kernel enforces the ceiling, so the code cannot forget it. The generated unit
pins the sensor inside a cgroup with a hard SLA: CPUQuota 25% of one core, MemoryHigh
150M (throttle), MemoryMax 250M (a hard kill of the sensor's own cgroup, never a user
process), and swap denied outright so the sensor can never push your pages out.

Even a crash-loop is bounded and self-healing. The unit restarts fast but backs off
after a burst, so a bad binary crash-loops quietly in its own cgroup while the host
carries on. On stop or crash the kernel releases everything automatically: fanotify
groups close (any pending permission event resolves as allowed), eBPF links detach,
netlink subscriptions vanish. There is no state left armed in the kernel that could
outlive the process. Contrast that, in shape rather than by name, with a
kernel-resident agent where a bad update faults the kernel itself. Our worst case is
one ordinary process restarting.

## Bounded by construction: why memory stays flat

Nothing in this process can grow without a ceiling, and the ceilings cascade, each
rung designed never to be reached because the rung below it acts first. That is the
whole reason the resident memory of a busy production host sits where it does and
stays there.

```
A BOUNDED RING                        THE CEILINGS CASCADE
   new event in                       bounded rings, dedup sets, per-sweep caps
        v                                       v
   ( FIFO overwrite ) --> novelty     runtime soft memory limit (128 MiB)
     at capacity the       gate:               v
     oldest slot is        seen        breaker: reduce, pause, then disarm
     reused: flat          before?             v
                           drop        kernel MemoryHigh 150M: throttle
                                                v
                                       kernel MemoryMax 250M: kill own cgroup
```

Dedup and novelty everywhere. A standing connection emits once, not on every tick,
because a bounded seen-set (a fixed ring that overwrites in place at capacity, no
reallocation) remembers it. A single write session that reaches userspace as two
kernel records folds to one line. A file is scanned once per content, not once per
event, because the scan queue dedups by content hash. An evicted entry may re-emit
once, which the idempotent downstream folds absorb, and memory stays flat either way.

Backpressure sheds, it never grows. The principle, stated once: bounded always beats
complete. When a queue fills, the lowest-value item is dropped, the drop is counted,
and the count rides the heartbeat and the wire so shedding is never
silent. A fork storm enlarges the receive buffer rather than stalling the drain, and
if it still overflows the kernel does the dropping and we count it. A summary that
would exceed its size cap sheds its lowest-value detail first, deterministically, and
says so in explicit counters. Telemetry sheds. The stream, the service and the
connection never do.

Hashing is fenced off the hot path. Two single-worker lanes carry all content
hashing, so a digest can never set the pace of a drain. The exec-image lane and the
settled-write lane are deliberately separate, so a hung open on slow network-backed
storage wedges only itself. Each image digest is capped at 64 MiB and read as
cap-plus-one, so a file that grows past the cap mid-read is detected and dropped
rather than half-hashed into a wrong claim. (This identity-digest cap is a different
bound from the deep YARA content scan's 32 MiB per-file cap in the trust table below:
one fingerprints the executable, the other reads bytes under an opt-in build.) Each
lane's queue depth doubles as a file-descriptor bound, because every pending job pins
exactly one open handle.

The numbers here (the 64 MiB image-hash cap, 8 KiB gzipped uplink per window, a 128
MiB runtime soft memory limit, every shed counted on the wire) are resource bounds,
not detection trip points, which is why they are safe to publish.

## The calm plane: you never feel it, and we prove it

Because the analytic brain is off-host, the sensor can afford to be quiet, and quiet
here is engineered, not hoped for. An on-host governor splits all work into two tiers
that meet nowhere.

The hot path never rides the scheduler. The kernel taps and the local verdict path
are outside the governor's reach by construction: it holds no handle to them. That is
why the sensor's reaction time never depends on the host being idle. Everything that
can wait a minute with zero loss (posture collection, deep scans, the injection
sweep, serializing and shipping the summary) is the only scheduled tier.

Admission reads cheap kernel signals and fails soft. The governor samples
pressure-stall information for CPU, memory and IO, with an idle-delta and
load-average fallback on kernels without it, plus battery, thermal, logged-in session
activity and a link byte-rate. Every signal is fail-soft: an unreadable one always
means unconstrained, never an error. The postures map cleanly: a busy host runs only
urgent work; a calm host admits deferrable work on one worker; a host that is calm
and whose link is measured idle and where nobody is at the keyboard opens a burst
window and the pool grows, collapsing back to one worker the instant the host looks
busy again. Note the deliberate asymmetry: admission fails open, but the burst needs
positive evidence of a quiet link, because the burst multiplies the sensor's own
footprint.

Heavy I/O opens only under suspicion. The one place the ransomware engine reads file
content is gated: only once a process is already elevated on cheap, in-memory signals
does it sample a few recently written file heads, and that sampling is budgeted per
process and globally. A host churning documents and logs all day never pays a content
read, because a benign workload never crosses the cheap gate. This is the pattern
throughout: expensive work is the exception a suspicion earns, not a background tax.

Deferred workers self-lower into a throttled child cgroup. Before touching any task a
worker locks to an OS thread, drops to the lowest scheduling-priority band, sets its
IO to the idle class (use a free disk fully, yield a busy one), and joins a child
cgroup the sensor carves inside its own delegated sub-tree, capped at 20% of one core
inside the unit's 25% outer quota. A mis-sized background job can starve only itself,
never the hot-path threads that stay in the parent. The lowering is monotone: the
sensor never raises its own priority, and the unit does not even grant the capability
that would allow it.

A starvation floor means an attacker cannot suppress the homework. Every deferrable
task carries a deadline. Past it the task runs even on a busy host, still at the
lowest priority the kernel offers. Keeping a machine loaded does not starve the scans;
they still run, politely, by their deadline.

Calm is measured, then shipped as proof. The sensor reads its own cgroup accounting
on a slow cadence and emits a footprint heartbeat, and the same measured-against-
ceiling proof rides every summary window on the wire, so the promise is provable
fleet-wide and never merely logged. A breach flag is only ever positive evidence: an
unmeasured dimension reads as unproven, never as a breach. A real heartbeat,
captured today:

```
# a real capture, root-privileged, in a transient systemd scope carrying the exact
# production cgroup SLA, every detection engine on its shipped default
whisper: sensor: calm footprint: cpu 2.61% of the 25% quota, mem 17235968B of the
157286400B soft ceiling (262144000B hard), within SLA; deferrable submitted=5
completed=2 dropped=0 promoted=0 pending=3
```

The same line carries the governor's counters (submitted, completed, dropped,
promoted, pending), so "is the sensor calm, and is it keeping up" is a one-line read
on the box. Read the cpu field for what it is: a live sample from a working host
under real activity, not a cherry-picked idle one, so the fair claim is about 2.6% of
a core and about 16 MB resident, not a promise with two decimal places. Under active
detection the sensor works harder, and the number that bounds that worst case is not
a sample at all: it is the kernel-enforced 25% ceiling, which the sensor is
engineered never to approach and
which holds even if everything above it fails. A full under-load footprint
distribution is a fair thing to ask for in a POC, and we would rather measure one on
your workload than publish a synthetic curve.

Above the polite scheduler sits an in-process breaker that grades one-directional
shedding notches (reduce sampling, then pause heavy scans, then trip a global disarm
latch that turns the whole deferrable tier into a counted no-op without exiting), and
every one of those notches sheds only the sensor's own discretionary work. The
connection is never a notch: the breaker holds no handle to it, by construction. The
full scheduler and the enforced floor have their own page in
[Calm footprint & SLA](/docs/footprint).

## The on-host graph, and attribution we own natively

As events flow, the sensor folds them inline into a bounded, in-memory picture of the
host's recent activity. This is where the design stops looking like a log shipper and
starts looking like something new. The graph is composed on the hot path, because a
graph folded later would be a picture of the past.

```
ONE HOST, COMPOSED LIVE                THE JOIN WE OWN
        parent                         process asked NAME
          ^ spawn                              v
        process                        answered ADDRESS  -->  graph verdict
     file / dns / conn                 source port stamped on both host-side
        v    v    v                    legs = the join key. native, because
  dir-prefix  query-name  peer         Whisper also serves the name
```

Every exec adds a process node and a spawn edge; every file event a folded
directory-prefix edge; every connection a peer edge; every query a resolved-name
edge, kept in its own ring so "asked for" stays apart from "connected to".

Process, then name, then address, then verdict, with no guessing. On the host, the
DNS attribution collector sees which name each process asked for. The socket collector
sees which peer each process actually dialed. The digest keeps asked-for and
connected-to as separate, joinable vocabularies under the same normalized key, each
carrying the socket's source port. Because Whisper also serves the host's DNS, the
backend already holds the resolver's half of that same moment: the query, the client
source port, the answer, and the graph verdict for the name. Stamping the source port
on both host-side legs gives the exact join: process, to name, to answered address, to
outbound connection, to graph verdict. Most tools have to guess the domain back from
an IP after the fact. We do not, because we own both ends of the lookup.

Captured facts survive their owner. When a two-second process is evicted before the
next summary, its connection legs are harvested into a bounded accumulator, so a dial
is never lost to process churn. A single startup walk of the process table seeds
long-running daemons that ran before the sensor existed, so their exposure never
falsely reads dormant. An event that cannot be attributed still folds into a degraded
process node keyed by PID alone: fail open, never a lost edge, never a wrong name.

Encrypted DNS, confronted rather than buried. Every EDR has this blind spot; here is
ours, and what surrounds it. A process that switches to DNS over HTTPS or DNS over
TLS bypasses the port-53 name tap, and the tap never claims what it cannot see. But
the process does not go dark: the dial to the resolver still lands as a
pid-attributed connection edge, the off-host graph still scores that peer (a known
public DoH endpoint reads as exactly that), and a process opening TLS straight to an
external resolver, instead of the host's configured DNS path, is itself a strong
anomaly signal on a host that never did it before. And Whisper holds a lever no
host-only sensor has: the resolution plane the host already points at is a policy
point, so per-device policy can refuse to bootstrap known DoH resolvers by name, and
where the endpoint's egress rides the Whisper plane the same policy denies or gates
the direct resolver dial itself, off-host, with no agent cooperation. PID
attribution is stated as post-hoc and degradable to an unattributed-but-held edge.
Sidestepping the tap does not sidestep the network that carries it.

## Safety in every syscall

A security sensor runs as a privileged process reading a filesystem an attacker may
be actively manipulating. The care shows in the small operations, the ones a review
reads line by line.

Symlink-safe reads, refused at the kernel. Every content read the detection engines
perform refuses symlinks in every path component, using the kernel's own
resolve-no-symlinks mode where it exists, with a clean fallback to a classic
no-follow open on older kernels and restrictive seccomp filters. The file collector's
settled-write open is fd-anchored, never a re-walk of a string path: the reported
directory handle is opened, then the entry is opened under it with no-follow and
non-blocking, so a planted symlink is refused and a FIFO cannot wedge the worker. A
racing rename cannot swap another object under the digest.

```
# the shape of every sensitive open (illustrative):
openat2(dir, name, RESOLVE_NO_SYMLINKS)   # refuse a planted link AT the kernel,
                                          # not in a check-then-open race
```

PID reuse is fenced, not wished away. PIDs recycle, and the sensor never pretends
otherwise. The stable identity is the triple (pid, start_ticks, boot_id): a recycled
PID always gets a fresh start time, and the kernel's per-boot random UUID fences off
reboots. When an asynchronous digest lands, the process start ticks are re-read and
compared; a mismatch means the PID was recycled mid-flight, and the digest is dropped
rather than attributed to the wrong process. The event-chain correlator mints start
ticks at fork so a different stamp on a reused PID evicts the stale chain before it
can mis-attribute an old process's history to a new one.

Telemetry integrity against a local forger. A netlink socket receives kernel
multicast and any local process's unicast to the same port. The drains therefore read
with the source visible and accept only datagrams whose source is the kernel (port id
0), which userspace cannot bind or forge. A local attacker cannot inject fake exec or
socket records to fabricate activity or bury real signal. The socket-dump collector
adds an in-band check: every frame must carry the current dump's sequence number, and
any residual tail from a stalled predecessor is flushed before the next request, so
one stalled dump can never bleed sockets into the next answer.

The spool cannot be turned into a weapon. A privileged writer appending into a
user-owned directory refuses a symlinked spool twice, with a pre-open check and an
atomic no-follow open, so telemetry can never be redirected into an append to an
arbitrary file. A refused spool means the sensor stands down with one clear line, and
the connection is unaffected.

Least privilege, itemized. The sensor starts as root because opening a fanotify group
requires it, but its reach is pinned to an itemized capability set, each with a stated
reason, and a bounding set caps what it could ever gain. It holds no kernel-module
capability on any build. The socket collector needs no capability at all. The separate
connection daemon runs with zero capabilities and no-new-privileges. Response
executors, where a host opts into them, use absolute compile-time program paths, argv
only, a reset environment, and never a shell or a path search.

These are checked, not just intended: a build-time safety suite reads the engine
source itself and fails the build if a mount-wide or filesystem-wide fanotify mark
appears, if a detection engine ever touches a kill or ptrace primitive, or if a
sensitive read is not symlink-refusing. The promise and the code cannot drift apart
silently.

## False positives, engineered out

An alert you cannot trust is worse than no alert, so false-positive safety here is
structural, a property of how conviction is reached, not a constant someone tuned. The
mechanisms below are design, and they are why a compiler, a backup job, an rsync or a
package upgrade cannot trip the ransomware engine.

Corroboration is required before conviction. No single cheap signal, and no innocuous
pair, can reach the firing line. Content entropy only counts beside destruction: an
in-place overwrite of files the process did not itself create, a mass delete, or a
uniform rename churn. A benign encrypted vault being populated, or a camera-RAW
import, only creates files, so its high-entropy content can never convict on its own.
The public formulation is exact: multi-signal correlation that requires a
corroborating destructive signal before entropy can convict.

Content exonerates as readily as it accuses. When sampled files read as genuinely
plain, the burst is judged to be exactly what it looks like, a build or a sync, and
confidence is reduced. A parallel compiler fan-out crosses the same rolled-up
mass-write shape a real encryptor would, and it is exonerated the same way, by its own
plain output.

Neutral verdicts an attacker cannot bank. A sample that opens with a known container
or compression signature is neutral: it neither convicts nor exonerates, so prepending
a familiar magic to ciphertext buys nothing. Armored near-uniform bytes are neutral
too, so base64-wrapping ciphertext to duck an entropy check earns no innocence, and a
legitimate encoded blob convicts no one.

Curated screens for the runtimes that look guilty. A JIT runtime's own code cache is
anonymous executable memory by design, so a curated screen clears only the
region-anonymity signals for known runtimes (the JVM, the V8 family, .NET, LuaJIT,
PyPy, Wine, QEMU and kin); a hollowed or deleted-backing process of the same name is
still a finding. A package upgrade unlinks a library and drops a fresh one at the same
path, so every daemon still mapping the old inode shows "deleted"; the engine
re-checks each such path in the process's own filesystem view, and clears the signal
when the upgrade shape is confirmed while keeping it when a backing is genuinely gone.
Each screen documents its own dodge in place, and each set grows by evidence, never by
guesswork.

Absence never convicts. A truncated memory-map read withholds the hollow judgement
entirely: a mapping the sweep never saw must not convict. An unreadable hash, a
vanished process, a dark meter all ship as absence, not as an invented zero. We could
not look must never read as suspicious.

Confidence never reaches certainty. Every finding carries a fused score that tops out
below 1.0, at a stable two-decimal precision, alongside the exact contributing signal
names and a few bounded sample paths, so a responder reads precisely what was seen and
how corroborated it was. A rule-driven finding reads its ATT&CK technique from the
rule and emits an "unmapped" token rather than inventing one. Certainty, and
destination reputation, are the off-host graph's second pass to add, against 7.52B
nodes and 38.6B relationships the host could never hold.

Why we abstract the numbers: we publish resource, safety and test figures freely,
because they build confidence without helping an attacker. We do not publish firing
thresholds, window sizes, signal weights, entropy cutoffs or canary details, because
those would be an evasion map. The mechanism is the story; the exact trip point is not
ours to hand out.

## What leaves the host, and what never does

Minimization here is enforced by the wire format: the summary cannot carry what it has
no field for. Everything observed is visible locally in plain NDJSON that you own;
only a bounded, deduplicated, curated digest ever leaves the machine.

| Stream | Kept local, in full | What actually ships |
|---|---|---|
| exec | pid, uids, task name, exec path, a bounded command line, image digest, stable identity | novel processes only, with the image hash and a known-good marker. The command line never ships: it is matched locally and stays on the host |
| file | each create, modify, move, delete with the exact path; a settled digest for executables only | folded directory prefixes of novel processes; an exact path only for a settled executable write with its digest. Data files are never hashed, contents never leave |
| conn | every outbound and accepted-inbound leg, pid-attributed | one record per novel (process, destination, port) leg. No payloads, ever: there is no field for them |
| dns | each query name per socket, pid-attributed | one record per novel (process, name) leg, same bounded shape |
| detections | full finding line, spooled first | technique, confidence, signal names, a few bounded sample paths, immediately |
| resource use | self-metered heartbeat | the published ceiling plus measured actuals, every window, never shed |

The raw stream is yours to read. The local sink is a plain NDJSON file, one JSON
object per line, created private to its owner, flushed per line so the last seconds
before a host goes down are on disk rather than lost in a buffer. You can `tail -f`
it, `jq` it, or feed it to whatever log pipeline you already run. Two ad-hoc commands
print the same events to stdout for a live look with no service running:

```
whisper sensor exec        # live exec stream to stdout, NDJSON
whisper sensor files --dir /var/lib/whisper-sensor/watch
```

The uplink is a novel-only delta. A bounded high-water set remembers every process,
connection leg, resolution and finding already shipped, so each fact crosses the wire
once. A long-lived process still ships new facts as they appear, because an old
process doing a new thing is exactly the signal. The payload is hard-capped at 8 KiB
gzipped per window, and the resource proof rides every window and is never shed,
because hiding the proof under load would hide exactly the window that matters. A
steady-state host's uplink is a heartbeat-sized delta a few times an hour; a noisy
host degrades to less detail, never more bandwidth.

Identity is stamped by the transport, never claimed by the body. The sensor never
mints an identity. It reports as the persisted /128 the installer bound, reused across
restarts and upgrades, and the primary uplink is keyless: source-bound to the host's
own /128, no API key, no enrollment token, no host secret on the wire. Any
identity-looking field inside a payload is ignored, so a host cannot claim to be
another host. The details are in [The /128 identity](/docs/wire-identity).

## In your hands

Power over the host is opt-in, and it escalates only when you say so. The whole
operator surface is four independent switches in strictly rising power, and none ever
implies the next.

```
RISING POWER, EACH ITS OWN EXPLICIT CHOICE
observe  -->  enforce guard  -->  freeze  -->  respond
OFF           its own            enable,       per-action
              config section     then arm      allowlist
```

Turning on telemetry can never turn on blocking. Turning on blocking can never turn on
freezing. The freeze responder dry-runs until a second flag arms it. The
bounded-response tier needs a host opt-in, a per-action allowlist, and an operator
scope that is never auto-enrolled.

Observe-mostly by default, once the sensor is running. Starting the sensor is its
own deliberate step, never a side effect of the base install: the plain install
one-liner places the binary only, and `WHISPER_SENSOR=1` (or a follow-up
`whisper service install --now --sensor`) is what starts it. From there, every
shipped collector runs by default, and so does every detection engine, the
behavioral correlators, the Sigma evaluator, the event-chain and injection
detectors alike, because opting into host telemetry should not mean hunting for a
dozen more knobs to turn each one on. Only two things stay opt-in past that point:
the YARA-X engine, gated behind its own `-tags yara` build, and every action tier
above observe, covered next.

The one blocking primitive is separate and fails open. The exec guard, the only thing
that can ever pause an operation, is off by default, lives in its own config section,
and cannot be armed from any CLI subcommand (a one-flag way to point a permission
guard at an arbitrary path is a foot-gun we refuse to ship). When armed it marks only
inode-scoped, dedicated directories, refuses host paths, and gives every pending exec
a short watchdogged deadline past which the answer is allow. Every unknown, every
error, every overload: allow. If the guard process dies, the kernel releases every
pending exec as allowed. The worst case for a user process is a bounded pause, never a
freeze.

The calm personality is yours to tune, within limits it cannot exceed. With an API
key, the backend computes a per-endpoint advisory hint the sensor pulls on an adaptive
cadence: lean in sooner on an endpoint with suspicious history, defer deeper on a
clean one, run a named task first. The contract is one-directional and worth stating
precisely: a hint can make the governor smarter about when to lean in or out, and
nothing else. It can never touch the hot path, never exceed the resource SLA (the one
lift it can request is hard-clamped at the outer quota and restored the moment the
hint lapses), never starve a past-deadline task, and never survive its TTL. No key, a
lapsed hint, or a backend outage all mean the same thing: the shipped local governor
runs byte-for-byte unmodified. The keyless tier is the baseline product, not a
degraded one.

What is deliberately not configurable is the design. Detector windows, thresholds,
weights and canary sets; the critical-config path allowlist; collector cadences and
buffer bounds; the response tier's action grammar. Config can only narrow the surface
(fewer directories, fewer actions, fewer restartable units), never widen, retarget or
defang it. A mis-tuned detector is a silently defanged detector, and every widening
knob is an attacker-reachable surface. There is no config that turns the sensor into a
different product on one host of your fleet.

Per-device policy and an off-host stop. Resolution and egress policy are control-plane
primitives with a device selector, so containment acts from the network side even
where no sensor runs. And the ultimate off switch is the identity itself: revoking an
endpoint's /128 cuts resolution and egress fleet-wide with no agent cooperation, which
is exactly why it survives a fully compromised host. The containment mechanics are in
[Containment](/docs/kill-switch).

Pause is a pause; erasure is a crypto-shred. Stopping the unit pauses observation with
config and spool intact for a later restart. Uninstall removes it entirely. And
releasing or erasing an identity tears down its DNS and reverse mapping, drops its
RDAP record to a covering allocation naming no agent, releases its allocation record and appends a revocation to the transparency ledger, whose
commitments are permanent by design (that permanence is the tamper-evidence) while
each entry's meaning depends on a per-entry secret held in a separate erasable store.
Deleting that secret makes the entry's meaning unrecoverable to anyone, including us,
and completes within the backups' maximum retention window, while inclusion and
consistency proofs keep verifying. Audit coexists with erasure.

## The trust gates, and the numbers

Lead with what a compiler enforces, because an enforceable gate beats any count of
tests. Four safety invariants fail the build the moment they are broken:

- The build fails if a detection engine references a kill or ptrace primitive, or a
  shell. Observe-only is not a policy statement; it is a compile gate.
- The build fails if a mount-wide or filesystem-wide fanotify mark appears anywhere.
  The sensor can never quietly grow a whole-filesystem watch.
- The build fails if a sensitive content read is not symlink-refusing. The
  open-safely discipline in the syscall section above is checked, never just
  intended.
- A unit test asserts the generated systemd unit carries the exact published cgroup
  ceiling, so the promise on this page and the floor in the kernel cannot drift
  apart silently.

The numbers below are the supporting cast: every figure is a resource bound or a
safety property, safe to publish precisely because knowing it helps an operator and
gives an attacker nothing.

| Area | The number | Why it is safe to publish |
|---|---|---|
| Live-captured footprint | ~2.6% of a core, ~16 MB resident | a live capture on a working host, shipped as proof every window; the ceiling below is the worst-case guarantee |
| Kernel-enforced ceiling | 25% CPU, 150M soft / 250M hard, swap denied, IO idle-weight, 64 tasks | already public; a ceiling you never have to trust us about |
| Image-hash discipline | 64 MiB cap on the executable digest, growth-detected, off the hot path | a resource bound, not a detection trip point |
| Uplink | 8 KiB gzipped per window, novel-only, sheds counted | a minimization bound |
| Injection sweep | bounded per sweep and per process, at host idle, a runtime cap and a starvation floor | coarse by design; injection persists, so latency costs nothing |
| Deep file scan (YARA) | 32 MiB per-file cap, distinct from the 64 MiB image-hash cap, bounded per sweep, a per-scan timeout | opt-in build; a fresh unknown binary still scans within about a minute |
| Exec guard | a short watchdogged verdict deadline, then allow | a safety bound; an attacker "exploiting" it gets allowed execution we still record |

Underneath the gates, as a footnote rather than the argument, sit roughly 2,112 test
functions and 93 fuzz targets, including the build-failing safety suites above
and byte-stable wire corpora. The fuzz targets sit on the parsers an attacker can
reach: the DNS wire decode, the event envelope, the netlink batch walkers, the
memory-map parser, the rule compilers and the process-identity math. Kernel-version
graceful degradation runs throughout: a collector that cannot attach is dropped with
one clear, actionable sentence naming the remedy, never a raw errno, and the sensor
keeps running on the rest.

Prove it yourself, keyless. You do not have to take the detection story on faith:
the sensor bundles a Whisper_EICAR_Test_File YARA rule precisely so an evaluator
can prove the engine end to end on their own box. Opt in to the YARA engine, write
the standard EICAR test string into the watched directory as an executable, and the
finding lands in the journal and the spool, named by the rule: the detection
analogue of the keyless dig-and-delv identity proof. The behavioral run is public
too: real Atomic Red Team atomics driven against the shipped sensor, 37 of 37
techniques captured as telemetry, live, with the signature layer naming 32 of 37
(86%); the verbatim captures are in [ATT&CK coverage](/docs/attack-coverage). A
public MITRE ATT&CK Evaluation is scheduled for 2027; until it runs, these
reader-runnable proofs stand in for the badge.

Maturity, once more: this deep-dive is the Linux sensor, live today. Windows
and macOS ship telemetry through the same wire contract; their deep-detection pass is
a separate story. Telemetry coverage (37/37) is visibility, not detection (32/37,
86%). Confidence never reaches 1.0 anywhere, and the public graph is 7.52B nodes and
38.6B relationships. We would rather under-claim in writing than have you find a gap in
practice.

If you have read this far as the skeptic it was written for, the through-line is one
sentence: every part of this sensor is bounded, transparent, and arranged so that its
own failure lands on itself and never on you. That is what care looks like when it is
built in rather than bolted on. The map that ties these pieces to the rest of the
system is [How the sensor works](/docs/architecture), the collectors are enumerated in
[Telemetry collectors](/docs/collectors), and the analytic layer that reads the stream
is in [Detection engines](/docs/detections).
