# The macOS sensor

The plane that protects a Mac does not run on macOS. The routable /128 identity, the
graph verdict on every destination, per-device policy and the off-host kill-switch
are network-side, so they cover a Mac today, sensor or no sensor, entitlement or no
entitlement, and anyone can verify every one of those claims with stock tools. On the
host, the detection engines themselves, the ransomware correlator, the
process-injection correlator, the event chain and the fenced response tier, are now
exercised on Apple-silicon over the bridge datapath (the exec, file and socket collectors are proven on real Apple hardware; the /dev/bpf qname tap needs root and is exercised through a scripted read seam rather than a live device; the engines are exercised in datapath tests rather than fired by a live Endpoint Security feed), fed through the bridge datapath,
reading the kernel's code-signing identity where the kernel will give it. What remains gated is one
thing, and we show the proof of the gate itself: the live Endpoint Security feed
needs Apple's ES entitlement plus Developer ID notarization, and macOS kills an
unapproved client before it reads a single event. Approval flips it on; nothing else
changes. Both halves, kept distinct, are the whole macOS story.

At a glance: a routable /128 identity on a Mac today; 7.52B graph nodes scoring its
destinations; zero host cooperation needed to revoke it; zero kernel extensions,
ever; one wire contract, byte-proven against Linux; four engines exercised on Apple-silicon over the bridge datapath (the exec, file and socket collectors are proven on real Apple hardware; the /dev/bpf qname tap needs root and is exercised through a scripted read seam rather than a live device; the engines are exercised in datapath tests rather than fired by a live Endpoint Security feed) on a
real Mac.

## What a Mac gets today

Start with the half that needs no entitlement, no installer and no trust in anyone's
release calendar, because it is the half that decides protection. These four layers
live on Whisper infrastructure, not on the endpoint, so a MacBook carries them the
moment it holds an address:

- A verifiable, routable identity. A real IPv6 /128 out of 2a04:2a01::/32, with
  DNSSEC-anchored reverse DNS, a DANE-EE key pin and a public RDAP record.
  Keyless-checkable by anyone with dig and curl, both of which ship with macOS.
  Whisper's API is nowhere in the trust decision.
- Off-host containment. Revoking the /128 cuts resolution and egress worldwide, even
  if the Mac is fully owned. The kill-switch does not run on the host, so a root
  attacker cannot find it, patch it or disarm it. That property holds identically for
  a Mac, a Linux server and a printer; it is the design, not a per-OS feature. Full
  detail in [Containment](/docs/kill-switch).
- Graph verdicts on every destination. Graph-first resolution against 7.52B nodes and
  38.6B relationships, over encrypted DoH/DoT or the device's dedicated /128 resolver.
  macOS has first-class encrypted-DNS support: a configuration profile pins the
  system resolver, so this layer needs no app on the Mac at all.
- Per-device policy, metered egress, two tiers. Per-device resolution policy, a
  per-agent egress firewall, and per-/128 egress metering and logs, all enforced
  off-host. Two-tier by design: keyless verify for everyone; the control plane and
  egress unlock with an API key.

Why we lead with this: on macOS, every endpoint vendor's on-host timeline is clocked
by Apple. Endpoint Security is a restricted, Apple-approved capability, and there is
no legitimate way around that queue. Our on-host bridge waits in it too, and the rest
of this page states exactly where that stands. The protection plane above
does not wait, because it is not an app.

```
ON THE NETWORK · live for a Mac today · keyless-verifiable
+----------------+----------------+----------------+------------------+
| IDENTITY       | THE GRAPH      | POLICY         | RESPONSE         |
| routable /128 ·| destination    | per-device ·   | revoke the /128, |
| DNSSEC · DANE ·| verdicts ·     | firewall ·     | worldwide, one   |
| RDAP · stock   | 7.52B nodes /   | metered egress,| call · survives  |
| dig + curl     | 38.6B edges      | off-host       | a lost Mac       |
+----------------+----------------+----------------+------------------+
   none of the four waits on an entitlement · they act on Whisper
   infrastructure, not on the endpoint

ON THE MAC · live ES feed entitlement-gated
+--------------------------------+---------------------------------+
| ES + NETWORK EXTENSION BRIDGE  | THE SAME GO BRAIN               |
| Apple's own frameworks, no     | one wire contract, byte-proven  |
| kext · exec + file + dns +     | against Linux · engines proven  |
| memory · live feed gated       | firing on a real Mac            |
+--------------------------------+---------------------------------+
```

