Skip to content

Development

SigmaHQ compatibility gate

Engine compatibility is checked against the active rule trees from the SigmaHQ commit pinned in compatibility/sigmahq-baseline.json. The gate loads the corpus through the RSigma parser for Windows, Linux, and macOS routing, then compares verdict and reason counts with the baseline. Parser or compiler failures, panics, unaccounted skip reasons, unknown unsupported-document reasons, and count drift fail the test. The emitted sigma-compatibility.json file is uploaded by CI for machine consumption.

To reproduce the gate locally, check out the pinned SigmaHQ commit and run:

RUSTINEL_EBPF_STUB=1 \
RUSTINEL_SIGMA_CORPUS_DIR=/path/to/sigma \
cargo test --locked --test sigma_corpus_compatibility -- --include-ignored

This test only parses, compiles, and routes rules. Live atomic firing tests and curated detection packs belong in rustinel-rules and are not run by this gate.

Build Matrix

Target Tooling
Windows userspace build Rust 1.92+, Visual Studio Build Tools
Linux userspace build Rust 1.92+
Linux eBPF object build nightly Rust, rust-src, bpf-linker
macOS userspace build Rust 1.92+, Xcode Command Line Tools

Fastest Local Build Paths

Userspace

cargo check --workspace
cargo build
cargo build --release

On Windows, run the final binary from an elevated PowerShell. On Linux, run it as root or with the required eBPF capabilities.

Linux eBPF Build

If ebpf/rustinel-ebpf.o already exists, a normal root cargo build embeds it and no nightly eBPF rebuild is needed.

If you need to rebuild the eBPF object from source:

rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
cargo install bpf-linker

cd ebpf
cargo +nightly build --release --bin rustinel-ebpf
cp target/bpfel-unknown-none/release/rustinel-ebpf rustinel-ebpf.o
cd ..

build.rs watches ebpf/src, ebpf/Cargo.toml, and ebpf/rustinel-ebpf.o. On Linux builds it embeds either the prebuilt object or a freshly compiled one.

Windows

cargo run -- run

Linux

sudo cargo run -- run

Linux With eBPF Override

Useful when iterating on ebpf/ without rebuilding the full userspace binary:

sudo env RUSTINEL_EBPF_OBJECT=$PWD/ebpf/rustinel-ebpf.o ./target/release/rustinel run

macOS

For normal contributor work, start with cargo check, cargo build, and the regular test suite. The signed app-bundle path below is only required when you need live Endpoint Security telemetry on macOS or when preparing a release.

macOS telemetry uses Apple's Endpoint Security framework. Creating an ES client (es_new_client) requires three things, and each missing piece surfaces a distinct startup error: running as root (NotPrivileged if not), the com.apple.developer.endpoint-security.client entitlement (NotEntitled if missing), and user approval via Full Disk Access / TCC (NotPermitted if not granted).

Maintainer release builds require Apple to approve the managed Endpoint Security capability. After approval, enable Endpoint Security on the explicit App ID used for the request and download a matching macOS provisioning profile. Install the profile's signing certificate and private key in the login keychain. Confirm that macOS can see the identity:

security find-identity -v -p codesigning

Build the binary, then create the app-like daemon bundle required for a restricted entitlement:

cargo build --release

scripts/macos/package-app.sh \
  --binary target/release/rustinel \
  --output target/release/Rustinel.app \
  --profile "$HOME/Downloads/rustinel.provisionprofile" \
  --identity "Developer ID Application: Example (TEAMID)"

The script derives the bundle identifier from the profile, validates that the profile authorizes Endpoint Security, embeds it at Contents/embedded.provisionprofile, adds the profile App ID and Team ID to the signed entitlements, enables the hardened runtime, and verifies the bundle. On macOS, YARA-X uses Wasmtime's Pulley interpreter because Endpoint Security clients cannot use hardened runtime relaxation entitlements for JIT executable memory.

Grant Rustinel.app Full Disk Access in System Settings, then run:

sudo ./target/release/Rustinel.app/Contents/MacOS/rustinel run

In another terminal, trigger and inspect the bundled demo:

whoami
cat logs/alerts.json.*

For a SIP-disabled VM or dedicated test Mac, an ad-hoc bundle can still be created without a profile:

scripts/macos/package-app.sh \
  --binary target/release/rustinel \
  --output target/release/Rustinel.app \
  --adhoc

