This document describes how to integrate AegisBPF events with popular Security Information and Event Management (SIEM) systems.
AegisBPF emits events in JSON format to stdout or journald. Two on-the-wire shapes are available:
- AegisBPF native (default): the project-internal JSON shape documented in this guide and described by
config/event-schema.json. - OCSF 1.1.0: opt-in via
--event-format=ocsf. File and network block events are reshaped into OCSF File Activity (class_uid 1001) and Network Activity (class_uid 4001) so SIEMs that ingest OCSF natively (Splunk, Elastic, Snowflake, AWS Security Lake) can parse without a custom mapping. See §OCSF below for the per-event mapping table.
| Event | Description |
|---|---|
exec |
Process execution observed |
block |
File open observed or blocked (audit/enforce) |
| Field | Type | Description |
|---|---|---|
type |
string | Event type (exec or block) |
pid |
integer | Process ID |
ppid |
integer | Parent process ID |
start_time |
integer | Process start time (kernel clock) |
exec_id |
string | Stable execution identifier (pid:start_time) |
trace_id |
string | Correlation identifier (equal to exec_id) |
cgid |
integer | Cgroup ID |
cgroup_path |
string | Cgroup path |
comm |
string | Command name (max 16 chars) |
| Field | Type | Description |
|---|---|---|
parent_start_time |
integer | Parent process start time |
parent_exec_id |
string | Stable parent execution identifier |
parent_trace_id |
string | Parent correlation identifier |
ino |
integer | Inode number |
dev |
integer | Device number |
path |
string | Path of the file when available |
resolved_path |
string | Canonical path (if different) |
action |
string | AUDIT (audit-only) or enforce action (TERM/KILL/INT/BLOCK) |
Note: when enforcement is inode-based, path may be empty; use ino + dev for correlation.
Pass --event-format=ocsf to aegisbpfd run to emit
OCSF 1.1.0 JSON for File and Network
block events. The EventFormat enum is global — every event of those
types emitted by the daemon is reshaped, regardless of sink.
sudo ./build/aegisbpf run --enforce --event-format=ocsf
sudo ./build/aegisbpf run --enforce --event-format=ocsf --log=bothAccepted aliases: ocsf, OCSF, ocsf-1.1, ocsf-1.1.0. The
inverse keyword is aegis (or default, json).
| AegisBPF event | OCSF class | activity_id |
|---|---|---|
File deny block (BlockEvent) |
File Activity (1001) | 14 (Open) |
Net deny block, egress / bind / listen / accept (NetBlockEvent) |
Network Activity (4001) | 1 (Open) |
Net deny block, sendmsg / recvmsg (NetBlockEvent) |
Network Activity (4001) | 6 (Traffic) |
Out of scope today (still emitted in the AegisBPF-native shape):
ExecEvent, ExecArgvEvent, ForensicEvent, KernelBlockEvent,
OverlayCopyUpEvent, state_change, control_change. OCSF support
for those is tracked in docs/POSITIONING.md §3.3.
| OCSF field | Source / value |
|---|---|
class_uid, class_name |
1001, "File Activity" |
category_uid, category_name |
1, "System Activity" |
activity_id, activity_name |
14, "Open" (AegisBPF blocks file_open / inode_permission) |
type_uid |
100114 (= class_uid * 100 + activity_id) |
action_id, action |
2 / "Denied" in enforce mode; 1 / "Allowed" when BlockEvent.action == "AUDIT" |
disposition_id, disposition |
2 / "Blocked" in enforce mode; omitted in audit mode |
status_id, status |
1 / "Success" (the policy decision was applied) |
severity_id, severity |
4 / "High" in enforce mode; 2 / "Low" in audit mode |
time |
Wall-clock epoch ms at format time |
metadata.version |
"1.1.0" |
metadata.product.{name,vendor_name,version} |
"AegisBPF", "AegisBPF Project", AEGIS_VERSION_STRING |
metadata.uid |
BlockEvent.exec_id (== pid:start_time) |
actor.process.{pid,name,parent_process.pid,created_time} |
BlockEvent.{pid,comm,ppid,start_time/1e6} |
device.{type_id,type,hostname} |
1, "Server", gethostname() |
file.{type_id,type,name,path,parent_folder} |
1, "Regular File", basename(path), path, dirname(path) |
unmapped.aegis_inode |
BlockEvent.ino |
unmapped.aegis_device |
BlockEvent.dev |
unmapped.aegis_cgroup_id |
BlockEvent.cgid |
unmapped.aegis_cgroup_path |
resolved cgroup path |
unmapped.aegis_parent_exec_id |
ppid:parent_start_time correlation handle |
| OCSF field | Source / value |
|---|---|
class_uid, class_name |
4001, "Network Activity" |
category_uid, category_name |
4, "Network Activity" |
activity_id, activity_name |
1 Open for connect/bind/listen/accept; 6 Traffic for sendmsg/recvmsg |
action_id / disposition_id / severity_id |
Same audit-vs-enforce semantics as File Activity above |
connection_info.protocol_num |
6 (TCP) or 17 (UDP) |
connection_info.protocol_name |
"tcp" / "udp" |
connection_info.protocol_ver_id |
4 (IPv4) or 6 (IPv6) |
dst_endpoint |
Remote peer for egress (connect/sendmsg); local port for bind/listen |
src_endpoint |
Remote peer for accept/recvmsg (peer originated); local port otherwise |
actor.process.{pid,name,parent_process.pid} |
Calling process |
unmapped.aegis_direction |
0–5 raw direction code |
unmapped.aegis_event_type |
net_connect_block / net_bind_block / etc. |
unmapped.aegis_rule_type |
ip / port / cidr / ip_port / identity |
{
"class_uid": 1001,
"class_name": "File Activity",
"category_uid": 1,
"category_name": "System Activity",
"activity_id": 14,
"activity_name": "Open",
"type_uid": 100114,
"type_name": "File Activity: Open",
"action_id": 2,
"action": "Denied",
"disposition_id": 2,
"disposition": "Blocked",
"status_id": 1,
"status": "Success",
"severity_id": 4,
"severity": "High",
"time": 1745676543210,
"message": "AegisBPF: file open denied",
"metadata": {
"version": "1.1.0",
"product": {
"name": "AegisBPF",
"vendor_name": "AegisBPF Project",
"version": "0.5.1"
},
"uid": "exec-1234-deadbeef"
},
"actor": {
"process": {
"pid": 1234,
"name": "evil-proc",
"created_time": 1700000000000,
"parent_process": {"pid": 5678}
}
},
"device": {"type_id": 1, "type": "Server", "hostname": "node-01"},
"file": {
"type_id": 1,
"type": "Regular File",
"name": "shadow",
"path": "/etc/shadow",
"parent_folder": "/etc"
},
"unmapped": {
"aegis_inode": 4242,
"aegis_device": 99,
"aegis_cgroup_id": 3405691582,
"aegis_cgroup_path": "/sys/fs/cgroup/system.slice"
}
}OCSF format is opt-in and orthogonal to the sink (--log=stdout|journald|both).
The journald path stores the OCSF payload in MESSAGE= while preserving the
existing AEGIS_* field set on the journal entry — consumers that want OCSF
read MESSAGE= directly; consumers that want raw fields read AEGIS_* (or
parse MESSAGE= as their existing pipelines already do).
-
Configure AegisBPF to output JSON to stdout:
aegisbpf run --log=stdout --log-format=json
-
Use a log shipper (e.g., Fluentd, Vector) to forward to Splunk HEC:
# Vector config example [sources.aegisbpf] type = "journald" include_units = ["aegisbpf"] [transforms.parse_aegisbpf] type = "remap" inputs = ["aegisbpf"] source = ''' . = parse_json!(.message) ''' [sinks.splunk] type = "splunk_hec" inputs = ["parse_aegisbpf"] endpoint = "https://splunk.example.com:8088" token = "${SPLUNK_HEC_TOKEN}" index = "security" sourcetype = "aegisbpf"
For Splunk CEF ingestion, transform AegisBPF events to CEF:
CEF:0|AegisBPF|AegisBPF|1.0|BLOCK|File Access Blocked|7|dst=<path> dproc=<comm> msg=<action> cn1=<pid> cn1Label=pid cn2=<ppid> cn2Label=ppid cs1=<cgroup_path> cs1Label=cgroup
Field mapping:
| CEF Field | AegisBPF Field |
|---|---|
dst |
path |
dproc |
comm |
msg |
action |
cn1 |
pid |
cn2 |
ppid |
cs1 |
cgroup_path |
deviceCustomDate1 |
start_time (converted to datetime if desired) |
filebeat.inputs:
- type: journald
id: aegisbpf
include_matches:
- _SYSTEMD_UNIT=aegisbpf.service
processors:
- decode_json_fields:
fields: ["message"]
target: "aegisbpf"
overwrite_keys: true
output.elasticsearch:
hosts: ["https://elasticsearch:9200"]
index: "aegisbpf-%{+yyyy.MM.dd}"{
"index_patterns": ["aegisbpf-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"type": { "type": "keyword" },
"start_time": { "type": "long" },
"exec_id": { "type": "keyword" },
"trace_id": { "type": "keyword" },
"parent_start_time": { "type": "long" },
"parent_exec_id": { "type": "keyword" },
"parent_trace_id": { "type": "keyword" },
"pid": { "type": "integer" },
"ppid": { "type": "integer" },
"comm": { "type": "keyword" },
"path": { "type": "keyword" },
"resolved_path": { "type": "keyword" },
"action": { "type": "keyword" },
"cgid": { "type": "long" },
"cgroup_path": { "type": "keyword" },
"dev": { "type": "integer" },
"ino": { "type": "long" }
}
}
}
}Example detection rule for suspicious activity:
{
"name": "AegisBPF High Block Rate",
"description": "Detects high rate of blocked executions indicating potential attack",
"risk_score": 70,
"severity": "high",
"type": "threshold",
"query": "aegisbpf.type:block",
"threshold": {
"field": ["host.name"],
"value": 100
},
"interval": "5m"
}Configure rsyslog to forward AegisBPF events:
# /etc/rsyslog.d/aegisbpf.conf
if $programname == 'aegisbpf' then {
action(type="omfwd"
target="syslog.example.com"
port="514"
protocol="tcp"
template="RSYSLOG_SyslogProtocol23Format")
stop
}<priority>1 <timestamp> <hostname> aegisbpf - - - <json_event>
Example:
<134>1 2024-01-15T10:30:00.000Z server1 aegisbpf - - - {"type":"block","pid":12345,"ppid":1000,"start_time":123456789,"exec_id":"12345:123456789","trace_id":"12345:123456789","cgid":5678,"cgroup_path":"/sys/fs/cgroup/user.slice/user-1000.slice","comm":"bash","path":"/usr/bin/malware","action":"KILL","ino":123456,"dev":259}
For metrics-based monitoring, use the built-in Prometheus endpoint:
aegisbpf metrics --out /var/lib/prometheus/node-exporter/aegisbpf.promKey low-cardinality metrics include:
aegisbpf_blocks_total,aegisbpf_ringbuf_drops_totalaegisbpf_net_blocks_total,aegisbpf_net_ringbuf_drops_totalaegisbpf_net_rules_totalaegisbpf_deny_inode_entries,aegisbpf_deny_path_entries,aegisbpf_allow_cgroup_entries
For short-lived debugging sessions, export detailed high-cardinality metrics:
aegisbpf metrics --detailed --out /tmp/aegisbpf.debug.promSee prometheus/alerts.yml for alert rules and grafana/dashboard.json for the Grafana dashboard.
See docs/METRICS_OPERATIONS.md for threshold interpretation and operator response flow.
Recommended SLO alerts:
AegisBPFEventLossSLOViolation(event-loss ratio > 0.1%)AegisBPFMetricsStale(no updates for >30m with active policy)
For troubleshooting policy rollouts and runtime startup checks, enable span logs:
AEGIS_OTEL_SPANS=1 aegisbpf policy apply /etc/aegisbpf/policy.confThis emits structured logs with:
message:otel_span_start/otel_span_endtrace_id,span_id,parent_span_idspan_name,duration_ms,status
Common span names include:
- Policy path:
cli.policy_apply,policy.apply,policy.apply_internal,policy.rollback_last_applied - Runtime path:
daemon.run,daemon.load_bpf,daemon.attach_programs,daemon.event_loop - Health path:
cli.health,health.detect_kernel_features,health.load_bpf
- Create a new log source with protocol type "Syslog"
- Use the following DSM parsing:
<device_extension>
<pattern id="aegisbpf_block"
regex="\"type\":\"block\".*\"pid\":(\d+).*\"path\":\"([^\"]+)\".*\"action\":\"([^\"]+)\""
capture_groups="pid,path,action">
<event name="File Access Blocked" category="Security" severity="high"/>
</pattern>
<pattern id="aegisbpf_exec"
regex="\"type\":\"exec\".*\"pid\":(\d+)"
capture_groups="pid">
<event name="Execution Observed" category="Audit" severity="low"/>
</pattern>
</device_extension>| Property Name | Expression |
|---|---|
| AegisBPF PID | \"pid\":(\d+) |
| AegisBPF Path | \"path\":\"([^\"]+)\" |
| AegisBPF Action | \"action\":\"([^\"]+)\" |
| AegisBPF Cgroup | \"cgroup_path\":\"([^\"]+)\" |
filter {
if [program] == "aegisbpf" {
json {
source => "message"
target => "aegisbpf"
}
mutate {
add_field => { "[@metadata][index]" => "so-aegisbpf" }
}
if [aegisbpf][type] == "block" {
mutate {
add_tag => ["alert", "execution_blocked"]
}
}
}
}<!-- /var/ossec/etc/decoders/aegisbpf_decoder.xml -->
<decoder name="aegisbpf">
<program_name>aegisbpf</program_name>
</decoder>
<decoder name="aegisbpf-block">
<parent>aegisbpf</parent>
<regex>"type":"block".*"pid":(\d+).*"path":"([^"]+)".*"action":"(\w+)"</regex>
<order>pid,path,action</order>
</decoder><!-- /var/ossec/etc/rules/aegisbpf_rules.xml -->
<group name="aegisbpf,">
<rule id="100001" level="10">
<decoded_as>aegisbpf-block</decoded_as>
<description>AegisBPF: Execution blocked - $(path)</description>
<group>execution_blocked,</group>
</rule>
<rule id="100002" level="12">
<if_sid>100001</if_sid>
<field name="action">KILL</field>
<description>AegisBPF: File access blocked - $(path)</description>
<group>malware,</group>
</rule>
</group>AegisBPF emits process start_time values (kernel monotonic clock). Use the ingest timestamp for event time, or convert start_time if you need a stable process timeline:
import time
def convert_start_time(boot_ns):
"""Convert kernel boot timestamp to wall-clock time."""
# Read boot time from /proc/stat
with open('/proc/stat') as f:
for line in f:
if line.startswith('btime'):
boot_time = int(line.split()[1])
break
# Convert nanoseconds to seconds and add to boot time
wall_time = boot_time + (boot_ns / 1e9)
return time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime(wall_time))Alternatively, configure journald output which automatically includes wall-clock timestamps.