The purple row is live for a Mac now and checkable keyless. The bottom row is the
on-host half: real, built, contract-tested, its engines exercised on Apple-silicon over the bridge datapath (the exec, file and socket collectors are proven on real Apple hardware; the /dev/bpf qname tap needs root and is exercised through a scripted read seam rather than a live device; the engines are exercised in datapath tests rather than fired by a live Endpoint Security feed),
and its live feed gated on Apple's Endpoint Security entitlement. We never round the
second row up into the first.

The fleet console already reads Macs this way: the live lab-fleet capture on the HTML
page (captured 2026-08-25) shows a genuinely mixed fleet with a macOS demo box among
the worst-ranked hosts, because identity, policy and containment are network-side.
Its /128 carries the same one-call revoke, sensor depth or not.

## Identity on a Mac: verify it from the terminal you already have

The keyless half first. macOS ships dig and curl, and those two are enough to check a
Whisper identity against the internet's own records, with our API nowhere in the
trust path. The example is a live identity, the agent `scout`, so every line below is
runnable from any Mac this second:

```
# reverse DNS names the address - the identity, in public
$ dig -x 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478 +short
ae3b051ff3bf7f478.tdc38e7c55bad3306a92b830f9bb1e4f9.agents.whisper.online.

# the DANE-EE key pin (usage 3, selector 1, matching 1) - no public CA needed
$ dig +short TLSA _443._tcp.ae3b051ff3bf7f478.tdc38e7c55bad3306a92b830f9bb1e4f9.agents.whisper.online
3 1 1 9EC1EF18A1F15E5480A0FC3C8D6E9690B2CCF1A7FA7146201940F28C422BB47D

# one keyless call returns the whole evidence bundle
$ curl -s 'https://rdap.whisper.online/verify-identity?ip=2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478'
{
  "is_whisper_agent": true,
  "fqdn": "ae3b051ff3bf7f478.tdc38e7c55bad3306a92b830f9bb1e4f9.agents.whisper.online",
  "dane_ok": true,
  "jws_ok": true,
  "evidence": {
    "posture": "tier1.5",
    "dane": { "usage": 3, "selector": 1, "matching": 1,
              "profile": "DANE-EE 3 1 1 (DNSSEC-anchored TLSA; no public CA needed)",
              "strong_pin": true, "served_leaf_matches": true },
    "rdap": "https://rdap.whisper.online/ip/2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478"
  }
}
```

The full chain (the DNSSEC signatures validated to the IANA root, the forward-confirmed
AAAA, the RDAP object) is in [Verify an identity](/docs/verify); delv, for validating
on the Mac itself rather than trusting a resolver, is one `brew install bind` away.

The keyed half. The whisper CLI is a single static binary and macOS is a
first-class build, and the install is genuinely easy: the same one command every
other platform uses, no Gatekeeper argument required. It fetches a signed,
checksum-verified binary and ad-hoc-signs it itself the moment it lands, so
there is no quarantine flag to clear and no right-click-to-open:

```
# the CLI, same command on macOS and Linux
$ curl -fsSL https://get.whisper.online | sh

# with your API key: one call registers an agent and hands back a routable /128
$ curl -s -X POST https://graph.whisper.online/api/query \
    -H 'content-type: application/json' \
    -H 'X-API-Key: whisper_live_...redacted' \
    -d '{"query":"CALL whisper.agents({op:\"register\", name:\"scout\"})"}'
```

