Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/irchaosclub/FANGS/llms.txt

Use this file to discover all available pages before exploring further.

After a runner begins executing a job, it streams captured eBPF events back to the orchestrator in real time through a single long-lived HTTP POST. Events are grouped into EventBatch objects and written as newline-delimited JSON (NDJSON) — one batch per line — in the request body. The runner flushes every 250 milliseconds or after accumulating 64 events, whichever comes first. Once the job completes, the runner posts a ScanResult to finalize the run record with terminal status and drop counts. The orchestrator uses both endpoints together to build a complete picture of what the package did during its sandbox execution.

POST /v1/runs//events

Streams captured eBPF events from a runner to the orchestrator. The body is NDJSON — a sequence of EventBatch JSON objects, each terminated by a newline (\n), all in a single HTTP request body. The connection stays open until the runner closes it after the sandbox exits. Path parameter
run_id
string
required
Hex-encoded run identifier returned by POST /v1/scans. Must match an existing row in the runs table.
Server-side processing For each EventBatch received, the orchestrator:
  1. Persists one EventRow per event to the events table with ts_ns = time.Now().UnixNano().
  2. Increments per-type metrics counters (one counter per EventType string).
  3. Resets a 2-second debounce timer for this run_id. When the timer fires, the Differ runs once on the complete event set. This prevents the Differ from executing once per batch during a high-volume stream.
On stream EOF (runner closes the connection):
  1. Transitions the run state to "done" in the runs table.
  2. Fires the debounced Differ if not already scheduled.
Request body format (NDJSON) Each line is one complete EventBatch JSON object:
{"run_id":[...16 bytes...],"seq":1,"events":[...]}\n
{"run_id":[...16 bytes...],"seq":2,"events":[...]}\n
run_id
array
16-byte binary run identifier. Must match the run_id path parameter.
seq
number
Monotonically increasing batch sequence number, per run. The orchestrator logs this for debugging but does not currently enforce ordering.
events
array
Array of EventEnvelope objects. Each envelope wraps one eBPF event.
Event types
NumericStringPayload structDescription
1file_accessOpenatEventAn openat() syscall matched a watched path prefix
2execExecEventA process was executed inside the cgroup
3net_connectNetConnectEventAn outbound TCP connection was initiated
4dns_queryDnsQueryEventA DNS query was sent from the cgroup
5tls_sniTLSSniEventA TLS SNI hostname was observed
Payload field reference Example batch
{
  "run_id": [161, 178, 195, 212, 229, 246, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
  "seq": 1,
  "events": [
    {
      "type": 1,
      "payload": {
        "Header": {"PID": 1234, "Comm": "npm", "TsNs": 1705312980000000000},
        "Flags": 0,
        "Path": "/etc/passwd",
        "PathLen": 11,
        "Truncated": 0
      }
    },
    {
      "type": 3,
      "payload": {
        "Header": {"PID": 1234, "Comm": "node", "TsNs": 1705312981000000000},
        "Family": 2,
        "DestPort": 443,
        "DestAddr": "151.101.1.195"
      }
    }
  ]
}
Response — 200 OK Returned after the runner closes the connection and the full stream has been processed.
{
  "received_batches": 3,
  "received_events": 142,
  "persisted": 142
}
received_batches
number
Total number of EventBatch objects decoded from the NDJSON stream.
received_events
number
Total number of individual events across all batches.
persisted
number
Number of events successfully written to the events table. Equal to received_events unless storage is not configured or individual rows failed to marshal.
Runner-side buffering The runner maintains a 1,024-event in-memory queue and flushes to this endpoint on two triggers: every 250 milliseconds or when 64 events have accumulated, whichever comes first. Events are dropped (with a warning log) when the queue is full and the HTTP connection is too slow to drain it. Dropped events are reported in the subsequent POST /v1/runs/{run_id}/result body.
If storage is not configured on the orchestrator (no database path set), events are logged and counted but not persisted. The persisted field in the response will be 0. The run state is still transitioned to "done" and the Differ is still scheduled (but it will find no events to analyze).

POST /v1/runs//result

Finalizes a run. The runner posts this after the sandbox container exits and the sensor has been detached, reporting terminal status, drop counts, and total scan duration. The orchestrator transitions the run to "done" or "failed" and stamps finished_at, events_emitted, events_dropped, and duration_ns on the runs row. Path parameter
run_id
string
required
Hex-encoded run identifier. Must match an existing row in runs.
Request body
{
  "run_id": [161, 178, 195, 212, 229, 246, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
  "status": "ok",
  "reason": "",
  "events_emitted": 142,
  "events_dropped": 0,
  "duration": 60123456789
}
run_id
array
16-byte binary run ID. Informational — the path parameter is authoritative.
status
string
required
Terminal status of the run. Must be one of:
ValueMeaning
"ok"Sandbox exited normally within the observation window
"failed"Sandbox or sensor encountered an unrecoverable error
"timeout"Observation window elapsed before the sandbox exited
reason
string
Human-readable failure description. Present when status is "failed" or "timeout". Stored in runs.failure_reason.
events_emitted
number
Total events the sensor emitted during the run. Stored in runs.events_emitted.
events_dropped
number
Events dropped due to ring-buffer overflow or queue saturation. Stored in runs.events_dropped. A non-zero value here means the recorded event set is incomplete and some behaviors may have been missed.
duration
number
Actual wall-clock scan duration in nanoseconds. Stored in runs.duration_ns.
Response — 200 OK
{"recorded": true}
recorded
boolean
true when the result was written to storage. false when the orchestrator has no storage backend configured (the result is still logged).
Error responses
StatusCondition
400Missing or empty run_id path parameter
400status is not one of ok, failed, or timeout
404No run exists with the given run_id
500Database write failed
The events_dropped metric is also forwarded to the Prometheus metrics sink when non-zero. Monitor the fangs_events_dropped_total counter to detect ring-buffer saturation — a sustained non-zero value indicates the sandbox is generating events faster than the runner can drain them, and scan accuracy may be degraded.

Build docs developers (and LLMs) love