eBPF Reachability Evidence System
Audience: operators and engineers collecting and verifying runtime reachability evidence in Stella Ops.
This documentation covers the eBPF-based runtime reachability evidence collection system in Stella Ops — the self-hosted DevOps vulnerability-scanner and release control plane.
Overview
The eBPF reachability system provides kernel-level syscall tracing to prove which code paths, files, and network connections were (or were not) executed in production. This runtime evidence complements static analysis with proof of actual behavior, feeding reachability-aware release decisions.
The collector lives in the Signals module as the StellaOps.Signals.Ebpf library (src/Signals/__Libraries/StellaOps.Signals.Ebpf/). The BPF programs (tracepoints/uprobes), CO-RE probe loader, NDJSON evidence writer, DSSE chunk signer, and Rekor submission paths are all present in source.
Implementation maturity (verify against source): The kernel attach path in
CoreProbeLoaderis not yet wired to libbpf —LoadAndAttachAsynccurrently backs the ring buffer with an in-processSimulatedRingBufferand is marked with an inline “In real implementation, this would: 1. Load BPF object using libbpf …” note. The BPF C sources (Probes/Bpf/*.bpf.c), the deterministic NDJSON writer (RuntimeEvidenceNdjsonWriter), the Attestor/Rekor-backed signer (AttestorEvidenceChunkSigner), and the chunk-chain finalizer/verifier (EvidenceChunkFinalizer) are implemented. Treat live kernel-level collection as not yet end-to-end wired until the libbpf load path lands; the signing, chaining, and verification layers above it are real.
Key Capabilities
- Syscall Tracing: Capture file access (
openat), process execution (exec), and network connections (inet_sock_set_state) - User-Space Probes: Monitor libc network functions and OpenSSL TLS operations
- Container Awareness: Automatic correlation of events to container IDs and image digests
- Signed Evidence Chains: DSSE-signed chunks (in-toto statement, predicate type
stella.ops/runtime-evidence@v1) with Rekor transparency log integration and previous-chunk hash linking - Deterministic Output: Canonical NDJSON (snake_case keys, no indentation, SHA-256 rolling content hash per chunk)
Authorization scopes
Reachability/runtime-signal APIs are gated by the canonical scopes in StellaOpsScopes.cs:
| Scope | Const | Purpose |
|---|---|---|
signals:read | SignalsRead | Read-only access to reachability signals |
signals:write | SignalsWrite | Write reachability signals |
signals:admin | SignalsAdmin | Administrative access to reachability signal ingestion |
Quick Start
Prerequisites
- Linux kernel 5.x+ with BTF support (4.14+ with external BTF)
- Container runtime (containerd, Docker, or CRI-O)
- StellaOps CLI installed
Inspect and Verify Runtime Evidence
The stella config signals CLI group (src/Cli/StellaOps.Cli/Commands/SignalsCommandGroup.cs) exposes inspect, list, summary, and verify-chain. There is no top-level stella signals group and no CLI start/status command; collection is enabled in-process by registering AddEbpfRuntimeEvidence(...) in the host.
Current status (2026-07-29):
inspect,list, andsummaryare discoverable but fail closed with exit code9and emit no signal data until a persisted query client is wired.verify-chainis implemented: it reads*.dsse.jsonsidecars, decodes the DSSE/in-toto statements, and verifies chunk linkage and sequence continuity.
# Not implemented; exits 9 and emits no signal rows
stella config signals inspect sha256:abc123... --format json
# Not implemented; exits 9
stella config signals list
# Not implemented; exits 9
stella config signals summary <target>
# Verify evidence chain integrity over a directory of signed chunks (fully functional)
stella config signals verify-chain /var/lib/stellaops/evidence
Configuration
Collection is configured through the EbpfEvidenceOptions object passed to AddEbpfRuntimeEvidence(...) (src/Signals/__Libraries/StellaOps.Signals.Ebpf/ServiceCollectionExtensions.cs), which composes NdjsonWriterOptions (rotation/compression), RuntimeEvidenceCollectorOptions, and RuntimeBtfSelectionOptions. Chunk signing uses a separate EvidenceChunkFinalizerOptions. The option names below are taken verbatim from source — there is no top-level signals: YAML config schema in the codebase for these settings.
For offline/air-gapped hosts there is a convenience overload AddEbpfRuntimeEvidenceAirGap(...), which is equivalent to AddEbpfRuntimeEvidence with UseAirGapMode = true. When air-gap mode is enabled the registration also wires an IAirGapProbeLoader (Probes/AirGapProbeLoader.cs), a pre-compiled CO-RE probe loader that loads probe bundles from disk with no network dependency.
services.AddEbpfRuntimeEvidence(options =>
{
options.OutputDirectory = "/var/lib/stellaops/evidence"; // default
options.ProcRoot = "/proc"; // default
options.CgroupRoot = "/sys/fs/cgroup"; // default
options.ProbeDirectory = null; // null => probes resolved from /usr/share/stellaops/probes, /opt/stellaops/probes, or <BaseDir>/probes
options.UseAirGapMode = false; // true => also registers IAirGapProbeLoader
options.SymbolCacheSizeLimit = 100_000;
// Rotation / compression (NdjsonWriterOptions):
options.WriterOptions = new()
{
MaxChunkSizeBytes = 100 * 1024 * 1024, // 100 MB, size-based rotation
MaxChunkDuration = TimeSpan.FromHours(1), // time-based rotation
UseGzipCompression = false,
BufferSize = 64 * 1024,
};
// Deterministic BTF source selection (RuntimeBtfSelectionOptions),
// used by the legacy RuntimeSignalCollector adapter:
options.BtfSelectionOptions = new()
{
ExternalVmlinuxPaths = [], // candidate full-kernel vmlinux BTF paths, deterministic order
SplitBtfDirectories = [], // candidate split-BTF root dirs, deterministic order
};
// RuntimeBtfSourceSelector (Services/RuntimeBtfSourceSelector.cs) prefers the kernel's
// own /sys/kernel/btf/vmlinux, then the configured external/split-BTF candidates
// (defaults under /var/lib|/usr/share|/usr/lib/stellaops/btf/split) — this is what
// lets the same probes load offline / across kernels without per-host recompilation.
});
// Chunk signing + Rekor submission is configured separately via EvidenceChunkFinalizerOptions:
// SigningKeyId (default "default"), SubmitToRekor (default true),
// CollectorVersion, KernelVersion, HostId, ChainStateDirectory.
// Signing is delegated to the Attestor service (AttestorEvidenceChunkSigner) which
// produces an in-toto/DSSE statement (predicateType "stella.ops/runtime-evidence@v1")
// and submits it to Rekor via IRekorClient.
Flagged — partial / not in the C# options model: the previously documented
filters.target_containers,filters.path_allowlist, andfilters.path_denylistsettings, and thesigning.key_id: fulciovalue, do not exist as configurable surface inStellaOps.Signals.Ebpf— the C#EbpfEvidenceOptionsmodel has no path-filter knobs, andEvidenceChunkFinalizerOptions.SigningKeyIddefaults to"default", notfulcio. Note the kernel-side primitives do exist: the BPF C sources define apath_filtershash map plus anopenat_config(filter_proc_sys/filter_devflags) inProbes/Bpf/syscall_openat.bpf.c, and adest_cidr_filtersmap insyscall_network.bpf.c. But because the libbpf load path is still simulated (see maturity note above) and no C# option populates those maps, path/CIDR filtering is not wired end-to-end today. Surfacing it throughEbpfEvidenceOptionsis roadmap, not current behavior.
Documentation Index
| Document | Description |
|---|---|
| ebpf-architecture.md | System design and data flow |
| evidence-schema.md | NDJSON schema reference |
| probe-reference.md | Tracepoint and uprobe details |
| deployment-guide.md | Kernel requirements and installation |
| operator-runbook.md | Operations and troubleshooting |
| security-model.md | Threat model and mitigations |
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ User Space │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Zastava │ │ Scanner │ │ RuntimeSignalCollector │ │
│ │ Container │ │ Reachability │ │ │ │
│ │ Tracker │ │ Merger │ │ ┌─────────────────┐ │ │
│ └──────┬──────┘ └──────┬───────┘ │ │ EventParser │ │ │
│ │ │ │ └────────┬────────┘ │ │
│ │ │ │ │ │ │
│ └────────┬───────┘ │ ┌────────▼────────┐ │ │
│ │ │ │ CgroupResolver │ │ │
│ ┌────────▼────────┐ │ └────────┬────────┘ │ │
│ │ RuntimeEvent │ │ │ │ │
│ │ Enricher │◄────────┤ ┌────────▼────────┐ │ │
│ └────────┬────────┘ │ │SymbolResolver │ │ │
│ │ │ └────────┬────────┘ │ │
│ ┌────────▼────────┐ │ │ │ │
│ │ NDJSON Writer │◄────────┼───────────┘ │ │
│ └────────┬────────┘ │ │ │
│ │ └─────────────────────────┘ │
│ ┌────────▼────────┐ │
│ │ ChunkFinalizer │──────► Signer ──────► Rekor │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
──────────┼──────────
│
┌─────────────────────────────┼───────────────────────────────────┐
│ Kernel │Space │
│ │ │
│ ┌──────────────────────────▼───────────────────────────────┐ │
│ │ Ring Buffer │ │
│ └──────────────────────────▲───────────────────────────────┘ │
│ │ │
│ ┌──────────────┐ ┌────────┴───────┐ ┌──────────────────┐ │
│ │ Tracepoints │ │ Uprobes │ │ BPF Maps │ │
│ │ │ │ │ │ │ │
│ │ sys_openat │ │ libc:connect │ │ cgroup_filter │ │
│ │ sched_exec │ │ libc:accept │ │ symbol_cache │ │
│ │ inet_sock │ │ SSL_read/write │ │ pid_namespace │ │
│ └──────────────┘ └────────────────┘ └──────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
Diagram-to-source mapping (verified against src/Signals/__Libraries/StellaOps.Signals.Ebpf/):
| Diagram box | Source type | Notes |
|---|---|---|
| RuntimeSignalCollector | Services/RuntimeSignalCollector.cs (+ RuntimeEvidenceCollector.cs) | Real; the RuntimeSignalCollector is the legacy adapter, RuntimeEvidenceCollector is the unified collector |
| EventParser | Parsers/EventParser.cs | Real |
| CgroupResolver | Cgroup/CgroupContainerResolver.cs | Real (class is CgroupContainerResolver) |
| SymbolResolver | Symbols/EnhancedSymbolResolver.cs (ISymbolResolver) | Real |
| RuntimeEventEnricher | Enrichment/RuntimeEventEnricher.cs | Real |
| NDJSON Writer | Output/RuntimeEvidenceNdjsonWriter.cs | Real |
| ChunkFinalizer | Signing/EvidenceChunkFinalizer.cs | Real |
| Signer → Rekor | Signing/AttestorEvidenceChunkSigner.cs | Real; delegates to Attestor signing + IRekorClient |
| Tracepoints / Uprobes / BPF Maps | Probes/Bpf/*.bpf.c, stella_common.h | C sources present (openat/exec/inet_sock tracepoints; libc connect/accept, OpenSSL SSL_read/write uprobes) |
| Ring Buffer | Probes/CoreProbeLoader.cs | Simulated today (SimulatedRingBuffer); libbpf attach not yet wired |
| (Air-gap probe loader) | Probes/AirGapProbeLoader.cs (IAirGapProbeLoader) | Real; registered only when UseAirGapMode = true / via AddEbpfRuntimeEvidenceAirGap. Loads pre-compiled CO-RE probe bundles offline. Not shown in the diagram above |
| (BTF source selector) | Services/RuntimeBtfSourceSelector.cs (RuntimeBtfSelectionOptions) | Real; deterministic selection of kernel /sys/kernel/btf/vmlinux vs external/split-BTF candidates so probes load across kernels and offline. Not shown in the diagram above |
| Zastava Container Tracker | Enrichment/IContainerStateProvider.cs | Integration interface only — not a bundled component (see Related Documentation note) |
| Scanner Reachability Merger | — | No direct source class named this; conceptual integration point for merging runtime evidence into Scanner reachability output. Treat as roadmap/illustrative. |
Related Documentation
Note: Zastava is a real subsystem (CLI
zastavacommand group, Scanner Zastava source handlers,docs/modules/zastava/), but it is not a top-levelsrc/Zastava/directory. In this collector, Zastava integration is expressed as an interface —IContainerStateProvider, documented as “typically wrap[ping] Zastava’s ContainerStateTracker or similar” — rather than a hard-wired dependency. The “Zastava Container Tracker” box in the diagram above is an integration point, not a bundled component.