A signed, notarized, stapled installer package now builds end to end from this
same tree, and Gatekeeper is clean on it: `spctl --assess --type install` reads
*accepted, source=Notarized Developer ID*, a flip from *rejected, source=no
usable signature* the week before. It has no download route open yet, so the
curl one-liner above stays the way to actually get whisper onto a Mac today; a
Homebrew tap also exists (`brew install whisper-sec/tap/whisper`) but is not
kept in step with every release, so we point you at the one-liner rather than
the tap.

How the Mac's traffic actually carries that identity is the same connectivity ladder
as everywhere else, all of it network-side and none of it entitlement-gated: a routed
/128 over WireGuard (a wg-quick config today, with one-line `whisper connect`
ergonomics for the routed tier on the way), the source-bound egress proxy where you
want packets leaving as the /128 without a tunnel, or simply keeping the Mac's own
address and pointing its DNS at the graph-first resolution plane, which macOS can pin
system-wide with an encrypted-DNS configuration profile. AI agents spawned on a Mac
follow the agent path, not the host path: each gets its own governed /128 from the
same one call, with policy composing down the tree. The deeper treatment is in
[The /128 identity](/docs/wire-identity).

## The on-host bridge: new eyes, the same brain

macOS is a new pair of eyes, not a second brain. Two small Swift front-ends observe
the Mac through Apple's own frameworks: an Endpoint Security daemon for process
activity and a NetworkExtension DNS proxy for DNS. Each observation is encoded as one
NDJSON line in the exact event vocabulary the Linux collectors emit and handed over a
local Unix-domain socket to a bridge collector inside the same whisper sensor service
that runs everywhere else. From that point on, a Mac event is indistinguishable from
a Linux event: it rides the identical pipeline, the wide tap, the bounded on-host
process graph, the local NDJSON spool the operator owns, and the curated uplink. No
kernel extension exists anywhere in this design, because Endpoint Security and
NetworkExtension are Apple's supported user-space surfaces and we refuse to ship
anything lower.

| Piece | What it is | Emits |
|---|---|---|
| Endpoint Security daemon | A small Swift daemon under launchd, subscribed notify-only: process events always, file and memory events behind their own flags | `exec` lines, plus `file` and `memory` when enabled (dev builds) |
| DNS proxy system extension | A NetworkExtension DNS proxy plus the container app that activates it, a transparent relay that observes as a side-effect | `dns` lines, UDP and TCP (dev builds) |
| The Go bridge | A darwin-only collector in the standard sensor service: binds the socket, validates every line, forwards into the shared pipeline | the same spool and uplink every Linux collector feeds |

```
SWIFT · Apple's frameworks · eyes only
+-------------------+
| ES DAEMON         |--+   +-----------------+   +------------+   +--------------+
| notify-only exec  |  +-->| one Unix socket |-->| GO BRIDGE  |-->| shared tap   |
+-------------------+  |   | 0700 · peer-cred|   | validate + |   | process graph|
+-------------------+  |   | gate · 64 KiB   |   | forward    |   | NDJSON spool |
| DNS PROXY         |--+   | lines · ≤8 conns|   +------------+   | curated      |
| transparent relay |      +-----------------+                    | uplink       |
+-------------------+                                             +------+-------+
                                                                         |
                                              +--------------------------v------+
                                              | SHARED GO ENGINES (OS-neutral)  |
                                              | consume copies off the tap ·    |
                                              | run on bridged events by design |
                                              +---------------------------------+
```

The bridge owns and binds the socket; the Swift daemons dial it. When nobody is
listening, the ES daemon writes its NDJSON to its own log instead, so a bridge that
is down costs observations, never a wedged daemon. Direction of trust is deliberate:
the Swift side stamps the host, timestamp and boot id, because on macOS it is the
vantage point that knows the host. The Swift side is eyes only: findings are made by
the shared engines, never bridged in.

