kfeatures

kfeatures is a pure-Go library that answers this question.

It probes kernel capabilities at runtime and returns actionable diagnostics: not just unsupported, but why and how to fix it.

if err := kfeatures.Check(kfeatures.FeatureBPFLSM, kfeatures.FeatureBTF); err != nil {
    var fe *kfeatures.FeatureError
    if errors.As(err, &fe) {
        log.Fatalf("%s - %s", fe.Feature, fe.Reason)
        // Output: BPF LSM - CONFIG_BPF_LSM=y but 'bpf' not in active LSM list; add lsm=...,bpf to kernel boot params
    }
}

The same answers are available from the CLI for CI/CD gating (semantic exit codes), and from --mcp mode for AI agents (JSON-RPC over stdio, every subcommand exposed as an MCP tool with a discoverable input schema). See CLI.

Why not cilium/ebpf/features or bpftool?§

cilium/ebpf/features answers: "Does this kernel support program type X?"

bpftool feature probe answers: "What BPF features does this kernel have?" (CLI only, not embeddable in Go)

Neither tells you whether your tool can actually run. For example, BPF LSM requires three things simultaneously: CONFIG_BPF_LSM=y in the kernel config, bpf in the active LSM boot parameter list, and the LSM program type supported by the running kernel. cilium/ebpf/features can only check the last one. bpftool can check the first and last, but not the second. Neither provides remediation guidance.

Capabilitycilium/ebpf/featuresbpftool feature probekfeatures
BPF program type probes
BPF map type / helper probes✓ †
BTF availability (/sys/kernel/btf/vmlinux)✗ *
Kernel config parsing (any CONFIG_*, =y/=m)
Active LSM list (/sys/kernel/security/lsm)
BPF LSM enabled (config + boot params + program type)
IMA detection (LSM list + securityfs directory)
IMA active measurement (runtime policy detection)
Process capabilities (CAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON)
Unprivileged BPF status
Mount-state gates (bpffs/tracefs/custom paths via superblock magic)
ELF requirement extraction (parse .o, derive requirements)
Composite feature validation
Actionable diagnostics (remediation steps)
Selective probing (minimize overhead)✓ ‡✗ §
Pure Go, no CGO
Usable as a Go library

* bpftool checks CONFIG_DEBUG_INFO_BTF in the kernel config but does not verify /sys/kernel/btf/vmlinux exists. † Exposed in kfeatures as parameterized requirements (RequireMapType, RequireProgramHelper) consumed by Check(...). cilium/ebpf/features is per-function: callers invoke individual probe functions on demand. § bpftool feature probe runs the full probe set on every invocation.

Other Go projects (libbpfgo, Tetragon, Falco libs) have some feature detection built in, but none is a standalone reusable library. They are either CGO-dependent, tightly coupled to their parent project, or written in C/C++.

Installation§

Library:

go get github.com/leodido/kfeatures

CLI binary (Linux amd64 / arm64):

# Replace VERSION (e.g. 0.5.1) and ARCH (amd64 or arm64).
curl -sSLO "https://github.com/leodido/kfeatures/releases/download/v${VERSION}/kfeatures_${VERSION}_linux_${ARCH}.tar.gz"
tar xzf "kfeatures_${VERSION}_linux_${ARCH}.tar.gz"
./kfeatures version

For supply-chain verification of the binary before extracting, see Verifying releases below.

Usage§

Quick check§

Validate that required kernel features are available:

import (
    "errors"
    "log"

    "github.com/leodido/kfeatures"
)

if err := kfeatures.Check(kfeatures.FeatureBPFLSM, kfeatures.FeatureBTF); err != nil {
    var fe *kfeatures.FeatureError
    if errors.As(err, &fe) {
        log.Fatalf("kernel not ready: %s - %s", fe.Feature, fe.Reason)
    }
}

Mixed requirements§

Combine Feature enums with parameterized workload requirements:

import (
    "github.com/cilium/ebpf"
    "github.com/cilium/ebpf/asm"
    "github.com/leodido/kfeatures"
)

err := kfeatures.Check(
    kfeatures.FeatureBTF,
    kfeatures.RequireProgramType(ebpf.XDP),
    kfeatures.RequireMapType(ebpf.Hash),
    kfeatures.RequireProgramHelper(ebpf.XDP, asm.FnMapLookupElem),
)

Custom mount paths (RequireMount)§

Gate on a filesystem mounted at an arbitrary path with the expected superblock magic. Useful when bpffs lives somewhere other than /sys/fs/bpf:

import (
    "github.com/leodido/kfeatures"
    "golang.org/x/sys/unix"
)

err := kfeatures.Check(
    kfeatures.RequireMount("/run/bpf", unix.BPF_FS_MAGIC),
)

Magic constants come from golang.org/x/sys/unix (e.g. unix.BPF_FS_MAGIC, unix.TRACEFS_MAGIC, unix.CGROUP2_SUPER_MAGIC).

