CONTENTS
0 scope
1 the install layout
2 hot-reload vs restart
3 driver signing in practice
4 the agent as a windows service
5 config — one file, two halves
6 server side — docker compose
7 what this series does not cover
──[ 0. Scope ]──
The last ten parts covered the codebase. This one is the operational pocket
reference: where binaries live on a host, what an operator can change without
restarting which service, and what is required to ship a build to a real
endpoint.
It is also the wrap-up. The narrative arc ends here.
──[ 1. The Install Layout ]──
C:\Program Files\WazabiEDR\
├── WazabiEDR_Driver.sys (loaded by service WazabiEDR)
├── WazabiEDR_Driver.inf
├── WazabiEDR_Agent.exe (service WazabiEDR_Agent)
├── wedr-plugin.exe (operator CLI, not a service)
└── plugins\
├── WazabiEDR_Plugin_DefenderBridge.exe
└── … (other vendored plugins)
C:\ProgramData\WazabiEDR\
├── agent.json (config, world-readable)
├── spool\
│ ├── active.ndjson
│ ├── batch-001.ndjson.zst
│ └── plugins\
│ ├── active.ndjson
│ └── batch-001.ndjson.zst
└── plugins\
└── <plugin_id>.json (manifest, ACL: admin write)
Two roots, deliberately separated. `Program Files` holds binaries that should be
read-only at runtime and replaceable only by an installer with elevated
privileges. `ProgramData` holds state that can be modified at runtime by the
agent itself.
`%ProgramData%WazabiEDRplugins` has the manifest ACL described in Part 9 —
Administrators write, everyone reads. The spool directory has a looser ACL (the
agent's service account writes; only Administrators read, because batches
contain event content that may include sensitive paths or values).
──[ 2. Hot-Reload vs Restart ]──
change effect
────────────────────────────── ────────────────────────────────
wedr-plugin enroll / revoke / update hot reload within 5 s
wedr-plugin auto-launch flag requires agent restart
agent.json edits require agent restart
driver binary replacement requires driver + agent restart
(in practice, requires reboot)
plugin binary replacement requires plugin restart;
doctor flags the sha256 drift
server image redeploy zero-downtime via rolling
restart of the API container
The hot-reload set is small but it covers the operations performed routinely
(enrolment, revocation, update after rebuild). Everything else is an explicit
restart because the implementation simplicity is high and the cost is low — an
agent restart loses no events thanks to the on-disk spool.
The driver is the worst case. Replacing a loaded `.sys` while an agent has an
open handle on the device is technically supported (`SERVICE_STOP_PENDING` →
unload → start new) but very rarely worth the operational risk. The standard
practice is to schedule a reboot.
──[ 3. Driver Signing in Practice ]──
Test posture for local development:
bcdedit /set testsigning on
bcdedit /set hypervisorlaunchtype off # if HVCI is enabled
`testsigning` (the Boot Configuration Data option enabling Test Signing Mode)
allows the kernel loader to accept drivers signed by a non-Microsoft,
non-WHQL-cross-signed certificate, including self-signed and (in some
configurations) unsigned. HVCI (Hypervisor-Protected Code Integrity, Part 1)
imposes stricter rules on top and must be off for test-signed drivers.
Production posture is fundamentally different. Any driver installed on
third-party endpoints must be signed by a certificate that the kernel loader
trusts without test signing. For kernel-mode drivers this requires either:
- EV Code Signing certificate + Microsoft Hardware Dev Center
attestation signing (the modern path), or
- WHQL cross-signing through the Hardware Compatibility
Program (the older path)
WHQL (Windows Hardware Quality Labs: Microsoft's hardware compatibility
certification programme, used as the trust anchor for production-signed
third-party kernel drivers) submission is a non-trivial operational step. It
requires a Microsoft Partner Center account, an EV cert from a recognised CA, a
submission package that includes the driver, the `.inf`, the catalog, and a
passing HLK test run. The current WazabiEDR driver is not WHQL-signed; the v1
deployment posture is test-signed VMs and lab machines, not third-party
endpoints.
──[ 4. The Agent as a Windows Service ]──
The agent is registered with the Service Control Manager via `sc.exe`:
sc.exe create WazabiEDR_Agent `
binPath= "\"C:\Program Files\WazabiEDR\WazabiEDR_Agent.exe\"" `
start= auto `
depend= WazabiEDR
`depend= WazabiEDR` declares a dependency on the driver service — the SCM starts
the driver first, and the agent only starts if the driver has loaded. The
dependency simplifies failure attribution: a driver-load failure produces a
clear "the dependency service or group failed to start" rather than an agent
that starts and then dies on `CreateFileW(\.WazabiEDR)`.
The service runs as `LOCAL_SYSTEM` (the highest-privilege built-in service
account). The justification is two specific accesses: the driver device's ACL
grants access only to `SYSTEM`, and the manifest directory is in a path whose
effective read access requires walking through directories owned by `SYSTEM`.
`LOCAL_SERVICE` would be a tighter principal but lacks the rights for the driver
IOCTL.
Service logs are written to the Windows Event Log under a registered source
(`WazabiEDR_Agent`). The Event Log is the standard sink: viewable in Event
Viewer, forwardable to any SIEM the operator already has via WEF (Windows Event
Forwarding), and managed by the OS with its own rotation policy. Maintaining a
separate plain-text log file would duplicate functionality the OS already
provides.
──[ 5. Config — One File, Two Halves ]──
`%ProgramData%WazabiEDRagent.json` is the single configuration surface. Two
top-level sections:
{
"agent": {
"console_output": false,
"spool_dir": "C:\\ProgramData\\WazabiEDR\\spool",
"max_bytes_per_file": 16777216,
"max_age": "1h",
"max_total_bytes": 1073741824,
"channel_capacity": 4096,
"zstd_level": 6
},
"shipper": {
"url": "https://wazabi.example.com/api/v1/agents/checkin",
"agent_token": "…",
"hmac_secret": "…",
"client_cert": "C:\\ProgramData\\WazabiEDR\\agent.crt",
"client_key": "C:\\ProgramData\\WazabiEDR\\agent.key",
"ca_bundle": "C:\\ProgramData\\WazabiEDR\\ca.pem"
}
}
The `shipper` block is optional. Omitting it puts the agent in spool-only mode
(useful for air-gapped trials and for development). The `agent` block has
documented defaults for every key; missing keys fall back to the default,
unknown keys are logged as warnings rather than errors so a newer agent does not
refuse an older config.
The forward-compatible policy has one trade — a typo on a known key is also a
warning, not an error, because the config parser cannot distinguish "unknown new
key" from "misspelled old key". Operators who want strictness can run
`WazabiEDR_Agent --check-config` as a separate operation that validates against
the current schema.
──[ 6. Server Side — Docker Compose ]──
The server repository ships a `docker-compose.yml` that brings up four
containers:
┌────────────────────┐
│ wazabi-api │ uvicorn workers behind a reverse proxy
│ wazabi-postgres │ state
│ wazabi-opensearch │ events + alerts
│ wazabi-redis │ cache + command queue
└────────────────────┘
`make up` brings the whole stack online. Database migrations run at
API-container startup via Alembic; the first start populates the OpenSearch
index templates (Part 6). For redeploys, `docker compose pull && docker compose
up -d` performs a rolling restart of the API container while leaving the three
stateful containers (Postgres, OpenSearch, Redis) running — those persist via
named volumes and are only restarted for engine-version upgrades.
Production deployments would substitute managed equivalents for the three
datastores (a managed Postgres for state, a managed Elasticsearch / OpenSearch
for events, a managed Redis for cache). The Compose file is the reference layout
and the developer environment; it is not a production deployment template.
──[ 7. What This Series Does Not Cover ]──
For honesty's sake:
The console UI is out of scope. There is a separate frontend project that calls
the server's API; it is not part of this codebase and not covered here.
The detection rule engine is out of scope. The server's `rules` endpoints are
stubs; the "Waza" rule language referenced in the server's `ARCHITECTURE.md` is
a separate project on a separate timeline. Telemetry collection (this series)
and alert generation are distinct product layers.
Packaging into an MSI installer signed for enterprise deployment is out of
scope. The repositories produce `.exe` and `.sys` artefacts; wrapping them in an
MSI that an enterprise administrator can deploy via Group Policy is a further
layer not covered by this codebase.
──[ end of series ]──
Part 0 ── Intro
Part 1 ── Driver skeleton (KMDF in Rust)
Part 2 ── The five kernel callbacks
Part 3 ── Wire format
Part 4 ── Ring buffer and inverted-call IOCTL
Part 5 ── User-mode agent
Part 6 ── Server (FastAPI + Postgres + OpenSearch + Redis)
Part 7 ── Plugin protocol
Part 8 ── Plugin SDK
Part 9 ── Manifest CLI
Part 10 ── Defender Bridge
Part 11 ── Packaging and deploy ◀ you are here
The intent of the series was to walk through a working EDR endpoint stack from
the kernel up, with the design decisions surfaced explicitly. The codebase is
not feature-complete against any commercial EDR — there is no production
console, no detection rule engine, no WHQL signing. What it has is a working
ingestion pipeline, every component readable by a developer who wants to
understand what an EDR actually does, and a set of decisions documented well
enough to extend without guessing.
The seven repositories at lululufr/WazabiEDR_* are the canonical reference. Each
has an `ARCHITECTURE.md` that documents its module in depth. This series was the
cross-repo narrative tour.