The consequence is the one that matters: every OS-neutral Go engine, the event-chain
correlator, the ransomware detector, the process graph, Sigma, YARA, runs on macOS
telemetry with zero changes, by construction, because the wire contract is
byte-identical and the tap is the same tap. The detection brain is shared; only the
eyes are per-OS. And "by construction" is no longer the strongest form of that
claim: the engines are now exercised on Apple-silicon over the bridge datapath (the exec, file and socket collectors are proven on real Apple hardware; the /dev/bpf qname tap needs root and is exercised through a scripted read seam rather than a live device; the engines are exercised in datapath tests rather than fired by a live Endpoint Security feed), through the real
bridge datapath (see "What has run on a real Mac" below). What we still do
not claim: the live, entitlement-signed Endpoint Security feed driving them in
production. The measured production engine runs published on this site are Linux and
Windows runs, and [Platform support](/docs/platforms) owns that matrix.

## What it streams today, exactly

One JSON object per line, a `kind` field discriminates, timestamps are epoch
microseconds everywhere. This is the same envelope the Linux pages document, which is
the point. Per kind:

| Kind | Carries | macOS producer |
|---|---|---|
| `exec` | pid, ppid, uid, comm, executable path, a vnode-pinned size-capped sha256, known-good mark, a bounded command line, boot id, host, plus the kernel's code-signing verdict: team id, signing id, cdhash, the platform-binary fact and the raw cs flags | Endpoint Security exec notifications: streaming, dev builds |
| `dns` | qname, qtype, querying pid, source port, the resolver the OS chose, host | The DNS proxy, UDP and TCP DNS both: streaming, dev builds |
| `file` | create / modify / move / delete / hash, with path and digest | Endpoint Security file subscriptions (create, settled close-after-modify, truncate, rename, unlink, clone), behind their own flag and scoped to the watch dirs the bridge announces: dev builds |
| `memory` | remote-thread-create, executable mprotect, executable mmap, each with the acting process's kernel signing identity | Endpoint Security memory notifications, behind their own flag; dispatched straight to the injection correlator, never to the spool: dev builds |
| `conn` | pid, destination, port, direction | Vocabulary reserved; macOS covered by the entitlement-free libproc socket sampler |

Two details worth trusting. The image hash is honest by construction: the size-capped
sha256 is pinned to the exec-time file identity, so a binary swapped between exec and
hash yields no digest rather than a wrong one, and any field a message cannot supply
stays at its zero value instead of being guessed. And DNS attribution is honest the
same way: the querying pid comes from the flow's own audit token and reads zero when
absent, an admitted absence, never a claim.

## Bounded and safe by construction

The macOS path inherits the same two laws every Linux collector obeys: observe never
blocks, and the sensor never takes the service down. The numbers below are resource
bounds and trust properties, compiled in rather than configured, published because
they build confidence without handing anyone an evasion map. The missing knobs are
the design here too.

### The Go bridge

- Every line is bounded at 64 KiB. The largest legitimate event is well under 8 KiB,
  so that is 8x headroom; a longer line is counted once as an oversize drop and the
  reader discards to the next newline to resync. A rogue writer cannot balloon memory
  or bloat the graph. The bound was deliberately tightened from an earlier, larger
  figure after security review.
- At most 8 concurrent peers, normally exactly 2. Beyond the cap a connection is
  closed and counted in a separate, security-relevant rejected counter, kept apart
  from the line-drop tally.
- A peer-credential gate on every accept. The bridge reads the connecting process's
  uid and admits only the socket owner or root, defense-in-depth behind the socket's
  0700 mode, because a root EDR's telemetry ingest must not rest on one OS behavior.
  A refused peer never reaches the parser.
- Malformed input is dropped, counted and surfaced, never guessed at: a line must be
  well-formed JSON with a known `kind` and the right field types to be forwarded,
  verbatim, onto the shared stream. A full spool costs one observation, never a
  stalled bridge, and drops surface as one plain journal line.
- Shutdown is airtight. Cancellation closes the listener to unblock accepts, a
  per-connection watcher unblocks parked reads, and serve returns only after every
  in-flight connection drains. A bind failure at startup is one clear line and the
  rest of the service keeps serving.

### The Endpoint Security daemon

- Notify-only, structurally. It never holds a verdict, so it can never block an exec,
  and it never stalls Endpoint Security's delivery path: heavy work such as image
  hashing runs on a bounded background lane of 64 in-flight permits, and when the
  lane is saturated the message is dropped, the same posture as a full kernel ring
  buffer on Linux, rather than making the kernel wait.
