Thanks for your interest in Wardyn! It's an eBPF watchdog for AI coding agents, written in Rust. Contributions — bug reports, docs, presets, code — are welcome.
By contributing you agree that your work is licensed under the project's AGPL-3.0-or-later license.
- Be respectful — see the Code of Conduct.
- Open an issue before a large change so we can agree on the approach.
- Report security issues privately — see SECURITY.md, not the public tracker.
Wardyn loads eBPF programs, so building and running it needs Linux. On
macOS/Windows, use a Linux VM (see scripts/setup-vm.sh).
Requirements:
- Rust nightly +
rust-src(pinned inrust-toolchain.toml). The eBPF crate is compiled with-Z build-std=corefor thebpfeltarget. bpf-linker, at the version pinned inBPF_LINKER_VERSION—scripts/setup-vm.shinstalls it, orcargo install bpf-linker --locked --version "$(cat BPF_LINKER_VERSION)". Pinned for the same reason as the compiler: it emits the bytecode the kernel verifies. Bumping it is a deliberate change — re-runjust e2eafterwards, and note that from 0.11 onwards bpf-linker needs a system LLVM matching rustc's (rustc -vVprints the version), not the one bundled with rustc.- To run enforcement: a kernel with BTF, cgroup v2, and BPF LSM
(
CONFIG_BPF_LSM=y+lsm=...,bpf; seescripts/enable-bpf-lsm.sh).
./scripts/setup-vm.sh # toolchain + bpf-linker (one-time)
cargo build # builds userspace + the eBPF object (via aya-build)
cargo test # unit tests (no root needed)
sudo ./target/debug/wardyn run -- bash # smoke-test observation.devcontainer/ builds the same toolchain in a container, so
"open the repo, run just build" works without provisioning a VM. It runs
privileged with seccomp unconfined, because Docker's default profile blocks
bpf(2).
Know what it can and cannot prove: a container shares the host's kernel.
Building, unit tests, --dry-run and (given cgroup v2) egress enforcement all
work; file/exec blocking does not, because BPF LSM is a host boot-time
setting — lsm=...,bpf — that nothing inside a container can turn on, and the VM
kernels behind Docker Desktop on macOS/Windows lack it. post-start.sh prints
which axes the host supports on every start. For a kernel-side change, the verdict
still comes from just e2e on a machine booted with the BPF LSM enabled.
See .devcontainer/README.md.
The policy engine and CLI live in wardyn-policy, deliberately free of
aya/libc, so the semantics stay testable anywhere:
just test-portable # cargo test -p wardyn-policy -p wardyn-common
just check-nolinker # type-check the userspace crate with no bpf-linkercheck-nolinker sets WARDYN_SKIP_EBPF_BUILD=1, which makes build.rs emit an
empty placeholder object. The resulting binary refuses to start — it exists to be
type-checked, never shipped.
CI runs these and they must pass — run them locally first (just lint does all
of the checks in one go):
cargo fmt --all --check
cargo clippy --locked --all-targets -- -D warnings
# the eBPF crate builds for a different target, so a plain clippy never sees it
cargo clippy --locked -p wardyn-ebpf --target bpfel-unknown-none -Zbuild-std=core -- -D warnings
shellcheck scripts/*.sh tests/e2e/run.sh .devcontainer/*.sh
cargo build --locked
cargo test --lockedKeep the build warning-free, including the eBPF crate.
Changing anything the kernel programs do? CI now loads the object for real:
just verify-programs (the Enforcement E2E workflow runs it) puts every
program in front of the verifier, including the file/exec LSM hooks, because
loading needs only CAP_BPF while attaching is what needs an active BPF LSM.
The same workflow then attaches the network hooks and asserts the kernel really
blocks. What is still not covered anywhere in CI is file/exec enforcement
firing — for that, run just e2e on a kernel booted with lsm=...,bpf and say
so in the PR.
Take a rejected program seriously: wardyn fails open, so it does not degrade, it disappears — a green feed over a kernel enforcing nothing.
wardyn/— userspace: map population, ring-buffer drain, TUI / plain feed, JSONL audit log, denial receipt. Linux-only (aya + libc).wardyn-ebpf/— the eBPF programs (tracepoints for observation; cgroup + LSM hooks for enforcement).#![no_std], verifier-constrained — read the comments.wardyn-common/— dependency-free types shared across the kernel/user boundary.wardyn-policy/— the policy engine and CLI parsing. Pure logic, no OS dependencies, and the single source of truth for policy semantics. If you change how rules resolve, add or update a test there.
Two invariants worth stating outright, because breaking either turns wardyn into a tool that lies:
- The userspace mirror must match the kernel matcher.
kernel_file_denialreproduces what the LSM hook does (basename, then ancestor directories bounded byMAX_DIR_WALK). Change one side and you must change the other, or the feed will showokfor a denied open. - Never claim a denial that wasn't made. A prediction is displayed; only a
DENY_*event from the deciding hook proves anything. If you add an enforcement path, make it report itself and bump itsSTATScounter.
Kernel offsets: the LSM file/exec matcher reads dentry fields at offsets
derived for a specific kernel (currently 6.8). If you build for another kernel,
regenerate them with scripts/kernel-offsets.sh
and update the OFFSETS_KERNEL constant.
- Small, focused commits with a clear subject line (imperative mood).
- Reference the milestone (M1–M4) or issue where relevant.
- Describe what changed and why, and how you tested it (which kernel, enforce on/off) — runtime enforcement can't be tested in CI.