12 KiB
LitterBox Roadmap — Elastic EDR Integration
Overview
Goal: let LitterBox dispatch a payload to a separate user-managed Windows VM that runs Elastic Defend, then query the user's local Elastic stack for any detection alerts raised against that execution.
Out of scope (user-managed):
- Deploying the Elastic stack — point users to elastic-container-project for a self-hosted Elasticsearch + Kibana + Fleet setup.
- Provisioning the EDR VM — the user brings their own Windows VM with Elastic Defend already enrolled.
- Running anything in the cloud. Everything is local / self-hosted.
In scope (this project):
- Build Whiskers in Rust — a single-binary Windows execution runner that the user drops on their EDR VM. Receives a payload over HTTP, executes it, reports stdout/stderr/PID. No local EDR reading, no log scraping — the Elastic Agent already on the VM handles all telemetry.
- Build LitterBox-side integration — a new analyzer module that talks to the agent's REST API + queries the user's local Elastic for alerts in the run window.
Design constraint — single-occupancy:
The agent runs one payload at a time. No parallel execution, no queue, no
slot management. The acquire/release lock is the primitive that enforces
this — it covers the whole orchestration window (exec start through Elastic
poll completion), not just the live-process window. New exec while a run is
in progress is rejected with 409.
Architecture
LitterBox (existing, on Windows analysis machine)
│
└─ NEW: ElasticEdrAnalyzer
│
├── HTTP ──► Whiskers (user's EDR VM, our Rust binary)
│ ├── exec / kill / lock
│ └── stdout / stderr / PID reporting
│
└── HTTP ──► User's local Elastic stack (their elastic-container-project)
└── /.siem-signals-*/_search filtered by host.name + time
User's EDR VM (their responsibility)
├── Whiskers.exe (our Rust binary, running on :8080)
└── Elastic Agent + Elastic Defend (user-installed, enrolled to their Elastic stack)
The agent is the only deliverable on the VM side. EDR telemetry comes from the Elastic Agent the user installed separately. LitterBox queries the user's Elastic stack via REST. No cloud anywhere.
Deployment matrix
LitterBox itself has two deploy methods (unchanged by this work):
- Direct Windows host — user clones the repo,
python -m venv venv,pip install -r requirements.txt, runs LitterBox. No install script. - Docker on Linux —
Docker/setup.shprovisions adockurr/windowscontainer;Docker/install.ps1bootstraps LitterBox inside the freshly-provisioned Windows VM.
This work is backward-compatible by design — every existing user's deployment continues working unchanged. Elastic capability is purely additive: drop a profile YAML, point at an agent URL, enable.
Roadmap
Phase A — Whiskers (Rust)
Build the agent first; nothing in Phase L can be tested end-to-end without it.
A1. Project scaffold
- Cargo project, tokio async runtime, axum web framework (minimal modern HTTP)
- Cross-compile target:
x86_64-pc-windows-gnufrom Linux, orx86_64-pc-windows-msvcfrom Windows - Goal: hello-world endpoint compiling to a single static
.exe
A2. REST endpoints (the entire agent surface)
| Endpoint | Behaviour |
|---|---|
GET /api/info |
{"hostname": ..., "os_version": ..., "agent_version": ...} — agent self-reports its identity. LitterBox uses the hostname to filter Elastic alerts; user never configures it manually. |
GET /api/lock/status |
{"in_use": bool} |
POST /api/lock/acquire |
200 if free, 409 if held |
POST /api/lock/release |
200 |
POST /api/execute/exec |
multipart (file, optional drop_path, executable_args, xor_key) → spawn the file, return {status, pid} |
POST /api/execute/kill |
terminate the spawned process if alive |
GET /api/logs/execution |
{pid, stdout, stderr, exit_code} for the last run |
GET /api/logs/agent |
agent's own debug log for troubleshooting |
That is the whole agent. No EDR plugins, no log scraping, no Windows Event Log integration. Just an execution runner that knows its own hostname.
A3. Build pipeline
cargo build --release --target x86_64-pc-windows-gnu→Whiskers.exe- Single static binary, no runtime dependencies on the target VM
- Ship in GitHub releases as a downloadable
.exe
A4. Agent install docs
- Drop the
.exeon the EDR VM, runWhiskers.exe --port 8080 - Allow inbound TCP 8080 in Windows Firewall
- Optional: register as a Windows service via
sc create
Deliverable after Phase A: a standalone .exe that any user can drop on
any Windows VM. Verifiable with curl http://<vm>:8080/api/lock/status.
Phase L — LitterBox-side integration
L1. Profile YAML schema + loader
# Config/edr_profiles/elastic.yml
name: "elastic"
display_name: "Elastic Defend"
agent_url: "http://<edr-vm-ip>:8080"
elastic_url: "https://<elastic-stack-ip>:9200"
elastic_apikey: "<base64-encoded-key>"
wait_seconds_for_alerts: 90 # Elastic's detection rule cycle is ~60s
# hostname is NOT configured here — agent self-reports via GET /api/info
# at the start of each run. If user moves the agent to a different VM,
# LitterBox picks up the new hostname automatically with no config edit.
Loader scans Config/edr_profiles/*.yml at LitterBox boot and registers
each profile. Real profile files are gitignored; ship a .example template.
L2. AgentClient
app/analyzers/edr/agent_client.py- Python REST wrapper for the Rust agent's API
- Methods:
lock_acquire,lock_release,lock_status,exec(file_bytes, drop_path, args, xor_key),kill,get_execution_logs,get_agent_logs - Fail-soft: if agent unreachable, profile is marked unavailable in UI
L3. ElasticClient
app/analyzers/edr/elastic_client.pyGET <elastic_url>/.siem-signals-*/_searchwithAuthorization: ApiKey <key>- Filter by
kibana.alert.original_timerange +host.name - Returns normalized alert records:
{title, severity, rule_id, detected_at, raw}
L4. ElasticEdrAnalyzer (orchestrator)
Per-payload flow:
AgentClient.get_info→{hostname, os_version, agent_version}(self-reported)AgentClient.lock_acquireAgentClient.exec(payload, args)- wait for execution to exit OR timeout
AgentClient.kill(idempotent if already exited)AgentClient.get_execution_logs→ stdout / stderr / exit_code- sleep
wait_seconds_for_alerts(allow Elastic detection rule cycle) ElasticClient.fetch_alerts(hostname_from_step_1, run_start, now)AgentClient.lock_release- return
findingsdict
Deliverable after Phase L: end-to-end works from CLI / API. Submit a payload, get back execution output + Elastic alerts.
Phase U — UI
U1. Upload-page integration
- "Run with Elastic" button on upload page (one button per registered profile)
- Profile YAML's
display_namebecomes the button label - Sits next to existing Static / Dynamic / HolyGrail buttons
U2. Results-page tab
- New "Elastic" tab when the profile was used
- Sub-sections: Execution output | Elastic Alerts | Verdict line at top
- Detection Score contribution: high/critical-severity Elastic alerts → +50
(mirrors the existing Defender pattern in
_calculate_rededr_risk)
Phase R — Report
R1. Self-contained downloadable report
- Add an EDR section to
app/templates/report.html - Same content as the U2 tab — verdict line, alert table, execution output
Phase D — Docs
D1. README + setup docs
- "Setting up Elastic for LitterBox" — point to
elastic-container-project,
list the minimum config the user needs:
- Elastic Defend integration enabled
- EDR VM enrolled to their Fleet
- Detection Engine rules enabled
- API key with
.siem-signals-*read scope
- "Deploying Whiskers" — drop the
.exe, run with--port, allow firewall, optional Windows-service registration - "Configuring LitterBox profile" — copy
Config/edr_profiles/elastic.yml.example, fill 4 fields (agent_url,elastic_url,elastic_apikey,hostname), restart LitterBox
Critical path
A1 → A2 → A3 → A4 (testable agent binary)
│
▼
L1 → L2 → L3 → L4 (testable end-to-end)
│
▼
U1 → U2 → R1 → D1
Phase A is the gate — until the Rust agent compiles and runs on a Windows VM, nothing in Phase L can be tested end-to-end. After A, L1-L4 are straight Python work in the existing LitterBox codebase.
Critical files
New (Phase A — separate Rust crate)
Whiskers/Cargo.tomlWhiskers/src/main.rs— entry, axum router setupWhiskers/src/api/{lock,execute,logs}.rs— endpoint handlersWhiskers/src/state.rs— shared lock + execution state
New (Phase L — LitterBox repo)
Config/edr_profiles/elastic.yml.exampleapp/analyzers/edr/__init__.pyapp/analyzers/edr/agent_client.pyapp/analyzers/edr/elastic_client.pyapp/analyzers/edr/elastic_edr_analyzer.pyapp/analyzers/edr/profile_loader.py
Modified (Phase L–R — LitterBox repo)
app/analyzers/manager.py— register ElasticEdrAnalyzerapp/blueprints/api.py— new endpoint to dispatch to a profileapp/templates/upload.html— "Run with X" buttons from profile registryapp/templates/results.html— new tab when an EDR profile was usedapp/templates/report.html— new EDR sectionapp/utils/risk_analyzer.py— Elastic alert contribution to Detection ScoreREADME.md— add the EDR section pointing to elastic-container-project
Out of scope (deferred / never)
- Cloud EDR integrations (Elastic Cloud, MDE Graph, CrowdStrike Cloud) — this project is local-only.
- EDR VM provisioning / docker-compose for the EDR VM — user brings their own VM with their own EDR.
- Elastic stack deployment — point to elastic-container-project, do not ship our own.
- Multiple EDR types beyond Elastic — agent's API surface is generic
enough that adding another EDR is a matter of writing another
*_client.pyinapp/analyzers/edr/and another profile YAML, but we don't ship more than Elastic in v1. - Multi-VM parallel runs / snapshot revert per submission — accept VM state accumulation; user reboots / reverts the VM manually if needed.
- Profile-management UI — YAML edit + restart is sufficient for v1.
Verification
End-to-end after Phase L:
- User deploys Elastic stack via elastic-container-project on a Linux box
- User stands up a Windows VM, installs Elastic Agent + Elastic Defend, enrolls to their stack
- User drops
Whiskers.exeon that VM, runs it with--port 8080 - User configures
Config/edr_profiles/elastic.ymlwith the 4 fields - User uploads a known-detected sample to LitterBox, clicks "Run with Elastic"
- Within ~90s after the payload executes, alerts appear in the Elastic tab matching what Kibana shows in the Detection Engine alerts page
- High/critical alerts visibly bump the Detection Score on the file's info page
If steps 5–7 work for a known Elastic-detected sample (e.g. a stock mimikatz build), the integration is functional.