- Memory-safe against its own lifecycle. Each message is retained exactly once across
  the lane and released exactly once; stop quiesces every in-flight message before
  the ES client is torn down, so shutdown can never touch a freed message.
- Self-muting. The daemon mutes its own process, so it never narrates its own execs.
- Bounded argv. Command lines are joined under byte and argument caps with an
  explicit truncation flag, decoded only as far as the cap allows, so a giant-argv
  exec cannot force a large allocation.

### The DNS proxy

- Conservative in what it does. It never black-holes a query, never fabricates an
  answer, never blocks host resolution. A flow it cannot classify is declined so the
  system serves it normally; a forward it cannot complete simply goes unanswered and
  the app retries exactly as it would without the proxy. Observation is a side-effect
  of a transparent relay and must never degrade it.
- Liberal in what it accepts. A malformed query datagram fails the full DNS-wire
  decode and is still forwarded verbatim; the observation is lost, the user's
  resolution is not.
- Bounded. Around 5 seconds of upstream deadline per UDP query; on TCP an idle timer
  resets on each byte of progress, so a slow-but-live transfer is never cut short; at
  most 256 concurrent in-flight forwards per flow, beyond which a query is dropped
  rather than letting descriptors grow without bound.
- No recursion trap. The upstream resolver is parsed strictly as an IP literal; a
  name is never resolved inside the very proxy that mediates all host DNS.

## One wire contract, byte-proven

"Same brain" would be marketing if it rested on a shared intention. It rests on a
corpus. The Swift encoders are proven byte-identical to the Go golden corpus for the
`exec` and `dns` kinds: a corpus-driven contract suite reads the staged Go corpus and
holds the live Swift encoder to it, and the suite is mutation-verified, meaning a
one-token edit to the corpus flips the tests red at the exact byte offset. It runs in
CI on Linux Swift, no Mac in the loop, so drift cannot land. The corpus spans
`exec`, `dns` and `file`, including a darwin-spelled file set, and the `memory`
goldens are carried verbatim by their Go twin, so the two encoders cannot move
apart silently. Only `conn` remains Go-canonical, because there is no macOS
producer for it yet; we state that rather than blur it.

- The sensor tree carries roughly 2,065 Go tests and more than 220 Swift tests, with four
  build-failing durability gates: a per-package coverage ratchet, a mutation gate
  holding at 80 against an 84 measured baseline on the pure-logic deciders, a
  platform-matrix gate that asserts the darwin build actually selects the bridge,
  and a fuzz-smoke gate over 34 fuzz targets.
- The bridge's decode boundary has its own envelope fuzz plus an adversarial NDJSON
  corpus, and the whole ingest path, accept, validate, drop, resync, back-pressure,
  shutdown, is unit-tested on Linux through a real socket, including a
  malformed-storm test and a two-writer concurrency test. That is possible because
  the ingest core is deliberately OS-neutral Go; only the thin socket lifecycle is
  darwin-specific.

What the proof buys you: when the entitlement lands and the bridge ships broadly,
macOS does not get a young detection stack. It gets the Linux one, unchanged, because
a macOS event has been provably indistinguishable on the wire the whole time. The
contract is the moat against per-OS drift, and it is enforced by CI, not by
discipline.

## What has run on a real Mac

Contract tests on Linux CI say the engines would run on macOS. In August 2026 we
closed the remaining distance and ran them on macOS: the compiled darwin sensor's
own engine and datapath suites, executed on a real Apple-silicon Mac, pushing
ES-shaped events through the real bridge ingest into the real engines, the exact
code path the live feed uses. Every engine fired. The captures below are from that
run, 2026-08-26, unedited apart from trimming:

