Coding Agent Protection — Observers & Events
pavri-endpointd (the Pavri endpoint sensor) observes coding-assistant activity on managed developer endpoints and emits structured events to the Pavri gateway. This page describes what the sensor detects, the five event variants it emits, and how to query them in ClickHouse.
Policy enforcement and dashboard surfacing for these events land in CAP-1.5 and CAP-1.6 respectively.
What CAP detects
The sensor uses OS-native primitives to observe three categories of activity:
- Process spawns — every process exec on the endpoint is captured (macOS: Endpoint Security
ES_EVENT_TYPE_NOTIFY_EXEC; Linux: eBPFsched_process_exectracepoint). The sensor walks the process ancestry to attribute the exec to a known coding assistant by binary signature, code-signing identity, and install path. - File reads and writes near sensitive paths — file I/O on configured sensitive paths (credential stores, workspace instruction files,
/etc, git directories, etc.) is captured (macOS: ESES_EVENT_TYPE_NOTIFY_OPEN/WRITE; Linux: eBPF LSM hooks onsecurity_file_open/vfs_write). Events are attributed to the assistant that owns the process chain. - Assistant discovery events — when the sensor first encounters a known assistant binary (by signature match), it emits a one-time discovery event recording the install location, code-signing identity, and workspace context. This keeps the Pavri assistant inventory current without requiring a separate scanner.
Event variants emitted to the gateway
All events travel to the gateway as EventEnvelope protobuf messages (reusing the existing Pavri telemetry envelope). Five new payload variants are introduced in CAP-1.2:
| Variant | Proto field | Description |
|---|---|---|
CodingActionEvent | EventEnvelope.coding_action | A process exec, file read/write, shell command, git operation, MCP tool call, or other assistant-attributed action. The kind field distinguishes action types (shell_exec, file_read, file_write, net_connect, git, mcp_tool_call, code_handoff, clipboard). |
AssistantDiscoveryEvent | EventEnvelope.assistant_discovery | Sensor detected a new assistant install on this endpoint. Emitted once per (kind, binary_path, code_signing_authority) tuple. Used to populate and refresh the assistant inventory in Pavri. |
CodingSessionEvent | EventEnvelope.coding_session | Session lifecycle event (started, closed, suspended, resumed). For CAP-1.2 the sensor emits session events; dashboard surfacing of coding sessions lands in CAP-1.6. |
ConfigDriftEvent | EventEnvelope.config_drift | Workspace instruction source changed (CLAUDE.md, .cursorrules, .windsurfrules, etc. modified since last scan). Used by the detection cascade to flag instruction-poisoning candidates. Full detection integration lands in CAP-1.4. |
EndpointPostureEvent | EventEnvelope.endpoint_posture | Periodic posture snapshot from the sensor: current observer health, drop counters, cert expiry. Supplements the heartbeat and does not insert a row in coding_events. |
ClickHouse query examples
Events land in the coding_events table (MergeTree, partitioned by month, 90-day TTL). AssistantDiscoveryEvent and EndpointPostureEvent do not insert coding_events rows; they are available via NATS JetStream subjects only.
Note: The
assistant_kindandworkspace_idcolumns onCodingActionEventrows are empty in CAP-1.2. They are populated in CAP-1.5 when the gateway gains access to the session record for full attribution enrichment.
-- Top assistants seen in the last hour (by session event rows)
SELECT assistant_kind, count() AS event_count
FROM coding_events
WHERE at > now() - INTERVAL 1 HOUR
AND assistant_kind != ''
GROUP BY assistant_kind
ORDER BY event_count DESC;
-- File-write events to sensitive paths in the last 24 hours
SELECT
endpoint_id,
session_id,
path_or_target,
verdict,
at
FROM coding_events
WHERE kind = 'file_write'
AND at > now() - INTERVAL 24 HOUR
AND (
path_or_target LIKE '/etc/%'
OR path_or_target LIKE '%/.ssh/%'
OR path_or_target LIKE '%/.aws/%'
)
ORDER BY at DESC
LIMIT 100;
-- Shell exec events denied by policy in the last 7 days
SELECT
endpoint_id,
path_or_target,
verdict_reason,
at
FROM coding_events
WHERE kind = 'shell_exec'
AND verdict = 'denied'
AND at > now() - INTERVAL 7 DAY
ORDER BY at DESC;
-- Event counts by kind for a specific session
SELECT kind, count() AS n
FROM coding_events
WHERE session_id = 'your-session-id'
GROUP BY kind
ORDER BY n DESC;
Column reference for coding_events:
| Column | Type | Notes |
|---|---|---|
org_id | LowCardinality(String) | Tenant identifier. |
endpoint_id | String | Sensor's stable endpoint identifier. |
session_id | String | Coding session UUID; empty for non-session rows. |
action_id | String | Per-action UUID; empty for session lifecycle rows. |
kind | LowCardinality(String) | Action type: shell_exec, file_read, file_write, git, mcp_tool_call, session.started, session.closed, config_drift, etc. |
assistant_kind | LowCardinality(String) | Assistant identifier (e.g., claude_code, cursor). Empty in CAP-1.2 for action rows; populated in CAP-1.5. |
workspace_id | String | Workspace root path. Empty in CAP-1.2 for action rows; populated in CAP-1.5. |
process_pid | UInt32 | PID of the observed process. |
process_binary_hash | String | SHA256 of the observed binary. |
path_or_target | String | File path, shell command, git remote, or MCP server identifier. |
byte_count | UInt64 | Bytes read or written (file events only). |
verdict | LowCardinality(String) | Policy enforcement decision: allow, denied, approved, warn. Empty until policy enforcement lands in CAP-1.5. |
verdict_reason | String | Human-readable reason for the policy decision. |
human_in_loop | UInt8 (bool) | Whether a human approval was recorded for this action. |
detection_ids | Array(LowCardinality(String)) | Detection IDs from the T1/T2/T3 cascade (populated in CAP-1.4+). |
at | DateTime64(3, 'UTC') | Event timestamp (millisecond precision). |
labels | Map(LowCardinality(String), String) | Arbitrary key-value metadata attached by the sensor or gateway. |
Full dashboard surfacing of coding events lands in CAP-1.6.