Do not use the ad-hoc path on a normal SIP-enabled Mac. Release builds require the Developer ID identity, the Endpoint Security provisioning profile, and a successful notarization. CI expects MACOS_SIGN_IDENTITY, MACOS_CERT_P12_BASE64, MACOS_CERT_PASSWORD, MACOS_PROVISIONING_PROFILE_BASE64, MACOS_NOTARY_APPLE_ID, MACOS_NOTARY_TEAM_ID, and MACOS_NOTARY_PASSWORD.

Testing

Unit and Integration Tests

cargo test --locked
cargo test --locked --test pipeline_sigma
cargo test --locked --test yara_disk
cargo test --locked --test yara_memory

Cargo auto-discovers the integration tests in tests/*.rs. The normal suite uses synthetic events and temporary Sigma, YARA, and IOC fixtures, so it does not require administrator/root privileges.

Ignored live tests cover process memory scanning and active-response termination. Build the test target first, then opt in explicitly:

cargo build --locked --example memory_target
cargo test --locked --test yara_memory -- --include-ignored
cargo test --locked --test active_response -- --include-ignored

Those ignored tests may require administrator rights on Windows or a controlled Linux host with permissive process-memory access.

Atomic Detection Gates

Pull requests run the rules repository's atomic detection suite against fresh Linux and Windows engine builds. The suite starts each binary with the required privileges, performs safe platform-specific actions, and checks for the alert that each action should produce. The rules checkout is pinned in both CI workflows so changes to detection content are introduced deliberately.

The pull request checks are currently visible but non-blocking while the short-lived process race is stabilized. The same checks run as blocking jobs after a push to the default branch. Once the suite is reliable, remove the conditional continue-on-error setting and require both matrix checks in branch protection.

Release tags run the same suite against the Linux x86_64, Linux arm64, Windows, and signed macOS artifacts before publish can run. The Intel macOS leg is optional because it uses the legacy hosted runner.

Replay Regression Fixture

A golden recording guards against a change to normalization, serialization, or matching quietly stopping a rule from firing:

tests/fixtures/replay/
├── windows-powershell-fixture.ps1     benign behavior generator
├── windows-powershell.ndjson          the recording
├── windows-powershell.manifest.json   its manifest
└── sigma/                             the rules it must fire

tests/replay_fixture.rs replays it against those rules on every platform in ordinary CI (no sensors, no privileges) and asserts both rules fire in the recorded order. Because it is a Windows capture, it also proves a recording replays away from the platform that produced it.

To regenerate it:

  1. On a Windows lab endpoint, run rustinel capture --output windows-powershell.ndjson.
  2. Run windows-powershell-fixture.ps1, then stop the capture with Ctrl-C.
  3. Confirm the manifest reads "status": "complete".
  4. Copy both files into tests/fixtures/replay/, replacing the previous pair. Never edit a recording by hand: the manifest checksum is verified on every replay and an edited payload is rejected.
  5. Run cargo test --test replay_fixture.

Development Environment Variables

Variable Purpose
RUSTINEL_EBPF_OBJECT Absolute path to a compiled .o, used instead of the embedded Linux object
RUSTINEL_BPF_INTERFACE macOS capture interface, when the default en0 is not the active one

Code Quality

cargo fmt
cargo clippy

Project Structure

src/
├── main.rs             # CLI entry point and platform bootstrapping
├── config.rs           # Config loading and defaults
├── alerts.rs           # ECS NDJSON alert sink
├── engine/             # Sigma rule parsing, classification, and evaluation
├── ioc/                # Atomic IOC loading and matching
├── models/             # Event, alert, and ECS data models
├── normalizer/         # Shared event normalization and enrichment
├── reload/             # Hot reload poller and worker
├── response/           # Optional process termination logic
├── scanner/            # YARA compilation and scanning
├── sensor/
│   ├── windows/        # ETW sensor implementation
│   ├── linux/          # eBPF userspace loader and event decoding
│   └── macos/          # Endpoint Security sensor
├── state/              # Process, DNS, SID, and network aggregation state
└── utils/              # Platform-specific helpers

ebpf/
└── src/                # Linux eBPF programs and event ABI definitions

Logging Guidance

Use the existing logging contract when adding runtime logs:

  • trace: high-volume internals
  • debug: troubleshooting detail
  • info: lifecycle and positive detections
  • warn and error: degraded behavior or failures

If a line can fire on most normal events, it does not belong at debug.