```
# the compiled darwin sensor test binary, run on the Mac itself:
# the ransomware datapath (T1486 mass-encrypt, the ES-spelled canary trip, T1490
# recovery-tool execs), the event chain, the injection correlator, the response fence
--- PASS: TestEventChain_OrderedChainFires (0.00s)
--- PASS: TestEventChain_SelfExecutingDropperFires (0.00s)
--- PASS: TestEventChain_DescendantConnectFires (0.00s)
--- PASS: TestRansomware_MacBridgeFileDatapathMassEncryptFiresT1486 (0.00s)
--- PASS: TestRansomware_MacBridgeCanaryHitESSpellingFiresT1486 (0.00s)
--- PASS: TestRansomware_MacBridgeDatapathExecsReachTheDetectorAndFireT1490 (0.00s)
--- PASS: TestESInjectionCorrelatorFiresAndDedups (0.00s)
    --- PASS: TestESInjectionCorrelatorMemoryShapes/remote_thread_fires_hollow (0.00s)
    --- PASS: TestESInjectionCorrelatorMemoryShapes/remote_thread_from_platform_binary_is_screened (0.00s)
    --- PASS: TestESInjectionCorrelatorMemoryShapes/W^X_mprotect_fires_rwx (0.00s)
--- PASS: TestDarrtProcessStopFencesThroughHandle (0.00s)
--- PASS: TestDarrtProcessStopPlatformBinaryRefusedBySigningAlone (0.00s)
--- PASS: TestDarCoreStartTicksFromTimeval (0.00s)
PASS
```

Three of those lines deserve a sentence each. The ransomware trio is the datapath
claim made real: mass-encryption pressure fed as bridged `file` events fires T1486,
a canary tripped under the exact resolved-path spelling Endpoint Security reports
fires the strongest signal, and recovery-tool execs riding the bridged `exec` stream
fire T1490. The injection lines are the correlator convicting on the memory shapes
ES reports in real time, remote-thread-create into a foreign task and a
writable-then-executable flip, while screening the same act when the actor is an
Apple platform binary. And the response lines are the process-stop identity fence
doing its one job: the (pid, start-ticks) identity is re-proven before every signal,
so a recycled pid, which always carries a different start time, is refused rather
than killed.

The full sensor service, the same binary an operator installs, also boots on the
machine and binds the root-only bridge socket the Swift daemons dial:

```
# the standard sensor service, started on the Mac
whisper: sensor: observing with 1 collector(s) -> /tmp/whisper-e2e/spool.ndjson
# the bridge socket, up, root-only
srwx------  1 root  daemon  0 Aug 26 06:42 /var/run/whisper-esclient.sock
```

And the Swift Endpoint Security client builds for release on the same machine, in
seconds:

```
$ swift build -c release
Building for production...
[11/12] Linking whisper-esclient
Build complete! (4.16s)
```

### The code-signing force-multiplier

One thing macOS gives the engines that Linux structurally cannot: on every event,
Endpoint Security attaches the kernel's own code-signing verdict for the acting
process, the team id, the signing id, the cdhash, the platform-binary fact and the
raw cs flags. A Linux exec event has no kernel-attested signing identity; here it
arrives free, on every message, and the engines already judge with it. The injection
correlator screens remote-thread creation from Apple platform binaries and treats an
ad-hoc image staging executable memory as the reflective-loader tell; the response
executor refuses to stop a platform binary on signing grounds alone; and absence is
treated as absence, a missing cs-flags fact is a non-signal, never a claim of
unsigned.
Both of the platform-binary judgments are in the passing lines above. The identity
itself is real and readable on the same machine, here via `codesign` on one of the
run's own detonation binaries:

```
# codesign(1) reading the identity of a detonation binary from the run
Identifier=payload
CodeDirectory v=20400 size=384 flags=0x20002(adhoc,linker-signed) hashes=9+0 location=embedded
CDHash=50a8e531f800f37e224934a9b18885a10c3909bc
Signature=adhoc
TeamIdentifier=not set
```

### The gate, proven, not asserted

The same machine also proves the one thing this page refuses to blur. The live
Endpoint Security feed requires Apple's ES entitlement plus a Developer ID signature
and notarization, and macOS enforces that in the kernel's integrity daemon, not in a
license agreement. We ran our real ES client, ad-hoc signed, on the SIP-enabled Mac
and let the OS answer:

