eBPF · LSM BPF enforcement

Runtime security that learns, then blocks - in the kernel

Pahlevan is an eBPF-powered Kubernetes runtime security tool. It self-learns per-workload baselines during a learning window, then enforces them in-kernel with LSM BPF - no hand-written rules.

  • Kubernetes 1.24+
  • eBPF CO-RE
  • Linux 5.8+
  • LSM BPF for enforcement
Pahlevan learn-then-enforce demo: a workload is profiled during a learning window, then an attacker's read of a sensitive file is denied in-kernel with EPERM
Learn the baseline, then deny the deviation in-kernel with EPERM.
Why Pahlevan

Most tools watch. Pahlevan closes the loop.

Kubernetes workloads face runtime attacks that slip past image scanning and perimeter defenses. Runtime tools either watch (alert-only) or make you write the rules yourself. Pahlevan does neither.

01

Auto-learning, not manual rules

Files a container opens and destinations it dials during a learning window are added to a per-cgroup allow-set automatically. Nothing to author, nothing to keep current, no rule language to learn.

02

Real in-kernel enforcement

Under enforcement, an open of an unlearned path or an egress to an unlearned destination is denied with EPERM by an LSM BPF program - the syscall never succeeds. Detection tools only tell you it already happened.

03

Accurate attribution

Events are tied to the real container via bpf_get_current_cgroup_id(), and file paths are resolved in-kernel with bpf_d_path(). A seccomp profile is generated from the learned syscall set.

How it works

Seven kernel programs, one loop

Every program below observes during the learning window and refuses during enforcement, scoped to a single cgroup. The allow-set is whatever the workload did while it was being watched.

Kernel program What it sees What it does about it
lsm/file_open Every open, with the path resolved in-kernel by bpf_d_path EPERM on a path outside the learned set
lsm/socket_connect Every outbound connect, IPv4 and IPv6, named against cluster Services EPERM on an unlearned destination
lsm/bprm_check_security Every exec: binary, argv, working directory, four levels of ancestry EPERM, or SIGKILL, on an unlearned binary
lsm/capable Every capability check, plus the task's effective, permitted and inheritable sets EPERM on a capability never exercised
kprobe/commit_creds The moment privilege actually changes - including changes no syscall asked for SIGKILL when privilege is gained with no execve to explain it
tracepoint/sys_enter Every syscall with its six arguments; escalation primitives report every occurrence Becomes the generated seccomp profile
uretprobe/readline Commands typed at an interactive prompt, builtins included Records what someone with a shell actually did
Architecture

A privileged data plane, an unprivileged control plane

Pahlevan splits the per-node eBPF data plane from a leader-elected control plane. The agent owns everything that touches the kernel; the operator owns everything that touches the Kubernetes API.

Agent · DaemonSet (privileged)

Runs on every node and owns the eBPF data plane: loading and attaching CO-RE programs, building per-container baselines, and enforcing locally in the kernel.

Operator · Deployment (leader-elected)

Requires no host access and runs in a user namespace (hostUsers: false), handling policy lifecycle, cluster-wide status aggregation, and CEL-based admission via a ValidatingAdmissionPolicy - no webhook.

Features

Built for enforcement, not just visibility

Adaptive learning

Per-cgroup allow-sets built from observed file opens and egress destinations during a learning window.

In-kernel file enforcement

lsm/file_open denies unlearned opens with EPERM, resolving full paths via bpf_d_path.

Egress enforcement

lsm/socket_connect denies connections to destinations outside the learned allow-set.

Full syscall observation

A raw_tracepoint/sys_enter sees every syscall, deduplicated in-kernel per (cgroup, syscall).

Generated seccomp profiles

A seccomp profile is generated from the learned syscall set - least privilege without hand-authoring.

CO-RE eBPF

Compile-once / run-everywhere programs, portable across kernels without per-node compilation.

Kubernetes-native

A PahlevanPolicy CRD drives a selector → learn → enforce lifecycle.

Self-healing

Automatic policy rollback when enforcement disrupts a workload.

Least-privilege control plane

The operator runs with hostUsers: false and needs no host access.

Constrains who execs, not just what

The allow-set cannot tell your app running python3 from an injected command running it. processFilter constrains the parent process, uid and gid at bprm_check_security.

Destinations have names

A denial reads prod/postgres:5432, resolved from Services and pods the agent already caches. An address the cluster has never seen is tagged external — the difference between a misconfiguration and exfiltration.

One resource across three signals

Events ship as OTLP log records to the same collector as the metrics and traces, sharing the attributes Grafana joins on. Loki, Tempo and Mimir correlate without a hand-written query.

