-
Notifications
You must be signed in to change notification settings - Fork 166
HTTP API Reference
Complete catalogue of LitterBox's HTTP endpoints. Pages render Jinja templates; /api/* routes return JSON. The frontend uses both β page routes for the SSR shell and /api/* for live polling.
LitterBox is not authenticated. It's designed to run on a private analyst network. Don't expose port 1337 to the internet.
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
Dashboard β sandbox health, scanner inventory, EDR fleet, recent samples |
GET |
/upload |
Upload drop-zone (file + EDR profile picker) |
GET |
/results/<analysis_type>/<target> |
Saved-view for a sample (analysis_type = static / dynamic / info / holygrail) |
GET |
/results/edr/<profile>/<target> |
Saved-view for one EDR profile run |
GET |
/analyze/<analysis_type>/<target> |
Live runner β POSTs from JS to itself to start, then polls |
GET |
/analyze/edr/<profile>/<target> |
Live EDR runner |
GET |
/analyze/all/<target> |
All-in-one fan-out β Static + every reachable EDR in parallel |
GET |
/whiskers |
EDR fleet detail page |
GET |
/doppelganger?type=blender|fuzzy[&hash=...] |
Doppelganger comparison page |
GET |
/holygrail |
BYOVD driver upload + analysis page |
GET |
/summary |
Cross-sample summary index |
GET |
/files |
All-samples summary (also a JSON endpoint β see below) |
GET |
/report/<target> |
Redirects to /api/report/<target>?download=true
|
| Method | Path | Body | Returns |
|---|---|---|---|
POST |
/upload |
multipart file
|
{message, file_info: {md5, size, original_name, ...}} |
POST |
/holygrail |
multipart file (.sys) |
{message, file_info, next_step} |
POST |
/validate/<pid> |
β |
200 {status: valid} / 404 {error}
|
DELETE |
/file/<target> |
β |
{status, message, details: {upload, result, analysis}} β removes upload file, results folder, and PE-Sieve analysis sub-folders |
| Method | Path | Body | Returns |
|---|---|---|---|
POST |
/analyze/static/<hash> |
β |
{status: 'success', results: {...}} (sync, ~1β10s) |
POST |
/analyze/dynamic/<hash-or-pid> |
optional {args: [str, ...]}
|
{status: 'success', results: {...}} (sync, longer β runs payload) |
POST |
/analyze/edr/<profile>/<hash> |
optional {args, xor_key}
|
Phase-1 dict; Phase-2 runs in a server-side daemon thread (poll the GET) |
GET |
/holygrail?hash=<hash> |
β | BYOVD analysis result for a previously-uploaded driver |
Status codes for analysis:
-
200β completed (or Phase-1 settled in the EDR case) -
202β early termination (process exited during init, dynamic only) -
409β busy (EDR agent lock held by another run) -
500β analyzer error -
502β agent unreachable / agent error (EDR only)
| Method | Path | Returns |
|---|---|---|
GET |
/api/results/info/<target> |
file_info.json (hashes, entropy, PE info) |
GET |
/api/results/static/<target> |
static_analysis_results.json |
GET |
/api/results/dynamic/<target> |
dynamic_analysis_results.json |
GET |
/api/results/holygrail/<target> |
byovd_results.json |
GET |
/api/results/edr/<target> |
{profiles: [...]} β index of saved EDR runs for the target |
GET |
/api/results/edr/<profile>/<target> |
edr_<profile>_results.json |
GET |
/api/results/risk/<target> |
{risk_score, risk_level, risk_factors: [...]} β recomputed each call |
GET |
/api/report/<target>[?download=true] |
Full HTML report β inline, or as Content-Disposition: attachment
|
| Method | Path | Returns |
|---|---|---|
GET |
/api/edr/profiles |
{profiles: [{name, display_name, kind, agent_url, ...}]} β secrets stripped |
GET |
/api/edr/agents/status[?refresh=1] |
Cached reachability snapshot for every profile (Whiskers + backend); ?refresh=1 forces a synchronous re-probe |
GET |
/api/edr/fibratus/<profile>/alerts/since?from=<iso>[&until=<iso>] |
Test passthrough β query Whiskers's event-log endpoint without dispatching a payload (validates the Fibratus β eventlog β Whiskers wire) |
| Method | Path | Returns |
|---|---|---|
GET |
/health |
Unified sandbox status β {status, issues, sandbox: {...}, scanners: {rows, counts}, edr_agents: {agents}}. One fetch covers boot validation + scanner inventory + EDR fleet reachability |
GET |
/files |
{driver_based, payload_based, pid_based} β full sample inventory |
POST |
/cleanup |
Clears Uploads/, Results/, doppelganger DB, PE-Sieve analysis folders, HolyGrail results |
/api/edr/agents/status is still available for callers that only need the focused EDR-fleet payload (e.g. the all-in-one preflight, the /whiskers agents page) β it shares the same TTL-cached probe as /health's edr_agents block. There's no separate /api/system/scanners route any more β read scanners.rows from /health.
| Method | Path | Body | Returns |
|---|---|---|---|
GET |
/doppelganger?type=blender |
β | Page with latest blender scan |
GET |
/doppelganger?type=fuzzy |
β | Page with fuzzy-DB stats |
GET |
/doppelganger?type=<t>&hash=<h> |
β | Comparison result |
POST |
/doppelganger |
{type: blender, operation: scan} |
{processes: [...]} β host snapshot |
POST |
/doppelganger |
{type: fuzzy, operation: create_db, folder_path, extensions?} |
{stats} |
POST |
/doppelganger |
{type: fuzzy, operation: analyze, hash, threshold?} |
{results} |
/api/results/edr/<profile>/<target> returns one of:
status |
Meaning |
|---|---|
polling_alerts |
Phase 1 succeeded; Phase 2 still in flight |
completed |
Phase 2 done with at least zero alerts collected |
blocked_by_av |
AV intercepted at write/spawn β run gated on AV-block path |
partial |
Phase 1 ok but backend alert query failed (sub_status: elastic_unreachable / elastic_error) |
agent_unreachable |
Phase 1 transport failure to Whiskers (not persisted to disk) |
busy |
Whiskers lock held (not persisted to disk) |
error |
Phase 1 generic failure (not persisted to disk) |
agent_unreachable, busy, and pre-execution error are intentionally not saved to JSON β they're transient transport failures, not real findings. The frontend treats them as ephemeral and the saved-view route correctly 404s.
For long-running EDR dispatches the frontend uses an adaptive poll:
POLL_INTERVAL_MS = 2000 # base
POLL_MAX_MS = 15000 # ceiling
POLL_BACKOFF = 1.5 # multiplier on each tick where alerts didn't change
Movement (total_alerts increased, or status changed) snaps the interval back to 2s. document.visibilityState === 'hidden' pauses the loop entirely.
There is no auth and CORS is permissive (Access-Control-Allow-Origin: * for /api/*). Run on a private network only.
- Application Architecture β backend structure
- GrumpyCats CLI β command-line client over this API
- GrumpyCats Library β Python library wrapping this API
- LitterBoxMCP β MCP server wrapping this API for LLMs
- π Home
- π§ Application Architecture
- π Dashboard
- π All in One Pipeline
- π― Detection Score Explained
- 𧬠Blender Scanner
- π FuzzyHash Scanner
- π‘οΈ HolyGrail BYOVD Scanner
- π YARA Rules Management