```
# the real ES client, ad-hoc signed, on a SIP-enabled Mac: killed before main()
EXITED code=137
# the system log names the killer and the reason
amfid: .../whisper-esclient not valid: Error Domain=AppleMobileFileIntegrityError Code=-423
       "The file is adhoc signed or signed by an unknown certificate chain"
# control: the SAME binary, signed WITHOUT the ES entitlement, runs fine -
# the gate is the entitlement, not the code
usage: whisper-esclient [--socket PATH] [--stdout] [--file-events] [--memory-events] [--watch-dir DIR ...]
# and running it as root changes nothing, with the reason spelled out
amfid: Adhoc signed app with restricted entitlements detected
amfid: .../whisper-esclient not valid: Code=-424 "The file is adhoc signed but contains restricted entitlements"
```

That is the flip-on-approval shape, stated exactly. The engines, the bridge, the
wire contract and the service model are proven on real macOS today; the engines themselves are not yet fired on a live host, and the one missing
piece is Apple's signature on the ES client, and the capture above is the OS itself
saying so. When the entitlement lands, the same client is signed, notarized and
loaded, and the proven pipeline behind the socket starts receiving live events. No
new code path is waiting to be written on approval day.

## Beyond parity, built dark: the inline-block plane

Everything above is the observe plane, and observe-only is the sensor's posture
everywhere. On Linux the one blocking primitive is the opt-in exec guard, an
inode-scoped deny. macOS offers strictly more: an Endpoint Security AUTH client can
make the kernel suspend an exec, a signal or a file open until a verdict comes back,
a true inline block at the syscall boundary, with the kernel's code-signing identity
attached to the thing being judged, so a deny can name a cdhash and a contained pid
can be protected from tampering. We have built that plane, and it ships dark:

- Double-armed, dark by default. The zero state subscribes to nothing and denies
  nothing. Enabled alone is observe: every would-be deny is reported as a dry-run
  finding and the verdict is allow. Only enabled and armed together can deny, and
  arming additionally requires the live prerequisites: the ES entitlement, and on a
  development Mac, SIP off. The e2e above is the reason those prerequisites are
  real, not ceremonial: without them macOS will not even create the client.
- Fail open, always. An empty set, an unknown operation, a malformed subject, an
  unproven pid identity, an out-of-budget verdict: allow. A deny is only ever a
  positive, in-budget, armed match against a specific pre-identified subject, a
  known-bad cdhash or a pinned pid. No glob, no path class, no deny-all exists in
  the grammar.
- Never a platform binary. The same kernel-verified signing rail the observe engines
  use applies here with no exceptions: an Apple platform binary is allowed even on
  an armed, matching entry, because denying the wrong platform exec can take the box
  down.
- Twinned, so it cannot drift. The Swift guard and the Go core hold identical
  verdict functions, pinned to one shared decision-vector fixture; a divergence
  fails the build, the same discipline as the wire contract.

This is the beyond-parity tier: when armed, macOS gets a prevention surface deeper
than the Linux exec guard, and it inherits every safety rail that guard taught us.
The memory path stays dark until the entitlement lands; the file collector is proven on real Apple hardware, and on customer machines SIP stays on,
always.

## Install and service model

The service shape is boring on purpose, and identical in kind to every other
platform. Three pieces land on a Mac:

- The ES daemon installs at /usr/local/libexec/whisper-esclient under a launchd
  LaunchDaemon, run-at-load with keep-alive, as root because Endpoint Security
  requires it. When the bridge socket is absent, its NDJSON lands in its own log
  file instead of vanishing; when the bridge comes up, the daemon is kickstarted to
  reconnect. macOS additionally requires Full Disk Access (a TCC grant, Apple's
  consent, not ours) before an ES client may be created.
- The DNS proxy ships as an ordinary app in /Applications that drives
  system-extension activation of the embedded DNS-proxy extension through the OS's
  own approval flow. It is a system extension, user-space by definition; there is no
  kext.
