Vivarium
Vivarium is an observation and sandbox helper for Ruby.

It combines:
- eBPF LSM monitoring via RbBCC (
vivariumd) - Ruby-side method boundary observation via
TracePoint(Vivarium.observe)
The goal is to visualize which Ruby method context triggered low-level events.
Current Scope
Implemented in this repository:
- BPF LSM hook on
file_open - BPF LSM hooks on
inode_symlink,inode_link,inode_rename,path_chmod - BPF tracepoint on
sys_enter_getdents64 - BPF tracepoint on
sys_enter_execve(captures executable path and first few argv entries asproc_exec) - BPF tracepoint on
sched_process_fork(tracks descendants and emitsproc_fork) - BPF LSM hooks for suspicious behavior checks:
ptrace_access_check(emitsptrace_check)sb_mount(emitssb_mount)kernel_read_file(emitskernel_read_file)task_kill(emitstask_kill)task_fix_setuid(emitssetid_change)capablefor high-risk capabilities only (emitscapable_check)bprm_creds_from_file(emitsbprm_creds)
- BPF LSM hook on
socket_create(flags unusual socket creation asodd_socket) - BPF LSM hook on
socket_connect(captures destination family/address/port assock_connect) - BPF tracepoints on
sys_enter_sendmsg,sys_enter_sendto,sys_enter_sendmmsg(capture UDP/53 DNS QNAME raw bytes asdns_req) - USDT probes for method span boundaries and exceptions (
span_start,span_stop,span_raise) - OpenSSL
SSL_writeuprobe event (ssl_write) - Shared pinned maps on bpffs
config_root_targets(root PID -> 0/1)config_spawned_targets(spawned TID -> 0/1)events(BPF_RINGBUF_OUTPUT, shared by system events and span events)
- Ruby API
Vivarium.observe do ... end- Registers current PID to
config_root_targets - eBPF tracks spawned descendants into
config_spawned_targetsviasched_process_fork TracePointemits span probes on allowlisted call/return and emitsspan_raiseon Ruby:raise- Correlator thread consumes ringbuf events and joins them to spans by
tid/time window - Renders a process tree once at session end
- Unregisters PID on block exit
- Registers current PID to
event_t currently:
struct event_t {
u64 ktime_ns;
u32 pid;
u32 tid;
char event_name[16];
char payload[256];
};
Requirements
- Linux kernel/environment supporting BPF LSM
libbccinstalledbpftoolinstalled (used to resolvestruct file::f_pathandstruct dentry::d_nameoffsets from BTF)- root privileges for
vivariumd - bpffs mounted (typically
/sys/fs/bpf)
Installation
Add to Gemfile:
gem "vivarium"
Then:
bundle install
Usage
1) Start daemon (root):
sudo bundle exec vivariumd
2) Observe in Ruby process:
require "vivarium"
Vivarium.observe do
File.read("/etc/passwd")
end
3) Network monitoring demo client:
bundle exec ruby examples/network_client_demo.rb
This demo intentionally triggers sock_connect, dns_req, and odd_socket events.
4) File operation demo client (only touches /tmp):
bundle exec ruby examples/file_operation_demo.rb
This demo intentionally triggers path_open, file_symlink, file_hardlink, file_rename, file_chmod, and file_getdents events under /tmp.
5) Execve demo client:
bundle exec ruby examples/execve_demo.rb
This demo intentionally triggers proc_exec with several argument patterns using direct execve-style process launches.
6) Signal demo client:
bundle exec ruby examples/signal_kill_demo.rb
This demo forks a child process and sends TERM with Process.kill, which is useful for triggering task_kill.
7) Privilege-related event demo client:
bundle exec ruby examples/privilege_event_demo.rb
This demo attempts setuid/setgid changes, sensitive file access, and sudo exec to trigger privilege-related events such as setid_change, capable_check, and bprm_creds.
You can also start top-level observation without a block (it keeps observing until process exit):
require "vivarium"
observer = Vivarium.top_observe
# or: Vivarium.observe
# do anything ...
observer.stop
Vivarium.observe / Vivarium.top_observe produce one process-tree report at session end (block exit, observer.stop, or process exit).
You can override pin directory via VIVARIUM_BPF_PIN_DIR on both sides:
VIVARIUM_BPF_PIN_DIR=/sys/fs/bpf/vivarium bundle exec vivariumd
Use Vivarium.bpf_pin_dir = "/sys/fs/bpf/..." in Ruby code to set it programmatically.
require "vivarium"
Vivarium.bpf_pin_dir = "/sys/fs/bpf/vivarium"
Development
Run tests:
bundle exec rake test
Daemon entrypoint:
bundle exec vivariumd --pin-dir /sys/fs/bpf/vivarium
Notes
- Thread/Ractor-awareness is not yet implemented.
- Current transport is ring buffer (
events) pinned under bpffs. - Ring buffer is single-consumer by nature; v1 supports a single observer per host.
payloadis 256 bytes inevent_t; some event types intentionally use smaller structured slices inside that buffer.proc_execcurrently stores the executable path plus up to 3 argv entries in 4 fixed 64-byte slots to keep the BPF verifier happy.span_raiseis emitted on Ruby:raiseand rendered asEXCPlines within the enclosing span.- Events that do not match any real span are grouped into synthetic
<no-span>spans. - Each event is tagged with severity metadata:
highforsetid_change,capable_check,bprm_creds,task_kill,ptrace_check,sb_mount, andkernel_read_file; others aremediumby default. capable_checkis intentionally filtered to high-risk capabilities to reduce noise from extremely frequentcapablehook calls.- Output format is textual process tree with a session header and per-span relative timing.
vivariumdresolvesstruct file::f_pathoffset from/sys/kernel/btf/vmlinuxat startup.vivariumdalso resolvesstruct dentry::d_nameoffset from/sys/kernel/btf/vmlinuxat startup.- You can override offsets manually with
VIVARIUM_FILE_F_PATH_OFFSETandVIVARIUM_DENTRY_D_NAME_OFFSETif auto-detection fails.
Contributing
Issues and pull requests are welcome.