All notable changes to AegisBPF will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Pre-install hook capability probe (
aegisbpf probe,src/hook_capabilities.{hpp,cpp},docs/HOOK_CAPABILITY_PROBE.md) — closes Honest Limitation #3. Operators can now runaegisbpf probebefore installing or rolling out AegisBPF on a fleet to find out, for each of the 14 LSM hooks AegisBPF wants to attach, whether the target kernel will let it. The probe loads vmlinux BTF (/sys/kernel/btf/vmlinux) and asks libbpf whether eachbpf_lsm_<hook>trampoline is present as aBTF_KIND_FUNC— the exact symbol BPF-LSM attach needs. Output JSON gains ahook_probe.hooks.<name>block per hook withkernel_supported,required,btf_symbol, anddescriptionfields, plus ahook_probe.btf_availablesummary so callers can distinguish "BTF was unavailable" from "BTF was there but symbol was missing". Catalog coverslsm_file_open,lsm_inode_permission(required), andlsm_bprm_check_security,lsm_bprm_ima_check,lsm_file_mmap,lsm_socket_{connect,bind,listen,accept,sendmsg,recvmsg},lsm_ptrace_access_check,lsm_locked_down,lsm_inode_copy_up(optional). Names mirror the keys in the daemon's runtime/var/lib/aegisbpf/capabilities.jsonso consumers can join "predicted attachable" against "actually attached". Probe needs no privileges beyond reading/sys/kernel/btf/vmlinuxand loads no BPF programs. New 5-test GTest suite (tests/test_hook_capabilities.cpp) pins the catalog shape (size + name set), enforces thebpf_lsm_*BTF-symbol prefix invariant, asserts onlylsm_file_open/lsm_inode_permissionare required, exercises the no-BTF path on hosts without/sys/kernel/btf/vmlinux(GTEST_SKIP()otherwise), and verifies the two required hooks resolve in vmlinux BTF on hosts that have it. Updates README Limitation #3 from "Runtime probing today; a machine-readable capability report is on the roadmap" to point ataegisbpf probeand the new doc.
- Bit-for-bit reproducible builds (
cmake/Reproducibility.cmake,AEGIS_REPRODUCIBLE_BUILD=ONby default) —aegisbpfis now byte-identical across builds from differing absolute source paths, hostnames, users, and wall-clock times, given the same compiler version andSOURCE_DATE_EPOCH. Implemented via-ffile-prefix-map=<src>=. -ffile-prefix-map=<build>=. -fdebug-prefix-map=<src>=. -fdebug-prefix-map=<build>=.(strips absolute paths from__FILE__, DWARF, and assertion macros),-Wl,--build-id=sha1(content-addressed build-id replacing the default uuid/random), andar -D/ranlib -D(zeroed mtime/uid/gid/mode on.aarchives, solibaegisbpf_lib.ais reproducible too).SOURCE_DATE_EPOCHis honoured and propagated. The flags module is included from the top-levelCMakeLists.txtafter all sanitizer/coverage/hardening flags so prefix-map applies to every TU.scripts/check_reproducible_build.shwas rewritten to do a real test: it stages two source-tree copies at distinctly different absolute paths (/tmp/aegis-repro-XXX/aaaaaa/srcvs…/bbbbbbbbbbbbbb/src), builds each withSKIP_BPF_BUILD=ON BUILD_TESTING=OFF, and compares the fullaegisbpfELF withsha256sum— no objcopy --strip-debug, no section extraction. On failure it runsdiffoscopeand retains the scratch trees (KEEP_TMP=1) for inspection. The previous workaround that compared only.text/.rodata/.data.rel.ropayloads is gone. CI runs the same script via.github/workflows/reproducibility.yml. Documented end-to-end indocs/REPRODUCIBLE_BUILDS.md(knob table, what's covered, what isn't, release-binary verification recipe). Adds a "Supply chain: Bit-for-bit reproducible builds" row to the README Standards Alignment matrix.
- BTFhub fallback resolver (
src/btf_loader.{hpp,cpp},AEGIS_BTF_PATHenv var) — explicit multi-tier lookup for the BTF blob handed to libbpf at BPF object load time, so kernels without/sys/kernel/btf/vmlinux(RHEL 7, very old embedded, stripped-down kernels) can still runaegisbpfd. Resolution order:AEGIS_BTF_PATHoverride →/sys/kernel/btf/vmlinux(kernel built-in) →/lib/modules/<release>/btf/vmlinux(Debian/Ubuntulinux-image-extralocation) →/var/lib/aegisbpf/btfs/<release>.btf(runtime cache) →/usr/lib/aegisbpf/btfs/<release>.btf(package-shipped) →/etc/aegisbpf/btfs/<release>.btf(operator-managed). An override that points at an unreadable file fails fast withBpfLoadFailedrather than silently falling back to the kernel BTF — a typo is much more likely than a deliberate mid-run swap, and a mismatched BTF would cause subtle CO-RE field-offset drift. The "no BTF found" path logs every searched location so operators can see exactly where to drop the blob. Pulled the inline lookup out ofbpf_ops.cppinto a pure functionresolve_btf_path(kernel_release, override) -> BtfResolution{path, source, searched}so it's testable without spinning up libbpf. New 7-test GTest suite (tests/test_btf_loader.cpp) covers env-var pickup, readable/unreadable override semantics, kernel-built-in preference, empty-kernel-release safety (no/lib/modules//btftraversal), andsearchedlist population. Documented end-to-end indocs/BTF_FALLBACK.md(resolution table, override semantics,scripts/btfgen.sh+ BTFhub-archive workflow, log examples). Flips the "Portability: BTFhub fallback for kernels without/sys/kernel/btf/vmlinux" row in the Standards Alignment matrix from Roadmap to shipped, and rewrites the matching Honest Limitation #6 from "unsupported" to "requires per-kernel blobs (here's how)".
- OCSF 1.1.0 event format (
--event-format=ocsf,src/ocsf_formatter.{hpp,cpp}) — opt-in OCSF JSON output for two highest-volume event types:BlockEventreshaped to OCSF File Activity (class_uid 1001, activity_id 14 Open) andNetBlockEventreshaped to OCSF Network Activity (class_uid 4001, activity_id 1 Open for connect/bind/listen/accept, activity_id 6 Traffic for sendmsg/recvmsg). Audit-mode events emitaction_id=1 (Allowed)with nodisposition_id; enforce-mode events emitaction_id=2 (Denied)+disposition_id=2 (Blocked). Severity scales (Low for audit, High for enforce). AegisBPF-specific forensic fields (inode/dev/cgroup id/exec id) are preserved under the OCSFunmappedextension so SIEM parsers see standard fields without losing evidence. Hostname cached at startup viagethostname(). NewEventFormatenum +set_event_format()/current_event_format()helpers; CLI flag acceptsaegis(default),ocsf,OCSF,ocsf-1.1,ocsf-1.1.0. Format dispatch is global and orthogonal to the sink (--log=stdout|journald|both); journald path stores the OCSF payload inMESSAGE=while preserving the existingAEGIS_*field set on the journal entry. New 9-test GTest suite (tests/test_ocsf_formatter.cpp) covers required fields per OCSF class, audit-vs-enforce semantics, file path resolution (raw vs resolved), root-file path handling, all six network direction codes, and CLI keyword acceptance. Out of scope (still emitted in AegisBPF-native shape today):ExecEvent,ExecArgvEvent,ForensicEvent,KernelBlockEvent,OverlayCopyUpEvent,state_change,control_change. Closesdocs/POSITIONING.md§3.3 OCSF row from "Roadmap" to "shipped for File + Network Activity"; flips the matching README Standards Alignment row.
- Landlock LSM filesystem self-sandbox (
--landlock,src/landlock.{hpp,cpp}) — opt-in post-init confinement of the daemon's own filesystem access to a fixed allowlist (RO/etc/aegisbpf,/usr/lib/aegisbpf,/proc,/sys/kernel/btf,$AEGIS_KEYS_DIR,dirname($AEGIS_BPF_OBJ); RW/var/lib/aegisbpf,/sys/fs/bpf). Directlandlock_create_ruleset/landlock_add_rule/landlock_restrict_selfsyscalls (no glibc wrapper dependency); raw__NR_*fallbacks for older libc. ABI version probed viaLANDLOCK_CREATE_RULESET_VERSION— ABI 2 picks upLANDLOCK_ACCESS_FS_REFER, ABI 3 addsLANDLOCK_ACCESS_FS_TRUNCATE. SetsNO_NEW_PRIVSunconditionally beforelandlock_restrict_self(idempotent with the seccomp path). Missing allowlist entries are logged and skipped, not fatal. Kernels without Landlock log a WARN and continue —--landlocknever fails startup on unsupported hosts. New 6-test GTest suite (tests/test_landlock_sandbox.cpp) covers ABI probe, default config shape,AEGIS_KEYS_DIRpickup, and a fork+restrict EACCES verification; self-skips viaGTEST_SKIP()when the kernel lacks Landlock. Closes the "daemon hardening: Landlock self-sandbox" row in the Standards Alignment matrix anddocs/HARDENING.md.
- Per-rule
actionfield onFileRuleandNetworkRule(AlloworBlock, defaultBlock) so a single policy can express both deny and allow semantics. Allow rules lower into the daemon's existing[allow_*]sections; no daemon change required. Allow > Blockmerge precedence inMergePolicies: any literal that appears in an[allow_*]section is removed from the corresponding[deny_*]section across the merged ConfigMap, mirroring Tetragon and KubeArmor behaviour. Sections that are emptied by the sweep are dropped from the final output.spec.workloadSelectorwith full KubernetesLabelSelectorsupport (matchLabels+matchExpressions: In/NotIn/Exists/DoesNotExist), plus a separatenamespaceSelectorandmatchNamespaceNamesshortcut. Replaces the v0.4.xPolicySelectorfor new policies.internal/selectorpackage that evaluatesworkloadSelectoragainst the live cluster (resolving namespaces, then matching pods inside each), with fallback to the legacyspec.selectoronly whenworkloadSelectoris unset.- Admission webhook validation for the new fields: rejects
Action=Allowon inode-based or protect file rules, detects in-specAllow/Blockcollisions on the same target (path / IP / CIDR / port / ip:port / binary hash), validatesLabelSelectorparseability, validatesmatchNamespaceNamesas DNS-1123 labels, and rejects cross-namespace selection from a namespaced AegisPolicy. Deprecatedstatus condition raised on policies that still usespec.selector, with reasonLegacySelectorInUse. The policy continues to reconcile normally; the condition is informational.- Pinned
controller-genMakefile workflow (make controller-gen / manifests / deepcopy / generate / verify-generated, controller-gen v0.14.0).verify-generatedfails CI when CRD YAML orzz_generated.deepcopy.godrift from the markers inapi/. - CRD schema regenerated for
aegispolicies.aegisbpf.ioandaegisclusterpolicies.aegisbpf.io: addsworkloadSelector(podSelector,namespaceSelector,matchNamespaceNames) and the per-ruleactionenum defaulted toBlock. - New example
operator/examples/allow-override.yamldemonstrating the cross-policy Allow override flow (a global block + a namespaced allow carve-out).
- v0.4.x policies that use
spec.selectorcontinue to admit, reconcile, and translate to byte-identical INI output. They simply gain aDeprecated=Truecondition. - The per-rule
actionfield defaults toBlock, so existing rule lists keep their original semantics with no edits. - The CRD remains
v1alpha1. Nov1alpha2bump. - Daemon (
policy_parse.cppand the BPF maps) is unchanged in v0.5.0; the operator translates per-rule Action into the existing[allow_*]and[deny_*]INI sections.
- Per-hook latency tracking (
hook_latencyPERCPU_ARRAY map) — records total, count, min, and max nanoseconds per LSM/tracepoint hook invocation for overhead benchmarking - In-kernel event pre-filtering (
event_approver_inode,event_approver_pathmaps) — Datadog-style approver/discarder pattern to suppress noisy events in-kernel, reducing ring buffer pressure - Priority ring buffer (
priority_events, 4 MB) — dedicated ring buffer for security-critical forensic events, isolated from the main events ring buffer to prevent drops - Forensic event capture (
ForensicEvent/forensic_block) — enriched block events with UID/GID, exec identity stage, verified_exec flag, and process context, emitted via the priority ring buffer - Startup self-tests (
src/selftest.{hpp,cpp}) — Datadog-pattern startup validation: map accessibility, ring buffer FD, config readability, and process_tree write/read/delete cycle - Map capacity monitoring (
src/map_monitor.{hpp,cpp}) — iterates BPF map entries to compute usage ratios and log warnings when thresholds are exceeded - Process cache /proc reconciliation (
src/proc_scan.{hpp,cpp}) — scans /proc at startup to populate process_tree with pre-existing processes - BPF program signing preparation (
src/bpf_signing.{hpp,cpp}) — SHA-256 hash verification of BPF object files with Ed25519 signature placeholder, break-glass override viaAEGIS_ALLOW_UNSIGNED_BPF - Binary hash verification (
src/binary_hash.{hpp,cpp}) — SHA-256 integrity verification for binary allow-lists with recursive directory scanning - Hot-loadable detection rules (
src/rule_engine.{hpp,cpp}) — JSON-based detection rule engine with comm/path matching, severity levels, and thread-safe hot-reload - Plugin/extension system (
src/plugin.{hpp,cpp}) — abstract plugin interface with virtual event handlers, lifecycle management, and break-on-consume dispatch; ships with built-in JsonLoggerPlugin
- Real kernel BPF testing (
.github/workflows/kernel-bpf-test.yml) — virtme-ng boots a real kernel in CI to test BPF object loading and map creation - BPF code coverage analysis (
.github/workflows/bpf-coverage.yml) — llvm-objdump instruction counting per BPF program with JSON summary artifact
- BPF hook functions instrumented with
record_hook_latency()calls at every return point acrossaegis_exec.bpf.h,aegis_file.bpf.h, andaegis_net.bpf.h handle_event()now processesEVENT_FORENSIC_BLOCKevents from the priority ring bufferBpfStateextended withhook_latency,event_approver_inode,event_approver_path, andpriority_eventsmap pointers- Daemon startup now runs self-tests, reconciles /proc, and checks map capacity after ring buffer creation
- Event union extended with
ForensicEvent forensicmember - Event schema (
config/event-schema.json) extended withForensicBlockEventdefinition - BPF map schema (
docs/BPF_MAP_SCHEMA.md) updated with 4 new maps and memory budget - Feature surface contract updated to validate new components
ForensicEventstatic_assert corrected to 104 bytes (was 112)
- Test suite: 210/210 passing
- Feature surface contract: passing
- Build: zero errors, zero warnings
- CRITICAL FIX: Eliminated TweetNaCl memory exhaustion vulnerability
- Replaced unbounded heap allocation with fixed 4KB stack-based buffers
- Added size validation to prevent memory exhaustion DoS attacks
- Implemented secure buffer zeroing with volatile pointers
- See
docs/SECURITY_FIX_TWEETNACL_MEMORY.mdfor full details
- New safe crypto wrapper functions (
src/tweetnacl_safe.hpp)crypto_sign_detached_safe()- Stack-based signature generationcrypto_sign_verify_detached_safe()- Stack-based signature verification- Size limit: 4096 bytes (33× larger than actual usage)
- Comprehensive test suite for crypto safety (
tests/test_crypto_safe.cpp)- 13 new tests covering edge cases and security boundaries
- Tests for empty messages, invalid signatures, size limits
- Security fix documentation and verification script
docs/SECURITY_FIX_TWEETNACL_MEMORY.md- Detailed security analysisSECURITY_FIX_SUMMARY.md- Implementation summaryscripts/verify_security_fix.sh- Automated verification
- Updated
crypto.cppto use safe crypto wrappers exclusively - Enhanced error messages to indicate size limit constraints
- Updated
SECURITY.mdwith security fixes history section
- Neutral to positive impact: stack allocation faster than heap
- Predictable memory usage with no fragmentation
- No measurable difference in test suite runtime
- Test suite expanded: 153 → 157 tests (all passing)
- Added edge case tests: empty messages, invalid signatures, boundary conditions
- Full backward compatibility verified
- OWASP Top 10 2021 compliant
- CERT Secure Coding Standards compliant
- CWE/SANS Top 25 compliant
- Memory safety guaranteed
- No breaking changes - fully backward compatible
- New limitation: Messages > 4096 bytes rejected (no legitimate use cases affected)
- All existing functionality preserved
0.1.0 - Previous Release
- Result error handling throughout the codebase
- Constant-time hash comparison (
constant_time_hex_compare()) to prevent timing side-channel attacks - Structured logging with text and JSON output formats
--log-leveland--log-formatCLI options--seccompflag for runtime syscall filtering- Thread-safe caching for cgroup and path resolution
- RAII wrappers for popen (PipeGuard) and ring_buffer (RingBufferGuard)
- Input validation for CLI path arguments
- Google Test unit tests for core components
- Google Benchmark performance tests
- Sanitizer builds (ASAN, UBSAN, TSAN)
- Code coverage reporting with gcovr and Codecov
- Comprehensive CI pipeline with test, sanitizer, and coverage jobs
- AppArmor profile for runtime confinement
- SELinux policy module
- Sigstore/Cosign code signing for releases
- SBOM generation (SPDX and CycloneDX)
- Prometheus alert rules
- Grafana dashboard
- JSON Schema for event validation
- Event schema validation tests and sample payloads
- SIEM integration documentation
- Dockerfile for containerized deployment
- Helm chart for Kubernetes deployment
- Architecture documentation
- Troubleshooting guide
- Man page
- Dev check and environment verification scripts
- Enforce-mode smoke test script
- Nightly fuzz workflow, perf regression workflow, and kernel matrix workflow
- All functions now return Result instead of int/bool
- Replaced std::cerr/std::cout with structured logging
- Improved error messages with context
- Event schema aligned with emitted JSON fields
- README/architecture diagrams updated to file-open enforcement
- popen() file descriptor leak in kernel config check
- Race conditions in cgroup path cache
- Race conditions in CWD resolution cache
- Thread-safety issue in journal error reporting
- Added seccomp-bpf syscall filter
- Added AppArmor and SELinux policies
- Added input validation for all user-provided paths
- Added constant-time comparison for all hash verification (BPF integrity, policy SHA256, bundle verification)
- Disabled
AEGIS_SKIP_BPF_VERIFYbypass in Release builds (only available in Debug builds) - Added try-catch exception handling in signed bundle parser to prevent crashes on malformed input
- Extended
json_escape()to handle all control characters, preventing JSON injection in logs
0.1.0 - 2024-01-01
- Initial release
- BPF LSM-based execution blocking
- Tracepoint-based audit mode (fallback)
- Policy file support with deny_path, deny_inode, allow_cgroup sections
- SHA256 hash-based blocking
- Prometheus metrics endpoint
- Journald integration
- CLI commands: run, block, allow, policy, stats, metrics, health