- The Go bridge rides the normal `whisper service install --sensor`, which renders a
  launchd unit on macOS the same way it renders a systemd unit on Linux. Same binary,
  same service manager abstraction, same commands an operator already knows.

And the caveats, stated as two gates rather than a footnote:

| Posture | What works | The gate |
|---|---|---|
| Developer and internal, today (current) | Building, testing, running the sensor service and exercising the engines over the bridge datapath on a stock Mac with SIP fully enabled, and the August 2026 run above did exactly that, in compiled arm64 datapath tests rather than a live host firing. Running the live ES client does not: macOS kills an ad-hoc-signed ES client before it starts, and the capture above is that kill, verbatim. A dev Mac that needs the live feed takes a one-time SIP relaxation plus system-extension developer mode; that is our internal harness, and nothing from it ships to customers. | Apple's platform policy on unsigned ES clients, by design. We work inside it, not around it. |
| Customer ship (gated) | SIP stays fully on. Delivered as a signed, notarized, stapled package under a Developer ID. | The Apple Endpoint Security entitlement, a restricted, Apple-approved capability, application in progress. Externally clocked, not an engineering gap: the engines are exercised in compiled arm64 datapath tests, the contract suite and the install model already exist, and approval flips the live feed on. |

## Where macOS stands

The status vocabulary here is the same one [Platform support](/docs/platforms)
defines, and it is exact. *Live* means shipped, and for Linux and Windows, measured
on a real box. *In development* means built, contract-tested and, for macOS,
exercised over the bridge datapath in compiled arm64 tests, but not yet customer-installable. macOS is in
development: the engines are exercised over the bridge datapath in compiled arm64 tests, not fired by a live feed, the wire
contract is byte-proven, and the live Endpoint Security feed waits on Apple's ES
entitlement, killed by the OS itself until then. We never round that up, on this
page or anywhere else.

- Live on a Mac today: the verifiable /128 identity, keyless verification with stock
  tools, graph-first resolution over DoH/DoT or a dedicated /128 resolver,
  per-device policy, metered egress, and the off-host kill-switch. None of these
  involves the entitlement, because none of them is an app.
- Exercised over the bridge datapath, August 2026 (the exec, file and socket collectors are proven on real Apple hardware; the /dev/bpf qname tap needs root and is exercised through a scripted read seam rather than a live device; the engines are exercised in datapath tests rather than fired by a live Endpoint Security feed): the ransomware correlator (T1486 and T1490),
  the process-injection correlator (T1055), the event chain and the fenced response
  tier. Separately, and this part IS proven on real hardware: the exec collector runs unentitled on a real Mac and reads the kernel's code-signing identity (signing id, cdhash, platform-binary, CS flags) where the kernel will give it; the sensor service boots
  and binds the bridge socket on the same machine.
- Entitlement-gated, flips on approval: the live ES observe feed (`exec`, `file`,
  `dns`, `memory`) as a customer install, an ad-hoc build is killed by the OS and we
  publish the kill line; and, one gate further, the ES-AUTH inline-block plane,
  built dark, arming only with the entitlement and, on a dev Mac, SIP off. `conn`
  has macOS covered by the entitlement-free libproc socket sampler.
- Measured in production elsewhere, by policy: the engine runs we publish as
  production proof are Linux and Windows runs. The day a live entitlement-signed
  macOS feed produces one, this page carries it.

What should convince you is the shape, not a promise. This is an EDR whose
containment does not live on the endpoint, so the platform Apple gates most tightly
is also the platform where our core protection loses nothing by waiting. And when
the on-host half does ship, it is not a fork and not a kext: it is the same Go brain
already exercised on Apple-silicon over the bridge datapath (the exec, file and socket collectors are proven on real Apple hardware; the /dev/bpf qname tap needs root and is exercised through a scripted read seam rather than a live device; the engines are exercised in datapath tests rather than fired by a live Endpoint Security feed), behind Apple's own frameworks, with a
CI-enforced proof that the Mac's telemetry was never a dialect, waiting only for
Apple's signature to start listening live.
