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 CoreProbeLoader is not yet wired to libbpf — LoadAndAttachAsync currently backs the ring buffer with an in-process SimulatedRingBuffer and 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

Authorization scopes

Reachability/runtime-signal APIs are gated by the canonical scopes in StellaOpsScopes.cs:

ScopeConstPurpose
signals:readSignalsReadRead-only access to reachability signals
signals:writeSignalsWriteWrite reachability signals
signals:adminSignalsAdminAdministrative access to reachability signal ingestion

Quick Start

Prerequisites

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, and summary are discoverable but fail closed with exit code 9 and emit no signal data until a persisted query client is wired. verify-chain is implemented: it reads *.dsse.json sidecars, 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, and filters.path_denylist settings, and the signing.key_id: fulcio value, do not exist as configurable surface in StellaOps.Signals.Ebpf — the C# EbpfEvidenceOptions model has no path-filter knobs, and EvidenceChunkFinalizerOptions.SigningKeyId defaults to "default", not fulcio. Note the kernel-side primitives do exist: the BPF C sources define a path_filters hash map plus an openat_config (filter_proc_sys / filter_dev flags) in Probes/Bpf/syscall_openat.bpf.c, and a dest_cidr_filters map in syscall_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 through EbpfEvidenceOptions is roadmap, not current behavior.

Documentation Index

DocumentDescription
ebpf-architecture.mdSystem design and data flow
evidence-schema.mdNDJSON schema reference
probe-reference.mdTracepoint and uprobe details
deployment-guide.mdKernel requirements and installation
operator-runbook.mdOperations and troubleshooting
security-model.mdThreat 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 boxSource typeNotes
RuntimeSignalCollectorServices/RuntimeSignalCollector.cs (+ RuntimeEvidenceCollector.cs)Real; the RuntimeSignalCollector is the legacy adapter, RuntimeEvidenceCollector is the unified collector
EventParserParsers/EventParser.csReal
CgroupResolverCgroup/CgroupContainerResolver.csReal (class is CgroupContainerResolver)
SymbolResolverSymbols/EnhancedSymbolResolver.cs (ISymbolResolver)Real
RuntimeEventEnricherEnrichment/RuntimeEventEnricher.csReal
NDJSON WriterOutput/RuntimeEvidenceNdjsonWriter.csReal
ChunkFinalizerSigning/EvidenceChunkFinalizer.csReal
Signer → RekorSigning/AttestorEvidenceChunkSigner.csReal; delegates to Attestor signing + IRekorClient
Tracepoints / Uprobes / BPF MapsProbes/Bpf/*.bpf.c, stella_common.hC sources present (openat/exec/inet_sock tracepoints; libc connect/accept, OpenSSL SSL_read/write uprobes)
Ring BufferProbes/CoreProbeLoader.csSimulated 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 TrackerEnrichment/IContainerStateProvider.csIntegration interface only — not a bundled component (see Related Documentation note)
Scanner Reachability MergerNo direct source class named this; conceptual integration point for merging runtime evidence into Scanner reachability output. Treat as roadmap/illustrative.

Note: Zastava is a real subsystem (CLI zastava command group, Scanner Zastava source handlers, docs/modules/zastava/), but it is not a top-level src/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.