agentguard-edr-bridge
v0.1.0
Published
Wazuh → AgentGuard correlation worker: polls a Wazuh indexer, normalizes OS-level alerts, and correlates them with the tool calls your AI agents made through the AgentGuard gateway (pid lineage / host+net / time window). Read-only toward the EDR.
Maintainers
Readme
@agentguard/edr-bridge
The EDR → AgentGuard correlation worker. It pulls OS-layer telemetry an EDR observed (currently
Wazuh) and correlates it to the tool calls AgentGuard already recorded, writing edr_events +
correlations to Supabase for the console to render.
This is the EDR→AgentGuard direction: AgentGuard pulls what the EDR saw. It is read-only toward the EDR (no active response) and runs off the gateway's blocking path — if the bridge is down, enforcement and tool-call logging are unaffected; only correlation is missing.
How it works
Wazuh indexer ──poll──▶ WazuhSource ──normalize──▶ OsEvent[]
│
recent tool_calls (Supabase) ─────────────┐ ▼
└──▶ correlate() ──▶ correlations + orphans ──▶ SupabaseWazuhSource(src/wazuh/source.ts) — queries the Wazuh indexer (OpenSearch)wazuh-alerts-*over a timestamp range and normalizes each hit via the adapter. Implements the EDR-agnosticEdrSourceinterface — swap EDRs by writing a new source.normalizeWazuhAlert(src/wazuh/adapter.ts) — defensively maps an (untrusted) Wazuh alert to a normalizedOsEvent(auditd process / syscheck file / decoded network / rule-only).correlate(src/correlate.ts) — the security-critical core. Per OS event, against same-host tool calls within the time window:pid_lineage(0.9) — the event's pid is the call's upstream-server PID or a descendant.host_net(0.7) — the event's dst IP appears in the call args.time_window(0.3–0.6) — fallback when lineage can't be computed. Shared-PID calls are disambiguated by the closest call in time. Unmatched events become orphans (possible gateway bypass).
runCycle/startLoop(src/worker.ts,src/run-loop.ts) — sliding-window poll;SupabaseStore(src/store-supabase.ts) dedupes by(source, external_id)and persists.
Run
From npm (no source access needed):
AGENTGUARD_WAZUH_INDEXER_URL=https://<indexer>:9200 \
AGENTGUARD_WAZUH_INDEXER_USERNAME=<read-only user> \
AGENTGUARD_WAZUH_INDEXER_PASSWORD=<password> \
NEXT_PUBLIC_SUPABASE_URL=https://<project>.supabase.co \
SUPABASE_SERVICE_ROLE_KEY=<service-role key> \
npx agentguard-edr-bridgeFrom this repo:
pnpm --filter agentguard-edr-bridge build
node packages/edr-bridge/dist/bin/edr-bridge.jsRequired env (see apps/dashboard/.env.local.example for the full list):
AGENTGUARD_WAZUH_INDEXER_URL, AGENTGUARD_WAZUH_INDEXER_PASSWORD, and a Supabase URL +
service-role key (AGENTGUARD_SUPABASE_* or the NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY
fallbacks). For the full Wazuh stack, use the harness: docker compose -f docker-compose.edr.yml up
(see docker/edr/README.md).
Test
pnpm --filter agentguard-edr-bridge testCovers the correlation engine (table-driven), the Wazuh adapter (fixtures), config parsing, and an
end-to-end source → adapter → worker → correlate smoke on fixture alerts — no live Wazuh required.
Notes
- Self-contained types (
src/types.ts) mirror@agentguard/shared— like the gateway, the bridge avoids a build-time dependency on shared (which has no dist build). The canonical contract and Supabase row shapes live in@agentguard/shared+ the SQL migrations. - Tool args and EDR fields are untrusted data — never interpreted as instructions.
