Coding Agent Protection — Sensor Deployment
This page covers building and running pavri-endpointd and configuring it via environment variables. Linux .deb and .rpm packages are available from the Linux packaging tree, Windows MVP deployment uses the Intune Win32 path documented below, and macOS uses the signed .pkg release path when signing credentials are present.
MDM rollout? If you are deploying
pavri-endpointdvia an MDM, start with the MDM rollout overview and your vendor's runbook (Jamf / Intune / Kandji / Workspace ONE). The reference below is for env-var and command-line use.
Per-OS install commands
macOS
go build -o /usr/local/bin/pavri-endpointd \
./services/endpointd/cmd/endpointd
Requires Go 1.22+. The binary links against CGO for the Endpoint Security framework bindings; Xcode Command Line Tools must be installed.
Apple Endpoint Security entitlement: Production-grade process and file observation on macOS requires an Apple Endpoint Security (ES) entitlement. This entitlement must be provisioned in your Developer ID certificate by Apple. Obtaining and configuring this entitlement is deferred to CAP-1.7, which will deliver a notarized, signed package.
Without the entitlement the macOS observer reports
StatusUnavailableat startup and the sensor degrades to basename-only attribution: it can still attribute processes by binary path and basename from the working directory, but cannot inspect the full process ancestry via ES authorization events. The sensor continues to run and send heartbeats; posture remainshealthy.
Linux
go build -o /usr/local/bin/pavri-endpointd \
./services/endpointd/cmd/endpointd
For package installs, build the native package first:
./services/endpointd/packaging/linux/build-binaries.sh
./services/endpointd/packaging/linux/build-deb.sh # Debian/Ubuntu
./services/endpointd/packaging/linux/build-rpm.sh # Fedora/RHEL-family
Then install the artifact and edit /etc/pavri-endpoint/endpointd.env:
sudo dpkg -i dist/linux/pavri-endpointd_<version>_amd64.deb
sudo rpm -Uvh dist/linux/pavri-endpointd-<version>-1.x86_64.rpm
sudo systemctl enable --now pavri-endpointd
The packages seed /etc/pavri-endpoint/endpointd.env with mode 0600,
install the systemd unit, and do not auto-enable the service before the gateway
URL and one-time enrollment token are set. Ubuntu .deb install/enroll/observe
has been live-verified. RPM packages have PR-safe structural checks plus a
nightly/manual live Fedora and RHEL-family userspace harness under
tests/e2e/endpoint-linux-rpm/; do not claim Fedora/RHEL production validation
until that live job or an equivalent VM run is green. The hosted workflow uses
privileged systemd containers, which are valid RPM userspace/scriptlet smoke
targets but not RHEL kernel/eBPF validation.
The eBPF probes require either root or the following Linux capabilities on the sensor process:
CAP_BPF— load and run BPF programs.CAP_PERFMON— attach to perf events and tracepoints (required alongsideCAP_BPFon kernel 5.8+).
Alternatively, run the sensor as root (sudo). Kernel 6.x or higher is recommended for LSM hook support; the sensor degrades gracefully to tracepoints-only on older kernels.
For live eBPF integration tests on a macOS workstation, use the Lima VM. See docs/lima-setup.md in the repository root.
make lima-up # provision Ubuntu 24.04 VM
make lima-test-ebpf # run eBPF tests inside the VM
Environment variables
All configuration is read from the process environment. No config file is required.
| Variable | Default | Purpose |
|---|---|---|
PAVRI_GATEWAY_URL | (required) | Base URL of the Pavri gateway service (e.g., https://gateway.example.com). Used for enrollment, heartbeat, and telemetry uploads. |
PAVRI_ENROLLMENT_TOKEN | (required for enrollment) | One-time enrollment credential. Mint this in the Pavri dashboard under Settings > Endpoints > Add Endpoint. |
PAVRI_ENDPOINT_STATE_DIR | /var/lib/pavri-endpoint | Directory where the sensor stores its enrollment state (TLS certificate, private key, endpoint_id). Disk persistence ships in CAP-1.7; in CAP-1.2 this is in-memory only. |
PAVRI_ENDPOINT_HEARTBEAT_INTERVAL_S | 60 | Seconds between heartbeat POSTs to the gateway. Decrease for faster posture updates; increase to reduce gateway load. |
PAVRI_ENDPOINT_SENSOR_VERSION | 0.1.0 | Reported sensor version. Overridden by the build system when packaging a release build. |
PAVRI_OBSERVER_ENABLED | true | Set to false to run in heartbeat-only mode. No observer pipeline is started; no coding events are emitted. Useful for enrollment-only rollouts or low-privilege environments. |
PAVRI_OBSERVER_CONCURRENCY | 4 | Number of attribution worker goroutines. Increase on high-event-rate endpoints (e.g., build servers). |
PAVRI_OBSERVER_MAILBOX_CAPACITY | 1024 | Per-observer bounded mailbox depth. Increase if the sensor logs drop warnings under normal load. |
PAVRI_TELEMETRY_BATCH_SIZE | 50 | Number of event envelopes that triggers an immediate flush to the gateway. |
PAVRI_TELEMETRY_FLUSH_INTERVAL_S | 5 | Maximum seconds between telemetry flushes regardless of batch fill. |
macOS Apple Endpoint Security
Full process and file observation on macOS requires a Developer ID certificate provisioned with the com.apple.developer.endpoint-security.client entitlement. Apple grants this entitlement through a review process; the application is separate from standard Developer ID provisioning.
Until CAP-1.7 delivers the signed, entitlement-bearing package:
- The macOS process observer starts and immediately returns
ErrNotEntitled. - The supervisor logs
observer unavailable: observer: macOS Endpoint Security entitlement requiredand marks the observerStatusUnavailable. - The sensor continues to run with heartbeat and basename-only attribution from working-directory inspection.
- Posture is reported as
healthy; no alert is raised.
Operators can verify the degraded state by querying the gateway's endpoint heartbeat records for observer_status: unavailable.
Linux eBPF capabilities
The Linux process observer (process_observer_linux.go) and file observer (file_observer_linux.go) load eBPF programs at startup. The required Linux capabilities are:
| Capability | Required for |
|---|---|
CAP_BPF | Loading BPF programs and maps. |
CAP_PERFMON | Attaching to tracepoints and perf events. Introduced in kernel 5.8; before 5.8 these operations required CAP_SYS_ADMIN. |
CAP_SYS_ADMIN | Alternative to CAP_BPF + CAP_PERFMON on kernels < 5.8. Also required for LSM hook attachment on some distributions. |
If these capabilities are absent, the observers return ErrInsufficientCapabilities and the supervisor degrades gracefully (same pattern as the macOS entitlement case).
Recommended approach: run the sensor with sudo during development and via the
packaged systemd unit in production. The unit grants
CAP_BPF CAP_PERFMON CAP_NET_ADMIN CAP_SYS_ADMIN CAP_DAC_READ_SEARCH CAP_KILL,
uses ProtectSystem=full and ProtectHome=read-only, and limits writable paths
to the endpoint state/runtime directories, /tmp, and
/usr/local/lib/pavri.
Kernel requirement: 6.x or higher for LSM hook support (security_file_open / vfs_write). The sensor falls back to tracepoints on older kernels. Ubuntu 24.04 ships kernel 6.8 and is the reference platform for eBPF testing.
Local MCP Broker
The MCP broker is the sensor subsystem that intercepts MCP tool-call traffic between assistants (Claude Desktop, Cursor, VS Code, JetBrains, Gemini CLI, Windsurf) and their configured MCP servers. It rewrites each assistant's MCP config to point at a sensor-managed endpoint, transparently spawns or proxies the original upstream, and evaluates each tools/call against the active policy snapshot before forwarding.
For the broker to function, the sensor needs:
| Requirement | Reason |
|---|---|
| Filesystem write access to the assistant config files | To rewrite (and back up) claude_desktop_config.json, ~/.cursor/mcp.json, .vscode/mcp.json, JetBrains mcp.xml, ~/.config/gemini/settings.json, and Windsurf's mcp.json. |
| UDS support for STDIO upstreams | macOS / Linux: bind a UDS under the sensor's state directory. Windows STDIO support is gated to CAP-2.1; the rest of the broker compiles cleanly on Windows. |
| Loopback ports for HTTP+SSE-legacy / Streamable HTTP upstreams | Default range 127.0.0.1:7878-7888. |
| Read access to the assistant's environment for upstream spawn | The broker captures env key names at rewrite time and re-reads values at upstream-spawn time, so secrets never persist on the manifest. |
Drift handling
If a rewritten config changes on disk, the sensor's fsnotify watcher reads the new content, queries the CAP-1.2 file observer for the most recent attributed writer, and emits a ConfigDriftEvent with one of these drift_kind values:
drift_kind | When it fires | Sensor response |
|---|---|---|
user_edit | A non-assistant process (or no observed writer within the attribution window) wrote the change. | Emit only. The broker does not auto-clobber a user's manual edit in CAP-1.3; re-rewrite-on-revert lands in CAP-1.5. |
assistant_self_modification | The writer chain matches a registered assistant binary. | No clobber. Emit and preserve the on-disk content. This is D-17 evidence: a coding agent rewriting its own MCP server list, plausibly to escape the broker chokepoint. |
schema_unknown | The new content does not parse against the rewriter's schema. | Emit only. |
restore_verification_failed | A Restore attempt produced content whose SHA does not match the manifest's OriginalContentSHA256. | Emit only. |
This ensures the sensor never silently undoes a legitimate user change while still catching the prompt-injection / supply-chain case where an assistant rewrites its own MCP server list to escape the broker chokepoint.
Live integration tests
The sensor ships a Lima-based live test harness that drives the broker against a real @modelcontextprotocol/server-filesystem upstream and asserts a runtime policy hot-swap takes effect on the next tools/call within 2 seconds:
make lima-up-mcp # provision Ubuntu 24.04 + Node 20 + Python (first run ~5 min)
make lima-test-mcp # install fixtures host-side, run mcplive-tagged tests in VM
make lima-down-mcp # destroy the VM
See MCP Broker for the full operator reference and Policy authoring (raw) for the policy bundle JSON shape.
Windows (CAP-2.1+)
Installation
The sensor ships as pavri-endpointd.exe plus Intune/Win32 staging artifacts
under services/endpointd/packaging/windows. This is the accepted MVP Windows
installer deliverable; a full MSI is not required for MVP. The installer stages
pavri-endpointd.exe and endpointctl.exe under
C:\Program Files\Pavri\pavri-endpointd and registers PavriEndpointd as
an automatic-start Windows service. Service account: LocalSystem (required
for ETW kernel providers and SeSystemProfilePrivilege).
Enrollment values are delivered by MDM to HKLM\SOFTWARE\Pavri; the Intune
install script writes them to machine-level environment variables before
starting the service and also writes the service-specific
HKLM\SYSTEM\CurrentControlSet\Services\PavriEndpointd\Environment
REG_MULTI_SZ block before Start-Service. PAVRI_GATEWAY_URL and
PAVRI_ENROLLMENT_TOKEN drive real gateway enrollment and endpoint health
heartbeats; endpoint state is persisted under
C:\ProgramData\Pavri\Endpoint.
.\services\endpointd\packaging\windows\build-binaries.ps1 -Arches amd64
IntuneWinAppUtil.exe -c dist\windows\pavri-endpoint-windows-amd64 -s install.ps1 -o dist\windows
Observer coverage
| Observer | Mechanism | Status in CAP-2.1 |
|---|---|---|
| Process | ETW Microsoft-Windows-Kernel-Process event 1 | At parity with macOS+Linux |
| Net | ETW Microsoft-Windows-Kernel-Network events 12 (TCPv4 connect), 28 (TCPv6 connect) | At parity |
| File | ETW Microsoft-Windows-Kernel-File events 12/15/16 | Best-effort, read-only. No enforcement. The minifilter driver path that would enable deny-write semantics is deferred to CAP-4.8 and requires WHQL signing. |
File observer gap (deferred to CAP-4.8)
CAP-2.1 ships ETW file observation only. ETW gives us visibility — read,
write, and create events are recorded and emitted as CodingActionEvent
with kind = KIND_FILE_READ / KIND_FILE_WRITE. ETW does not give us
enforcement — there is no kernel-level path to deny a write before it
reaches the disk.
The enforcement path requires a minifilter driver (Filter Manager
callbacks at IRP_MJ_CREATE / IRP_MJ_WRITE). A minifilter is a
kernel-mode component, so it needs:
- A code-signing certificate accepted by the Windows Hardware Certification.
- WHQL "attest signing" via the Microsoft Hardware Dev Center Dashboard (free for an EV cert holder; turnaround ~1 business day).
- A
.catfile embedded in the driver package (signed by Microsoft).
The minifilter or equivalent supported chokepoint is tracked for CAP-4.8. The CAP-2.1 WindowsFileObserver
emits read/write events with the same envelope shape as the Linux/macOS file
observers, so detection-side rules function correctly — they observe and warn
but do not deny. Operators who require deny-on-Windows during CAP-2.1
should pair the sensor with an existing EDR product or accept the gap.
Signing the user-mode service binary (Authenticode)
The user-mode binary pavri-endpointd.exe needs an Authenticode signature for
SmartScreen reputation and to satisfy enterprise software allowlisting policies.
The Win32 package should contain that signed binary; Intune manages package
delivery and integrity.
Required: an EV (Extended Validation) Code Signing Certificate from a CA accepted by the Microsoft Trusted Root Program (DigiCert, Sectigo, GlobalSign, etc.). EV is required to seed SmartScreen reputation in a reasonable time window — non-EV certs accumulate reputation slowly.
Build-side signing pipeline:
signtool.exe sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a /v pavri-endpointd.exe
The signing pipeline runs in a separate, certificate-bearing CI job;
public-PR CI jobs run unsigned (the cross-compile gate in
.github/workflows/cap-2-1-windows.yml does not sign).
Privilege requirements
The sensor's Windows service must run as LocalSystem (or a service
account with SeSystemProfilePrivilege and SeAuditPrivilege). ETW kernel
providers reject session creation otherwise. The Intune Win32 installer configures
this; operators running the binary interactively for development must open
an elevated command prompt (Run as Administrator).
CAP-2.3: JetBrains plugin and CLI shims
CAP-2.3 ships two enrichment clients of the sensor:
- JetBrains plugin — see
jetbrains-plugin.md. Captures chat-vs-user edit attribution from JetBrains AI Assistant inside any IntelliJ Platform IDE. - CLI shims — see
cli-shim-deployment.md. PATH-injected wrapper binary covers Claude Code, Gemini CLI, and Aider on macOS / Linux / Windows.
Both clients post to the sensor's local UDS / named pipe; neither talks to backend services directly.