Reusable presets (FeatureGroup)§

FeatureGroup packages a set of requirements as a single value you can pass anywhere a Requirement is accepted:

var TracingTool = kfeatures.FeatureGroup{
    kfeatures.FeatureBTF,
    kfeatures.FeatureKprobeMulti,
    kfeatures.RequireProgramType(ebpf.Kprobe),
}

if err := kfeatures.Check(TracingTool); err != nil {
    log.Fatal(err)
}

Extract requirements from a compiled object (FromELF)§

Point FromELF at an eBPF .o and get back a FeatureGroup describing its program types, map types, and helper-per-program requirements (directly consumable by Check):

reqs, err := kfeatures.FromELF("./bpf/probe.o")
if err != nil {
    log.Fatal(err)
}
if err := kfeatures.Check(reqs); err != nil {
    log.Fatalf("kernel cannot run probe.o: %v", err)
}

Output is deterministic (deduplicated, stably ordered). Unknown ELF kinds fail closed.

Render remediation (Diagnose)§

Check returns the diagnosis for the first failing feature. To inspect any feature against a single probe snapshot, call Diagnose directly:

sf, _ := kfeatures.Probe()
if !sf.BPFLSMEnabled.Supported {
    fmt.Println(sf.Diagnose(kfeatures.FeatureBPFLSM))
    // CONFIG_BPF_LSM=y but 'bpf' not in active LSM list; add lsm=...,bpf to kernel boot params
}

Full probe§

Probe all features for diagnostics and reporting:

sf, err := kfeatures.Probe()
if err != nil {
    log.Fatal(err)
}
fmt.Println(sf)

Sample output (truncated):

Kernel: 6.1.0-generic

Program Types:
  LSM: yes
  kprobe: yes
  kprobe.multi: yes

Core:
  BTF: yes

Security Subsystems:
  BPF LSM enabled: yes
  IMA enabled: no
  IMA directory: yes
  Active LSMs: lockdown, capability, yama, apparmor, bpf

Filesystems:
  tracefs: yes
  bpffs: yes

Individual fields are typed and inspectable programmatically (see SystemFeatures).

Selective probing§

Probe only what you need:

sf, err := kfeatures.ProbeWith(
    kfeatures.WithProgramTypes(ebpf.LSM, ebpf.Kprobe),
    kfeatures.WithSecuritySubsystems(),
    kfeatures.WithCapabilities(),
)

WithX options select probe scope. They do not define requirements; use Check(...) for gating.

CLI§

A CLI is included for operator diagnostics, CI/CD gating, and AI-agent integration:

go install github.com/leodido/kfeatures/cmd/kfeatures@latest
kfeatures probe                                    # probe all features
kfeatures check --require bpf-lsm,btf,cap-bpf      # exit 0 if met, 1 otherwise
kfeatures probe --json                             # JSON output
kfeatures config                                   # display kernel config

CI/CD gating (semantic exit codes)§

kfeatures check exits 0 when all requirements are met and 1 when any are missing. Drop it into a Helm chart pre-install hook, an init container, or a CI job. With --json the verdict is a parse-friendly object on stdout:

$ kfeatures check --require bpf-lsm,btf --json
{
  "ok": false,
  "feature": "bpf-lsm",
  "reason": "CONFIG_BPF_LSM=y but 'bpf' not in active LSM list; add lsm=...,bpf to kernel boot params"
}
$ echo $?
1

Invocation errors (missing required flag, unknown flag, invalid value, unknown subcommand) emit a structured JSON envelope on stderr and exit with a semantic code so wrappers can distinguish "the user invoked us wrong" from "the kernel is missing a feature":

Exit codeCategoryExample
0OKcheck passed
1RuntimeFeatureError, probe failure, missing kernel config
10Inputmissing_required_flag: required flag not provided
11Inputinvalid_flag_value: wrong type or unknown enum
12Inputunknown_flag
14Inputunknown_command
$ kfeatures check --require bogus
{"error":"invalid_flag_value","exit_code":11,"flag":"require","got":"bogus","expected":"feature","command":"kfeatures check","message":"invalid argument \"bogus\" for \"-r, --require\" flag: unknown feature: \"bogus\" (available: …)"}
$ echo $?
11

Codes follow the structcli/exitcode categories: input errors 1019 are agent-fixable, runtime errors 19 are operator-fixable.

AI agents (--jsonschema, --mcp)§

kfeatures is built to be driven by LLM agents and code-generation tooling without --help scraping.

--jsonschema dumps a JSON Schema describing a command's flags. Use =tree to walk the entire subtree:

$ kfeatures check --jsonschema | jq '.title, .properties | keys'
"kfeatures check"
[
  "json",
  "require"
]

$ kfeatures --jsonschema=tree | jq 'map(.title) | map(select(test("^kfeatures( probe| check| config| version)?$")))'
[
  "kfeatures",
  "kfeatures check",
  "kfeatures config",
  "kfeatures probe",
  "kfeatures version"
]

