A lightweight host and container security agent that hooks into Linux kernel syscalls using eBPF (CO-RE), streams events to userspace via BPF ring buffers in real-time, enriches them with process and container metadata, and evaluates them against behavioral baselines + anomaly scoring rules to emit alerts.
┌─────────────────────────────────────────────────────────────────────────────┐
│ KERNEL SPACE (eBPF) │
├─────────────────────────────────────────────────────────────────────────────┤
│ Tracepoints: │
│ • sys_enter_execve / sys_enter_execveat → execve events (pid, ppid, │
│ uid, cgroup_id, comm, filename) │
│ • sys_enter_connect / sys_enter_accept* → network events (pid, ppid, │
│ uid, cgroup_id, comm, dest_ip, dest_port, is_accept) │
│ • sys_enter_openat (filtered) → sensitive file opens only │
│ │
│ All events → BPF Ring Buffer (per program) │
└────────────────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ USERSPACE (Go Agent) │
├─────────────────────────────────────────────────────────────────────────────┤
│ 1. Ring Buffer Readers (per tracepoint) │
│ 2. Event Consolidation Channel │
│ 3. Real-time Enrichment: │
│ • /proc/<pid>/cmdline → full command line │
│ • /proc/<pid>/cgroup → container ID (64-char hex) │
│ • UID lookup → username │
│ 4. Baseline Store (per-container JSON, persisted to disk) │
│ • Observed binaries (allowlist) │
│ • Observed dest IP:port pairs │
│ • Process tree (parent comm → child comm) │
│ • Learning phase tracking (configurable window) │
│ 5. Rule Engine (baseline-aware scoring): │
│ • root_exec_in_container (high) │
│ • shell_spawned_by_non_shell_parent (medium) │
│ • outbound_connection_from_container (medium, baseline-aware) │
│ • sensitive_file_read (high, always alert) │
│ • unseen_binary_exec (high, baseline-aware) │
│ • anomalous_process_tree (high, baseline-aware) │
│ 6. Output: JSON alerts to stdout (SIEM-ready) │
└─────────────────────────────────────────────────────────────────────────────┘
- execve/execveat: Captures every process execution with binary path, PID, PPID, UID, cgroup ID
- connect/accept/accept4: Captures outbound AND inbound connections with dest IP, port, protocol
- openat (filtered): Only emits events for sensitive paths (
/etc/shadow, SSH keys, Docker socket, etc.) — reduces noise
- Real-time enrichment: cmdline, container ID, username
- Behavioral Baselining: Per-container learning phase (configurable, default 10 min) builds allowlists of binaries, network destinations, process tree edges
- Anomaly Scoring: Deviations from baseline increment a running risk score per container (exponential moving average)
- Baseline-Aware Rules: Rules check baseline before alerting (e.g., only alert on new outbound destinations after learning)
- Persistence: Baselines saved to
baseline_data/<container_id>.json— survives agent restarts - Configurable: All thresholds/weights via
agent-config.yaml
| Rule | Severity | Baseline-Aware | Description |
|---|---|---|---|
root_exec_in_container |
High | ✅ | Root exec of binary not seen during learning |
shell_spawned_by_non_shell_parent |
Medium | ✅ | Shell spawned by non-shell parent; checks baseline for parent→child edge |
outbound_connection_from_container |
Medium | ✅ | Outbound connection to destination not in baseline (ignores loopback, accept) |
sensitive_file_read |
High | ❌ (always) | Read of /etc/shadow, SSH keys, Docker socket by non-allowed process |
unseen_binary_exec |
High | ✅ | Binary execution not in baseline allowlist |
anomalous_process_tree |
High | ✅ | Parent→child process relationship not seen during learning |
Linux environment (Ubuntu 22.04+/Debian 12+, kernel 5.15+) with:
- Kernel with
CONFIG_DEBUG_INFO_BTF=y(check:grep CONFIG_DEBUG_INFO_BTF /boot/config-$(uname -r)) - LLVM/Clang ≥ 14
- libbpf development headers
- Go ≥ 1.21
Install on Ubuntu/Debian:
sudo apt update
sudo apt install -y clang llvm libbpf-dev linux-headers-$(uname -r) bpftool make go gitVerify BTF is available:
ls -l /sys/kernel/btf/vmlinuxExtract kernel BTF to vmlinux.h and generate Go bindings via bpf2go:
make generateThis creates bpf/vmlinux.h and gen/*_bpf.go files.
Compile the userspace Go agent binary (Linux target):
make buildOutput: ./ebpf-runtime-agent
Requires root (CAP_BPF, CAP_PERFMON, CAP_SYS_ADMIN):
sudo ./ebpf-runtime-agent run [config.yaml]Default config: agent-config.yaml in working directory.
Run options:
# Run with all events logged as JSON (for SIEM ingestion)
sudo ./ebpf-runtime-agent run --log-all-events
# Run with web dashboard on port 8080
sudo ./ebpf-runtime-agent run --web --web-port 8080
# Custom config and baseline directory
sudo ./ebpf-runtime-agent run -c /etc/agent-config.yaml --baseline-dir /var/lib/ebpf-agent# Run the agent
sudo ./ebpf-runtime-agent run [flags]
# Show agent status
sudo ./ebpf-runtime-agent status
# Reset baseline for a container
sudo ./ebpf-runtime-agent baseline reset <container_id>
# Show version
./ebpf-runtime-agent version
# Help
./ebpf-runtime-agent --help
./ebpf-runtime-agent run --help# Terminal 1: Run agent
sudo ./ebpf-runtime-agent run
# Terminal 2: Trigger detections
docker run --rm alpine whoami # root_exec_in_container
docker run --rm alpine sh -c "echo hello" # shell_spawned_by_non_shell_parent
docker run --rm alpine wget -qO- http://example.com # outbound_connection
sudo cat /etc/shadow # sensitive_file_readThe test/ directory provides a complete demo environment:
- Start test containers (benign workloads for baseline learning):
cd test
docker compose up -dThis starts:
test-web: nginx on port 8080test-python-app: Python script polling httpbin.org every 30stest-alpine: Idle Alpine container for manual testing
- Run the agent and wait for baseline learning to complete (default 10 min):
sudo ../ebpf-runtime-agent runWatch for "Learning complete for container X" logs (or just wait 10 min).
- Simulate attacks against
test-alpine:
./attack-sim.sh test-alpineThis triggers all 6 rule types in sequence. Observe the agent's JSON alerts and escalating risk scores.
- Cleanup:
docker compose downbaseline_window_minutes: 10 # Learning phase duration
risk_threshold: 40.0 # Alert threshold (not currently used for gating)
rule_weights:
root_exec: 15.0
shell_spawned_by_non_shell_parent: 20.0
outbound_connection_from_container: 10.0
sensitive_file_read: 30.0
alert_on_startup: true # Emit alerts during learning phasebaseline_window_minutes: How long the agent observes before considering the baseline "learned." During learning, anomaly scores are dampened and baseline-aware rules don't fire.rule_weights: Score added to container's running risk score when a rule triggers. Higher = more severe.risk_threshold: Reserved for future use (e.g., gating alerts, web dashboard coloring).
All alerts are emitted as structured JSON to stdout:
{
"timestamp": "2026-09-02T15:04:05.000Z",
"severity": "high",
"rule": "unseen_binary_exec",
"message": "Unseen binary execution in container a1b2c3d4: /usr/bin/curl",
"pid": 12345,
"ppid": 12300,
"uid": 0,
"username": "root",
"comm": "curl",
"container_id": "a1b2c3d4e5f67890...",
"cmdline": "curl --version"
}This is SIEM-ready (Elastic, Splunk, Loki, etc. can ingest directly).
Enable --log-all-events to stream every enriched event (not just alerts) as JSON lines:
sudo ./ebpf-runtime-agent run --log-all-events | jq .Example event:
{
"type": "EXECVE",
"timestamp": "2026-09-02T15:04:05.123Z",
"pid": 12345,
"ppid": 12300,
"uid": 0,
"cgroup_id": 42,
"username": "root",
"comm": "curl",
"container_id": "a1b2c3d4e5f67890...",
"cmdline": "curl --version",
"filename": "/usr/bin/curl"
}Pipe to log shippers (Filebeat, Fluent Bit, Vector) for Elastic/Splunk/Datadog.
Enable a minimal live dashboard:
sudo ./ebpf-runtime-agent run --web --web-port 8080Then open http://<host>:8080 in a browser. Features:
- Live container risk scores (auto-refreshes every 5s)
- Learning phase status per container
- Observed binaries/destinations counts
- Color-coded risk levels (green/yellow/red)
The dashboard is a single embedded HTML page — no external dependencies.
| Limitation | Details |
|---|---|
| Linux only | eBPF CO-RE requires Linux kernel ≥ 5.8 with BTF. No Windows/macOS support. |
| Single host | No multi-node/Kubernetes support. Designed for single-host Docker/containerd. |
| Heuristic scoring | Not ML-based. Anomaly scores use explainable weighted heuristics (transparent, interview-friendly). |
| No enforcement | Detection only. No process killing/blocking (future work). |
| Kernel version | Requires kernel ≥ 5.8 with BTF. Older kernels need BCC (not supported here). |
| cgroup v1/v2 | Container ID extraction works for standard Docker/containerd cgroup layouts. May need tuning for exotic runtimes. |
| Ring buffer loss | Under extreme load, ring buffer may drop events (256KB per program). Tunable in C code. |
| IPv6 parsing | Basic support; dest_port byte-order handled but IPv6 scope IDs not fully parsed. |
.
├── bpf/ # eBPF C programs (kernel-space)
│ ├── common.h # Shared struct definitions
│ ├── execve.c # execve/execveat tracepoints
│ ├── connect.c # connect/accept/accept4 tracepoints
│ └── openat.c # openat tracepoint (sensitive-only)
├── gen/ # Generated Go bindings (bpf2go output)
│ ├── generate.go # go:generate directives
│ └── *_bpf.go # Generated loader code
├── internal/
│ ├── baseline/ # Baseline store + anomaly scoring
│ ├── config/ # YAML config loading
│ ├── enrich/ # PID → cmdline, container ID, username
│ ├── events/ # Event parsing + types
│ ├── loader/ # eBPF program loading + attachment
│ ├── output/ # JSON console sink (extensible)
│ └── rules/ # Detection rules + engine
├── test/ # Demo environment
│ ├── docker-compose.yml # Benign test containers
│ └── attack-sim.sh # Attack simulation script
├── agent-config.yaml # Default configuration
├── main.go # CLI entrypoint (Cobra)
├── Makefile # Build automation
├── setup-vm.sh # Fresh Ubuntu VM setup
├── ebpf-runtime-agent.service # systemd service
├── .gitignore
└── go.mod / go.sum # Go module
- Add C program in
bpf/<name>.cwithSEC("tracepoint/...")and ringbuf - Add
go:generateline ingen/generate.go - Run
make generate - Update
internal/loader/loader.goto load/attach new program - Update
internal/events/event.goif new fields needed
- Implement
Ruleinterface ininternal/rules/rules.go - Register in
NewRuleEngineininternal/rules/engine.go - Add weight to
agent-config.yamlunderrule_weights
After modifying .c files or common.h:
make generate
make build- Kubernetes integration (pod/namespace awareness via kubelet API)
- Active response: kill container, block network, pause cgroup
- Web dashboard (serving live risk scores via embedded HTTP) — implemented in Phase 3
- ML-based anomaly detection (replace heuristic scoring)
- Syscall frequency anomaly detection (statistical models)
- File integrity monitoring (inotify/fanotify + eBPF)
- Encrypted traffic analysis (TLS SNI extraction via uprobe)
MIT License — see LICENSE file (if added).