See what a policy really does

pahlevan policy explain -f policy.yaml translates it offline and names every part the data plane cannot enforce. --strict fails a CI gate.

Learn → enforce

How it works

  1. 1

    Select

    A PahlevanPolicy selector matches target pods; the agent identifies their cgroups via bpf_get_current_cgroup_id().

  2. 2

    Learn

    During the learning window, every file a container opens (path resolved with bpf_d_path) and every egress destination is added to that cgroup's allow-set. Syscalls are observed in parallel.

  3. 3

    Transition

    On autoTransition (or when you flip the mode), the policy moves to enforcement and a seccomp profile is generated from the learned syscalls.

  4. 4

    Enforce

    An open of an unlearned path - or egress to an unlearned destination - is denied in-kernel with EPERM before the operation completes.

Quick Start

Install, then apply an adaptive policy

1. Install the operator and node agents

kubectl apply -f https://github.com/obsernetics/pahlevan/releases/latest/download/install.yaml

Or with Helm

helm repo add pahlevan https://obsernetics.github.io/pahlevan/charts
helm repo update
helm install pahlevan pahlevan/pahlevan-operator \
  -n pahlevan-system --create-namespace

2. Watch it learn, then enforce

kubectl get pahlevanpolicy nginx-security -w

Start in Monitoring. Flip enforcementConfig.mode to Blocking once you trust the learned baseline to have denials enforced in the kernel.

Apply an adaptive policy

apiVersion: policy.pahlevan.io/v1alpha1
kind: PahlevanPolicy
metadata:
  name: nginx-security
spec:
  selector:
    matchLabels:
      app: nginx
  learningConfig:
    duration: 5m          # observe normal behavior
    autoTransition: true  # then enforce automatically
  enforcementConfig:
    mode: Monitoring      # use Blocking to deny in-kernel
  selfHealing:
    enabled: true         # roll back if it breaks the workload
Measured, not marketing

Every number here came from a run

test/benchmark/run.sh drives 26 attack scenarios and 3 benign controls through a real workload inside a kernel-isolated VM, twice: once with no agent installed at all, then with Pahlevan learning and enforcing.

The control pass is the part that makes the rest trustworthy. Without it, a scenario that silently failed to execute is indistinguishable from one that was prevented, and a CPU figure has no idle node to subtract. Scenarios are mapped to 27 MITRE ATT&CK for Containers techniques and committed alongside the harness, so a run is reproducible rather than reported.

Read the benign controls first. Pahlevan blocks some of them, because curl and cat are outside a learned baseline too. That is the trade the whole design makes, not a footnote to it: a baseline narrow enough to stop an attacker is narrow enough to stop an administrator.

Methodology & recorded runs ↗
Install

Three ways to get Pahlevan running

The container image ships the agent, operator, and CLI in one distroless image on GHCR. Pick Helm, the single-file manifest, or pull the image directly.

Helm (recommended)

helm repo add pahlevan https://obsernetics.github.io/pahlevan/charts
helm repo update
helm install pahlevan pahlevan/pahlevan-operator \
  -n pahlevan-system --create-namespace

kubectl (single manifest)

kubectl apply -f https://github.com/obsernetics/pahlevan/releases/latest/download/install.yaml

Container image (GHCR)

docker pull ghcr.io/obsernetics/pahlevan:latest
# or pin a version
docker pull ghcr.io/obsernetics/pahlevan:v2.1.0

One distroless image contains pahlevan-agent (privileged DaemonSet), pahlevan-operator (leader-elected Deployment), and the pahlevan CLI. The Helm chart defaults its image to ghcr.io/obsernetics/pahlevan.

After installing, apply a PahlevanPolicy to start the learn → enforce loop.

Requirements

  • Kubernetes v1.24+ - the user-namespace operator needs v1.30+.
  • Linux kernel 5.8+ for observation (CO-RE, ring buffer, CAP_BPF).
  • Kernel 5.7+ with CONFIG_BPF_LSM and lsm=bpf for in-kernel enforcement. Monitoring-only mode works without the BPF LSM.

Benchmarks

Pahlevan ships a reproducible harness built from real attack scenarios, with a no-agent control pass to give the numbers a denominator. Because enforcement requires the BPF LSM, it runs inside an eBPF-capable VM.

Results are generated by the harness, not hand-written - no numbers are published until a run is committed.

Benchmark methodology & results ↗

Stop watching. Start blocking.

Learn what your workloads do, then enforce it in the kernel - without writing a single rule.