(--jsonschema=tree walks every node, including cobra-generated help and completion leaves; filter with jq to the ones you care about.)

--mcp turns kfeatures into a Model Context Protocol server over stdio. Each runnable leaf command becomes an MCP tool whose input schema mirrors the cobra flag set; agents introspect via tools/list and invoke via tools/call:

// Claude Desktop / any MCP-aware client config
{
  "mcpServers": {
    "kfeatures": {
      "command": "kfeatures",
      "args": ["--mcp"]
    }
  }
}

Tools exposed: probe, check, config. The server stays alive across business-outcome errors (a failing check does not terminate the session), and invocation errors flow through the same structured envelope as the CLI. Pure stdlib JSON-RPC inside structcli; no extra heavy SDK dependency.

What it detects§

CategoryFeatures
Program typesLSM, kprobe, kprobe.multi, tracepoint, fentry
CoreBTF availability (CO-RE)
SecurityBPF LSM enabled, IMA enabled, IMA active measurement, active LSM list
Capabilities and runtime gatesCAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON, unprivileged BPF disabled, BPF stats enabled
Syscallsbpf(), perf_event_open()
JITenabled, hardened, kallsyms, memory limit, CONFIG_BPF_JIT_ALWAYS_ON
Filesystemstracefs, debugfs, securityfs, bpffs (gated tracefs/bpffs checks verify the filesystem is mounted with the expected superblock magic)
Custom mount gatesany path + superblock magic via RequireMount
Namespacesinitial user namespace, initial PID namespace
Parameterized workload requirementsprogram type, map type, helper-per-program-type via requirement items
ELF-derived requirementsprogram/map types and helper-per-program requirements via FromELF
Mitigation contextSpectre v1/v2 vulnerability status
Kernel configCONFIG_BPF_LSM, CONFIG_IMA, CONFIG_DEBUG_INFO_BTF, CONFIG_FPROBE, any CONFIG_*

Stability§

Pre-1.0. The public API may change between minor versions; breaking changes are called out explicitly in CHANGELOG.md. The FromELF contract (signature, determinism, fail-closed semantics) is frozen; see CONTRIBUTING.md.

Requirements§

  • Linux for runtime probing/checking (uses Linux-specific syscalls and sysfs).
  • FromELF is parser-only and works on any platform.
  • Some probes require CAP_BPF or CAP_SYS_ADMIN.

Verifying releases§

Every release artifact (each platform tarball and the checksums.txt) is signed with cosign keyless signing backed by GitHub's OIDC token. Each artifact has a sibling <artifact>.sigstore.json bundle containing the signature, the signing certificate (with the workflow identity baked in), and the Rekor transparency-log inclusion proof.

To verify before extracting (replace VERSION and ARCH):

curl -sSLO "https://github.com/leodido/kfeatures/releases/download/v${VERSION}/kfeatures_${VERSION}_linux_${ARCH}.tar.gz"
curl -sSLO "https://github.com/leodido/kfeatures/releases/download/v${VERSION}/kfeatures_${VERSION}_linux_${ARCH}.tar.gz.sigstore.json"

cosign verify-blob \
  --bundle "kfeatures_${VERSION}_linux_${ARCH}.tar.gz.sigstore.json" \
  --certificate-identity "https://github.com/leodido/kfeatures/.github/workflows/release.yaml@refs/tags/v${VERSION}" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  "kfeatures_${VERSION}_linux_${ARCH}.tar.gz"

A successful verification proves that the artifact was produced by the release.yaml workflow at the tagged revision, signed by GitHub's OIDC issuer, and is recorded on the public Rekor transparency log. Requires cosign v2.0+.

Contributing§

See CONTRIBUTING.md for the API model, the feature-addition checklist, and the development workflow.

License§

Apache License 2.0. Project attribution in NOTICE, per Apache 2.0 §4(d).

Why Apache 2.0§

kfeatures is pure-Go userspace. No kernel source embedded, no cgo, no GPL/LGPL deps. Kernel interaction is uABI only: reads from /proc and /sys, syscalls and constants via golang.org/x/sys/unix (BSD-3-Clause), ELF parsing via github.com/cilium/ebpf (MIT; never calls BPF_PROG_LOAD). The kernel's COPYING carves "user programs that use kernel services by normal system calls" out of GPL: the carve-out ps, ls, and mount rely on.

Apache 2.0 over MIT:

  • Patent grant (§3). Probing eBPF, LSM, IMA, namespaces, and Spectre mitigations is patent-adjacent. Apache 2.0 grants an irrevocable patent license with defensive termination. MIT has none.
  • Adopter alignment. Cilium, Tetragon, Tracee, Falco, Pixie, and Inspektor Gadget are Apache 2.0. No compatibility review needed.

Thank you for getting this far...

This website doesn't allow commenting. The comments policy explains why.