Detection as Code

June 23, 2026 · View on GitHub

Detection as Code exists from day one but stays lightweight for v1. The goal is to make quality visible through CI without blocking contribution on heavyweight infrastructure.

Mandatory v1 checks

These run in CI on every push and pull request (tools/validate.py, then tools/build_packs.py):

CheckWhere
Lint + format (ruff)CI (uv run ruff)
Type check (ty)CI (uv run ty)
Valid YAMLvalidate.py
Valid Sigma structurevalidate.py
YARA compiles (yara-x)validate.py
Valid IOC set (schema)validate.py
IOC value sanity (hash/ip/domain/regex)validate.py
Required metadatavalidate.py
Unique artifact IDsvalidate.py
Source / license metadatavalidate.py
ATT&CK tags when relevantvalidate.py
Pack attack_coverage drift guardvalidate.py
Rustinel telemetry compatibilityvalidate.py
Pack manifest validation (schema)validate.py
Rule reference / extends integrityvalidate.py
Sigma load/compile validationvalidate.py (stub) †
Engine-ready packs + zip artifactsbuild_packs.py
Flattened IOC files (per type)build_packs.py
Generated index.jsonbuild_packs.py

YARA content is compiled with yara-x (the same engine Rustinel embeds), so a rule that won't load is a hard CI failure rather than a structural guess. If yara-x is not installed locally, validate.py falls back to structural brace/condition checks and warns.

† Sigma load/compile validation is still structural. Once a Rustinel CLI is available in CI, replace the structural stub with rustinel compile <pack> to get a true Sigma load/compile gate.

Progressive / optional checks

Added incrementally as the project matures:

  • Atomic firing tests — implemented (see Firing tests)
  • Detection coverage reports
  • Performance smoke tests
  • Field mapping coverage
  • False-positive feedback tracking
  • Artifact checksums in releases (the build already emits sha256 per zip in index.json)

Dynamic testing policy

We do not require one full dynamic / end-to-end test per rule in v1. Dynamic tests start small:

Pack levelv1 requirement
EssentialSelected Atomic tests where they make sense
AdvancedBest-effort dynamic tests
HuntingNo dynamic test requirement

Priority: prove that the most important Essential detections work end to end.

Firing tests

Beyond static validation, the repo ships atomic firing tests that prove a rule actually fires against the real engine. The harness (tests/atomic/, orchestrated by run_atomics.py) installs a released Rustinel engine on a real runner, performs a small safe atomic action for each rule — the behaviour the rule is meant to catch — and checks the engine wrote a matching alert.

atomic action  ->  real OS telemetry (eBPF / ETW / ES)  ->  rustinel  ->  alert?

Run by .github/workflows/atomic.yml on every pull request:

PlatformSensorGates CI?
LinuxeBPFyes
WindowsETWyes
macOSEndpoint Securityno — experimental, reports only (continue-on-error)

The same workflow runs run_atomics.py --check-coverage --strict-essential, which flags any rule marked test_status: atomic that lacks a test, plus Essential rules still left untested. See tests/atomic/README.md for how a test is judged and how to add one.

TTP / Atomic / CTI strategy

The baseline is TTP/Atomic-based, not threat-feed-based. Each detection should map to:

  • ATT&CK tactics
  • ATT&CK techniques
  • Rustinel telemetry
  • reproducible behavior
  • Atomic-style validation where possible

CTI is used to prioritize future additions, not to blindly import noisy feed content.